wbmailing 2.2.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wbmailing might be problematic. Click here for more details.
- wbmailing/__init__.py +1 -0
- wbmailing/admin.py +74 -0
- wbmailing/apps.py +14 -0
- wbmailing/backend.py +131 -0
- wbmailing/celery.py +0 -0
- wbmailing/dynamic_preferences_registry.py +35 -0
- wbmailing/factories.py +211 -0
- wbmailing/filters/__init__.py +8 -0
- wbmailing/filters/mailing_lists.py +84 -0
- wbmailing/filters/mails.py +74 -0
- wbmailing/management/__init__.py +22 -0
- wbmailing/migrations/0001_initial_squashed_squashed_0008_alter_mail_bcc_email_alter_mail_cc_email_and_more.py +649 -0
- wbmailing/migrations/0002_delete_mailingsettings.py +16 -0
- wbmailing/migrations/0003_alter_mailinglistsubscriberchangerequest_options.py +25 -0
- wbmailing/migrations/__init__.py +0 -0
- wbmailing/models/__init__.py +6 -0
- wbmailing/models/mailing_lists.py +386 -0
- wbmailing/models/mails.py +895 -0
- wbmailing/serializers/__init__.py +19 -0
- wbmailing/serializers/mailing_lists.py +209 -0
- wbmailing/serializers/mails.py +251 -0
- wbmailing/tasks.py +37 -0
- wbmailing/templatetags/__init__.py +0 -0
- wbmailing/templatetags/mailing_tags.py +22 -0
- wbmailing/tests/__init__.py +0 -0
- wbmailing/tests/conftest.py +30 -0
- wbmailing/tests/models/__init__.py +0 -0
- wbmailing/tests/models/test_mailing_lists.py +297 -0
- wbmailing/tests/models/test_mails.py +205 -0
- wbmailing/tests/signals.py +124 -0
- wbmailing/tests/test_serializers.py +28 -0
- wbmailing/tests/test_tasks.py +49 -0
- wbmailing/tests/test_viewsets.py +216 -0
- wbmailing/tests/tests.py +142 -0
- wbmailing/urls.py +90 -0
- wbmailing/viewsets/__init__.py +32 -0
- wbmailing/viewsets/analytics.py +110 -0
- wbmailing/viewsets/buttons/__init__.py +10 -0
- wbmailing/viewsets/buttons/mailing_lists.py +91 -0
- wbmailing/viewsets/buttons/mails.py +98 -0
- wbmailing/viewsets/display/__init__.py +16 -0
- wbmailing/viewsets/display/mailing_lists.py +175 -0
- wbmailing/viewsets/display/mails.py +318 -0
- wbmailing/viewsets/endpoints/__init__.py +8 -0
- wbmailing/viewsets/endpoints/mailing_lists.py +86 -0
- wbmailing/viewsets/endpoints/mails.py +51 -0
- wbmailing/viewsets/mailing_lists.py +320 -0
- wbmailing/viewsets/mails.py +425 -0
- wbmailing/viewsets/menu/__init__.py +5 -0
- wbmailing/viewsets/menu/mailing_lists.py +37 -0
- wbmailing/viewsets/menu/mails.py +25 -0
- wbmailing/viewsets/titles/__init__.py +17 -0
- wbmailing/viewsets/titles/mailing_lists.py +63 -0
- wbmailing/viewsets/titles/mails.py +55 -0
- wbmailing-2.2.1.dist-info/METADATA +5 -0
- wbmailing-2.2.1.dist-info/RECORD +57 -0
- wbmailing-2.2.1.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from django.contrib.messages import warning
|
|
2
|
+
from django.contrib.postgres.aggregates import StringAgg
|
|
3
|
+
from django.db.models import Count, Exists, F, OuterRef, Q, Subquery
|
|
4
|
+
from django.db.models.functions import Coalesce
|
|
5
|
+
from django.shortcuts import get_object_or_404, redirect, render
|
|
6
|
+
from django.utils.functional import cached_property
|
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
|
8
|
+
from django.views.generic import View
|
|
9
|
+
from rest_framework.decorators import action
|
|
10
|
+
from rest_framework.response import Response
|
|
11
|
+
from wbcore import serializers as wb_serializers
|
|
12
|
+
from wbcore import viewsets
|
|
13
|
+
from wbcore.contrib.directory.models import Company, EmailContact, Entry
|
|
14
|
+
from wbmailing import models, serializers
|
|
15
|
+
from wbmailing.filters import (
|
|
16
|
+
EmailContactMailingListFilterSet,
|
|
17
|
+
MailingListEmailContactThroughModelModelFilterSet,
|
|
18
|
+
MailingListFilterSet,
|
|
19
|
+
)
|
|
20
|
+
from wbmailing.models import (
|
|
21
|
+
MailingListEmailContactThroughModel,
|
|
22
|
+
MailingListSubscriberChangeRequest,
|
|
23
|
+
)
|
|
24
|
+
from wbmailing.viewsets.buttons import (
|
|
25
|
+
MailingListButtonConfig,
|
|
26
|
+
MailingListEmailContactThroughModelButtonConfig,
|
|
27
|
+
MailingListSubcriptionRequestButtonConfig,
|
|
28
|
+
)
|
|
29
|
+
from wbmailing.viewsets.display import (
|
|
30
|
+
EmailContactMailingListDisplayConfig,
|
|
31
|
+
MailingListDisplayConfig,
|
|
32
|
+
MailingListEntryDisplayConfig,
|
|
33
|
+
MailingListSubscriberChangeRequestDisplayConfig,
|
|
34
|
+
MailingListSubscriberRequestEntryDisplayConfig,
|
|
35
|
+
MailingListSubscriberRequestMailingListDisplayConfig,
|
|
36
|
+
)
|
|
37
|
+
from wbmailing.viewsets.endpoints import (
|
|
38
|
+
EmailContactMailingListEndpointConfig,
|
|
39
|
+
MailingListEntryEndpointConfig,
|
|
40
|
+
MailingListSubscriberRequestEntryEndpointConfig,
|
|
41
|
+
MailingListSubscriberRequestMailingListEndpointConfig,
|
|
42
|
+
)
|
|
43
|
+
from wbmailing.viewsets.titles import (
|
|
44
|
+
EmailContactMailingListTitleConfig,
|
|
45
|
+
MailingListEntryTitleConfig,
|
|
46
|
+
MailingListSubscriberChangeRequestTitleConfig,
|
|
47
|
+
MailingListSubscriberRequestEntryTitleConfig,
|
|
48
|
+
MailingListTitleConfig,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MailingListRepresentationViewSet(viewsets.RepresentationViewSet):
|
|
53
|
+
IDENTIFIER = "wbmailing:mailinglist"
|
|
54
|
+
|
|
55
|
+
ordering_fields = ("title",)
|
|
56
|
+
search_fields = ("title",)
|
|
57
|
+
queryset = models.MailingList.objects.all()
|
|
58
|
+
serializer_class = serializers.MailingListRepresentationSerializer
|
|
59
|
+
filterset_class = MailingListFilterSet
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MailingListModelViewSet(viewsets.ModelViewSet):
|
|
63
|
+
IDENTIFIER = "wbmailing:mailinglist"
|
|
64
|
+
|
|
65
|
+
queryset = models.MailingList.objects.all()
|
|
66
|
+
|
|
67
|
+
def get_serializer_class(self):
|
|
68
|
+
if getattr(self, "action", None) == "list":
|
|
69
|
+
return serializers.MailingListListSerializer
|
|
70
|
+
return serializers.MailingListModelSerializer
|
|
71
|
+
|
|
72
|
+
filterset_class = MailingListFilterSet
|
|
73
|
+
|
|
74
|
+
ordering_fields = ordering = ("title",)
|
|
75
|
+
search_fields = ("title",)
|
|
76
|
+
|
|
77
|
+
display_config_class = MailingListDisplayConfig
|
|
78
|
+
title_config_class = MailingListTitleConfig
|
|
79
|
+
button_config_class = MailingListButtonConfig
|
|
80
|
+
|
|
81
|
+
def get_queryset(self):
|
|
82
|
+
qs = self.filter_queryset(
|
|
83
|
+
models.MailingList.objects.annotate(
|
|
84
|
+
nb_subscribers=Coalesce(
|
|
85
|
+
Subquery(
|
|
86
|
+
MailingListEmailContactThroughModel.objects.filter(
|
|
87
|
+
mailing_list=OuterRef("pk"), status=MailingListEmailContactThroughModel.Status.SUBSCRIBED
|
|
88
|
+
)
|
|
89
|
+
.values("mailing_list")
|
|
90
|
+
.annotate(c=Count("mailing_list"))
|
|
91
|
+
.values("c")[:1]
|
|
92
|
+
),
|
|
93
|
+
0,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
qs = qs.prefetch_related("email_contacts")
|
|
98
|
+
return qs
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class MailingListSubscriberChangeRequestModelViewSet(viewsets.ModelViewSet):
|
|
102
|
+
queryset = (
|
|
103
|
+
models.MailingListSubscriberChangeRequest.objects.select_related("email_contact")
|
|
104
|
+
.select_related("mailing_list")
|
|
105
|
+
.select_related("requester")
|
|
106
|
+
.select_related("approver")
|
|
107
|
+
).annotate(entry_repr=F("email_contact__entry__computed_str"))
|
|
108
|
+
|
|
109
|
+
serializer_class = serializers.MailingListSubscriberChangeRequestModelSerializer
|
|
110
|
+
|
|
111
|
+
search_fields = ("email_contact__address", "mailing_list__title")
|
|
112
|
+
ordering_fields = ("email_contact__address", "mailing_list__title", "created")
|
|
113
|
+
ordering = ("-created",)
|
|
114
|
+
filterset_fields = {
|
|
115
|
+
"email_contact": ["exact"],
|
|
116
|
+
"mailing_list": ["exact"],
|
|
117
|
+
"requester": ["exact"],
|
|
118
|
+
"created": ["gte", "exact", "lte"],
|
|
119
|
+
"status": ["exact"],
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
display_config_class = MailingListSubscriberChangeRequestDisplayConfig
|
|
123
|
+
title_config_class = MailingListSubscriberChangeRequestTitleConfig
|
|
124
|
+
button_config_class = MailingListSubcriptionRequestButtonConfig
|
|
125
|
+
|
|
126
|
+
def add_messages(self, request, instance=None, **kwargs):
|
|
127
|
+
if instance:
|
|
128
|
+
_type = (
|
|
129
|
+
"subscribed"
|
|
130
|
+
if instance.type == MailingListSubscriberChangeRequest.Type.SUBSCRIBING
|
|
131
|
+
else "unsubscribed"
|
|
132
|
+
)
|
|
133
|
+
warning(
|
|
134
|
+
request,
|
|
135
|
+
f"Upon approval, This change request will {_type} {instance.email_contact.address} to {instance.mailing_list.title}",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@action(detail=False, methods=["GET"])
|
|
139
|
+
def approveall(self, request, pk=None):
|
|
140
|
+
for request in models.MailingListSubscriberChangeRequest.objects.filter(
|
|
141
|
+
status=models.MailingListSubscriberChangeRequest.Status.PENDING
|
|
142
|
+
).all():
|
|
143
|
+
request.approve()
|
|
144
|
+
request.save()
|
|
145
|
+
return Response({"send": True})
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class MailingListSubscriberRequestMailingListModelViewSet(MailingListSubscriberChangeRequestModelViewSet):
|
|
149
|
+
IDENTIFIER = "wbmailing:mailing_list-mailinglistsubscriberchangerequest"
|
|
150
|
+
|
|
151
|
+
display_config_class = MailingListSubscriberRequestMailingListDisplayConfig
|
|
152
|
+
endpoint_config_class = MailingListSubscriberRequestMailingListEndpointConfig
|
|
153
|
+
|
|
154
|
+
def get_queryset(self):
|
|
155
|
+
return super().get_queryset().filter(mailing_list__id=self.kwargs["mailing_list_id"])
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class MailingListEmailContactThroughModelModelViewSet(viewsets.ModelViewSet):
|
|
159
|
+
queryset = MailingListEmailContactThroughModel.objects.select_related(
|
|
160
|
+
"email_contact",
|
|
161
|
+
"mailing_list",
|
|
162
|
+
)
|
|
163
|
+
search_fields = ["email_contact__address", "email_contact__entry__computed_str"]
|
|
164
|
+
button_config_class = MailingListEmailContactThroughModelButtonConfig
|
|
165
|
+
serializer_class = serializers.MailingListEmailContactThroughModelModelSerializer
|
|
166
|
+
|
|
167
|
+
ordering = ["email_contact__address", "mailing_list__title"]
|
|
168
|
+
ordering_fields = ["email_contact__address", "status", "mailing_list__title"]
|
|
169
|
+
|
|
170
|
+
filterset_class = MailingListEmailContactThroughModelModelFilterSet
|
|
171
|
+
|
|
172
|
+
@action(detail=True, methods=["GET", "PATCH"])
|
|
173
|
+
def removeexpirationdate(self, request, mailing_list_id=None, pk=None, **kwargs):
|
|
174
|
+
through = get_object_or_404(MailingListEmailContactThroughModel, id=pk)
|
|
175
|
+
if (qs := through.requests.filter(expiration_date__isnull=False)).exists():
|
|
176
|
+
last_request = qs.latest("created")
|
|
177
|
+
last_request.expiration_date = None
|
|
178
|
+
last_request.save()
|
|
179
|
+
return Response({"send": True})
|
|
180
|
+
|
|
181
|
+
@action(detail=True, methods=["GET", "PATCH"])
|
|
182
|
+
def delete(self, request, mailing_list_id=None, pk=None, **kwargs):
|
|
183
|
+
through = get_object_or_404(MailingListEmailContactThroughModel, id=pk)
|
|
184
|
+
through.delete()
|
|
185
|
+
return Response({"send": True})
|
|
186
|
+
|
|
187
|
+
@action(detail=True, methods=["GET", "PATCH"])
|
|
188
|
+
def unsubscribe(self, request, pk=None, **kwargs):
|
|
189
|
+
through = get_object_or_404(MailingListEmailContactThroughModel, id=pk)
|
|
190
|
+
if through.status == MailingListEmailContactThroughModel.Status.SUBSCRIBED:
|
|
191
|
+
through.change_state(
|
|
192
|
+
reason=_("Unsubscribed by {}").format(str(request.user)),
|
|
193
|
+
requester=request.user.profile,
|
|
194
|
+
approver=request.user.profile,
|
|
195
|
+
automatically_approve=True,
|
|
196
|
+
)
|
|
197
|
+
return Response({"send": True})
|
|
198
|
+
|
|
199
|
+
def get_queryset(self):
|
|
200
|
+
return (
|
|
201
|
+
super()
|
|
202
|
+
.get_queryset()
|
|
203
|
+
.annotate(
|
|
204
|
+
expiration_date=MailingListEmailContactThroughModel.get_expired_date_subquery(),
|
|
205
|
+
in_charge=StringAgg(F("email_contact__entry__relationship_managers__computed_str"), delimiter=", "),
|
|
206
|
+
is_pending_request_change=Exists(
|
|
207
|
+
MailingListSubscriberChangeRequest.objects.filter(
|
|
208
|
+
relationship=OuterRef("pk"), status=MailingListSubscriberChangeRequest.Status.PENDING
|
|
209
|
+
)
|
|
210
|
+
),
|
|
211
|
+
is_public=F("mailing_list__is_public"),
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class EmailContactMailingListModelViewSet(MailingListEmailContactThroughModelModelViewSet):
|
|
217
|
+
filterset_class = EmailContactMailingListFilterSet
|
|
218
|
+
|
|
219
|
+
display_config_class = EmailContactMailingListDisplayConfig
|
|
220
|
+
title_config_class = EmailContactMailingListTitleConfig
|
|
221
|
+
endpoint_config_class = EmailContactMailingListEndpointConfig
|
|
222
|
+
|
|
223
|
+
def get_queryset(self):
|
|
224
|
+
return super().get_queryset().filter(mailing_list=self.kwargs["mailing_list_id"])
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class MailingListEntryModelViewSet(MailingListEmailContactThroughModelModelViewSet):
|
|
228
|
+
filterset_fields = {"mailing_list": ["exact"], "status": ["exact"]}
|
|
229
|
+
|
|
230
|
+
display_config_class = MailingListEntryDisplayConfig
|
|
231
|
+
title_config_class = MailingListEntryTitleConfig
|
|
232
|
+
endpoint_config_class = MailingListEntryEndpointConfig
|
|
233
|
+
|
|
234
|
+
@cached_property
|
|
235
|
+
def casted_entry(self):
|
|
236
|
+
return get_object_or_404(Entry, id=self.kwargs["entry_id"]).get_casted_entry()
|
|
237
|
+
|
|
238
|
+
@cached_property
|
|
239
|
+
def primary_email(self):
|
|
240
|
+
return EmailContact.objects.filter(entry=self.kwargs["entry_id"], primary=True).first()
|
|
241
|
+
|
|
242
|
+
def add_messages(self, request, instance=None, **kwargs):
|
|
243
|
+
if not self.primary_email:
|
|
244
|
+
warning(
|
|
245
|
+
request,
|
|
246
|
+
"This person does not have a primary email. Adds one first before adding them to a mailing list.",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def get_queryset(self):
|
|
250
|
+
qs = super().get_queryset()
|
|
251
|
+
entry = self.casted_entry
|
|
252
|
+
if isinstance(entry, Company):
|
|
253
|
+
email_contacts = EmailContact.objects.filter(
|
|
254
|
+
Q(entry=entry) | Q(entry__in=entry.employees.all())
|
|
255
|
+
).distinct()
|
|
256
|
+
qs = qs.filter(email_contact__in=email_contacts)
|
|
257
|
+
else:
|
|
258
|
+
qs = qs.filter(email_contact__entry=entry)
|
|
259
|
+
return qs
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class MailingListSubscriberRequestEntryModelViewSet(MailingListSubscriberChangeRequestModelViewSet):
|
|
263
|
+
IDENTIFIER = "wbmailing:entry-mailinglistsubscriberchangerequest"
|
|
264
|
+
display_config_class = MailingListSubscriberRequestEntryDisplayConfig
|
|
265
|
+
title_config_class = MailingListSubscriberRequestEntryTitleConfig
|
|
266
|
+
endpoint_config_class = MailingListSubscriberRequestEntryEndpointConfig
|
|
267
|
+
|
|
268
|
+
@cached_property
|
|
269
|
+
def primary_email(self):
|
|
270
|
+
return EmailContact.objects.filter(entry=self.kwargs["entry_id"], primary=True).first()
|
|
271
|
+
|
|
272
|
+
def get_serializer_class(self):
|
|
273
|
+
if self.primary_email:
|
|
274
|
+
|
|
275
|
+
class Serializer(serializers.MailingListSubscriberChangeRequestModelSerializer):
|
|
276
|
+
email_contact = wb_serializers.PrimaryKeyRelatedField(
|
|
277
|
+
queryset=EmailContact.objects.filter(entry=self.kwargs["entry_id"]),
|
|
278
|
+
label=_("Email"),
|
|
279
|
+
many=False,
|
|
280
|
+
default=self.primary_email,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
else:
|
|
284
|
+
|
|
285
|
+
class Serializer(serializers.MailingListSubscriberChangeRequestModelSerializer):
|
|
286
|
+
email_contact = wb_serializers.PrimaryKeyRelatedField(label=_("No Emails"), read_only=True)
|
|
287
|
+
|
|
288
|
+
return Serializer
|
|
289
|
+
|
|
290
|
+
def get_queryset(self):
|
|
291
|
+
return super().get_queryset().filter(email_contact__entry__id=self.kwargs["entry_id"])
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
#################
|
|
295
|
+
# TODO Old system
|
|
296
|
+
#################
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class ManageMailingListSubscriptions(View):
|
|
300
|
+
def get(self, request, email_contact_id, *args, **kwargs):
|
|
301
|
+
email_contact = get_object_or_404(EmailContact, id=email_contact_id)
|
|
302
|
+
context = {
|
|
303
|
+
"title": _("Manage Mailing List Subscriptions"),
|
|
304
|
+
"email_contact": email_contact,
|
|
305
|
+
"mailing_lists": models.MailingList.get_subscribed_mailing_lists(email_contact),
|
|
306
|
+
}
|
|
307
|
+
return render(request, "mailing/manage_mailing_list_subscriptions.html", context=context)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class UnsubscribeView(View):
|
|
311
|
+
def get(self, request, email_contact_id, mailing_list_id, *args, **kwargs):
|
|
312
|
+
email_contact = get_object_or_404(EmailContact, id=email_contact_id)
|
|
313
|
+
mailing_list = get_object_or_404(models.MailingList, id=mailing_list_id)
|
|
314
|
+
mailing_list.unsubscribe(
|
|
315
|
+
email_contact, reason=_("The user requested to be unsubscribed."), automatically_approve=True
|
|
316
|
+
)
|
|
317
|
+
return redirect(
|
|
318
|
+
"wbmailing:manage_mailing_list_subscriptions",
|
|
319
|
+
email_contact_id=email_contact_id,
|
|
320
|
+
)
|