• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

liqd / adhocracy-plus / 18908688697

29 Oct 2025 12:59PM UTC coverage: 44.622% (-44.5%) from 89.135%
18908688697

Pull #2986

github

web-flow
Merge 1dfde8ee7 into 445e1d498
Pull Request #2986: Draft: Speed up Github Ci Tests

3012 of 6750 relevant lines covered (44.62%)

0.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.71
/apps/notifications/emails.py
1
from django.contrib import auth
1✔
2
from django.urls import reverse
1✔
3
from wagtail.models import Site
1✔
4

5
from apps.cms.settings.models import ImportantPages
1✔
6
from apps.organisations.models import Organisation
1✔
7
from apps.projects import tasks
1✔
8
from apps.users.emails import EmailAplus as Email
1✔
9

10
User = auth.get_user_model()
1✔
11

12

13
def _exclude_actor(receivers, actor):
1✔
14
    if not actor:
×
15
        return receivers
×
16

17
    if hasattr(receivers, "exclude"):
×
18
        return receivers.exclude(id=actor.id)
×
19

20
    return [receiver for receiver in receivers if not receiver == actor]
×
21

22

23
def _exclude_moderators(receivers, action):
1✔
24
    if hasattr(action, "project"):
×
25
        moderator_ids = action.project.moderators.values_list("id", flat=True)
×
26

27
        if hasattr(receivers, "exclude"):
×
28
            return receivers.exclude(id__in=moderator_ids)
×
29

30
        return [user for user in receivers if user.id not in moderator_ids]
×
31

32
    return receivers
×
33

34

35
def _exclude_notifications_disabled(receivers):
1✔
36
    if hasattr(receivers, "filter"):
×
37
        return receivers.filter(get_notifications=True)
×
38

39
    return [user for user in receivers if user.get_notifications]
×
40

41

42
class NotifyCreatorEmail(Email):
1✔
43
    template_name = "a4_candy_notifications/emails/notify_creator"
1✔
44

45
    def get_organisation(self):
1✔
46
        return self.object.project.organisation
×
47

48
    def get_receivers(self):
1✔
49
        action = self.object
×
50
        if hasattr(action.target, "creator"):
×
51
            receivers = [action.target.creator]
×
52
            receivers = _exclude_notifications_disabled(receivers)
×
53
            receivers = _exclude_actor(receivers, action.actor)
×
54
            receivers = _exclude_moderators(receivers, action)
×
55
            return receivers
×
56
        return []
×
57

58

59
class NotifyCreatorOnModeratorFeedback(Email):
1✔
60
    template_name = "a4_candy_notifications/emails/notify_creator_on_moderator_feedback"
1✔
61

62
    def get_organisation(self):
1✔
63
        return self.object.project.organisation
×
64

65
    def get_receivers(self):
1✔
66
        receivers = [self.object.creator]
×
67
        receivers = _exclude_notifications_disabled(receivers)
×
68
        return receivers
×
69

70
    def get_context(self):
1✔
71
        context = super().get_context()
×
72
        context["object"] = self.object
×
73
        return context
×
74

75

76
class NotifyCreatorOnModeratorBlocked(Email):
1✔
77
    template_name = "a4_candy_notifications/emails/notify_creator_on_moderator_blocked"
1✔
78

79
    def get_organisation(self):
1✔
80
        return self.object.project.organisation
×
81

82
    def get_receivers(self):
1✔
83
        receivers = [self.object.creator]
×
84
        receivers = _exclude_notifications_disabled(receivers)
×
85
        return receivers
×
86

87
    def get_netiquette_url(self):
1✔
88
        organisation = self.get_organisation()
×
89
        site = Site.objects.filter(is_default_site=True).first()
×
90
        important_pages = ImportantPages.for_site(site)
×
91
        if organisation.netiquette:
×
92
            return reverse(
×
93
                "organisation-netiquette",
94
                kwargs={"organisation_slug": organisation.slug},
95
            )
96
        elif (
×
97
            getattr(important_pages, "netiquette")
98
            and getattr(important_pages, "netiquette").live
99
        ):
100
            return getattr(important_pages, "netiquette").url
×
101
        else:
102
            return ""
×
103

104
    def get_discussion_url(self):
1✔
105
        if self.object.parent_comment.exists():
×
106
            return self.object.parent_comment.first().content_object.get_absolute_url()
×
107
        elif self.object.content_object.get_absolute_url():
×
108
            return self.object.content_object.get_absolute_url()
×
109
        else:
110
            return self.object.module.get_detail_url
×
111

112
    def get_context(self):
1✔
113
        context = super().get_context()
×
114
        context["module"] = self.object.module
×
115
        context["project"] = self.object.project
×
116
        context["netiquette_url"] = self.get_netiquette_url()
×
117
        context["discussion_url"] = self.get_discussion_url()
×
118
        return context
×
119

120

121
class NotifyCreatorOnModeratorCommentFeedback(Email):
1✔
122
    template_name = (
1✔
123
        "a4_candy_notifications/emails" "/notify_creator_on_moderator_comment_feedback"
124
    )
125

126
    def get_organisation(self):
1✔
127
        return self.object.project.organisation
×
128

129
    def get_receivers(self):
1✔
130
        receivers = [self.object.comment.creator]
×
131
        receivers = _exclude_notifications_disabled(receivers)
×
132
        return receivers
×
133

134
    def get_context(self):
1✔
135
        context = super().get_context()
×
136
        context["project"] = self.object.project
×
137
        context["moderator_name"] = self.object.creator.username
×
138
        context["moderator_feedback"] = self.object.feedback_text
×
139
        context["comment_url"] = self.object.comment.get_absolute_url()
×
140
        return context
×
141

142

143
class NotifyModeratorsEmail(Email):
1✔
144
    template_name = "a4_candy_notifications/emails/notify_moderator"
1✔
145

146
    def get_organisation(self):
1✔
147
        return self.object.project.organisation
×
148

149
    def get_receivers(self):
1✔
150
        action = self.object
×
151
        receivers = action.project.moderators.all()
×
152
        receivers = _exclude_actor(receivers, action.actor)
×
153
        receivers = _exclude_notifications_disabled(receivers)
×
154
        return receivers
×
155

156

157
class NotifyInitiatorsOnProjectCreatedEmail(Email):
1✔
158
    template_name = "a4_candy_notifications/emails/notify_initiators_project_created"
1✔
159

160
    def get_organisation(self):
1✔
161
        return self.object.organisation
×
162

163
    def get_receivers(self):
1✔
164
        project = self.object
×
165
        creator = User.objects.get(pk=self.kwargs["creator_pk"])
×
166
        receivers = project.organisation.initiators.all()
×
167
        receivers = _exclude_actor(receivers, creator)
×
168
        receivers = _exclude_notifications_disabled(receivers)
×
169
        return receivers
×
170

171
    def get_context(self):
1✔
172
        context = super().get_context()
×
173
        creator = User.objects.get(pk=self.kwargs["creator_pk"])
×
174
        context["creator"] = creator
×
175
        context["project"] = self.object
×
176
        return context
×
177

178

179
class NotifyInitiatorsOnProjectDeletedEmail(Email):
1✔
180
    template_name = "a4_candy_notifications/emails/notify_initiators_project_deleted"
1✔
181

182
    @classmethod
1✔
183
    def send_no_object(cls, object, *args, **kwargs):
1✔
184
        organisation = object.organisation
×
185
        object_dict = {
×
186
            "name": object.name,
187
            "initiators": list(
188
                organisation.initiators.all().distinct().values_list("email", flat=True)
189
            ),
190
            "organisation_id": organisation.id,
191
        }
192
        tasks.send_async_no_object.delay(
×
193
            cls.__module__, cls.__name__, object_dict, args, kwargs
194
        )
195
        return []
×
196

197
    def get_organisation(self):
1✔
198
        try:
×
199
            return Organisation.objects.get(id=self.object["organisation_id"])
×
200
        except Organisation.DoesNotExist:
×
201
            pass
×
202

203
    def get_receivers(self):
1✔
204
        return self.object["initiators"]
×
205

206
    def get_context(self):
1✔
207
        context = super().get_context()
×
208
        context["name"] = self.object["name"]
×
209
        return context
×
210

211

212
class NotifyFollowersOnPhaseStartedEmail(Email):
1✔
213
    template_name = "a4_candy_notifications/emails" "/notify_followers_phase_started"
1✔
214

215
    def get_organisation(self):
1✔
216
        return self.object.project.organisation
×
217

218
    def get_receivers(self):
1✔
219
        action = self.object
×
220
        receivers = User.objects.filter(
×
221
            follow__project=action.project,
222
            follow__enabled=True,
223
        )
224
        receivers = _exclude_notifications_disabled(receivers)
×
225
        return receivers
×
226

227

228
class NotifyFollowersOnPhaseIsOverSoonEmail(Email):
1✔
229
    template_name = "a4_candy_notifications/emails" "/notify_followers_phase_over_soon"
1✔
230

231
    def get_organisation(self):
1✔
232
        return self.object.project.organisation
×
233

234
    def get_receivers(self):
1✔
235
        action = self.object
×
236
        receivers = User.objects.filter(
×
237
            follow__project=action.project,
238
            follow__enabled=True,
239
        )
240
        receivers = _exclude_notifications_disabled(receivers)
×
241
        return receivers
×
242

243

244
class NotifyFollowersOnUpcommingEventEmail(Email):
1✔
245
    template_name = "a4_candy_notifications/emails" "/notify_followers_event_upcomming"
1✔
246

247
    def get_organisation(self):
1✔
248
        return self.object.project.organisation
×
249

250
    def get_receivers(self):
1✔
251
        action = self.object
×
252
        receivers = User.objects.filter(
×
253
            follow__project=action.project,
254
            follow__enabled=True,
255
        )
256
        receivers = _exclude_notifications_disabled(receivers)
×
257
        return receivers
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc