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.

Files changed (57) hide show
  1. wbmailing/__init__.py +1 -0
  2. wbmailing/admin.py +74 -0
  3. wbmailing/apps.py +14 -0
  4. wbmailing/backend.py +131 -0
  5. wbmailing/celery.py +0 -0
  6. wbmailing/dynamic_preferences_registry.py +35 -0
  7. wbmailing/factories.py +211 -0
  8. wbmailing/filters/__init__.py +8 -0
  9. wbmailing/filters/mailing_lists.py +84 -0
  10. wbmailing/filters/mails.py +74 -0
  11. wbmailing/management/__init__.py +22 -0
  12. wbmailing/migrations/0001_initial_squashed_squashed_0008_alter_mail_bcc_email_alter_mail_cc_email_and_more.py +649 -0
  13. wbmailing/migrations/0002_delete_mailingsettings.py +16 -0
  14. wbmailing/migrations/0003_alter_mailinglistsubscriberchangerequest_options.py +25 -0
  15. wbmailing/migrations/__init__.py +0 -0
  16. wbmailing/models/__init__.py +6 -0
  17. wbmailing/models/mailing_lists.py +386 -0
  18. wbmailing/models/mails.py +895 -0
  19. wbmailing/serializers/__init__.py +19 -0
  20. wbmailing/serializers/mailing_lists.py +209 -0
  21. wbmailing/serializers/mails.py +251 -0
  22. wbmailing/tasks.py +37 -0
  23. wbmailing/templatetags/__init__.py +0 -0
  24. wbmailing/templatetags/mailing_tags.py +22 -0
  25. wbmailing/tests/__init__.py +0 -0
  26. wbmailing/tests/conftest.py +30 -0
  27. wbmailing/tests/models/__init__.py +0 -0
  28. wbmailing/tests/models/test_mailing_lists.py +297 -0
  29. wbmailing/tests/models/test_mails.py +205 -0
  30. wbmailing/tests/signals.py +124 -0
  31. wbmailing/tests/test_serializers.py +28 -0
  32. wbmailing/tests/test_tasks.py +49 -0
  33. wbmailing/tests/test_viewsets.py +216 -0
  34. wbmailing/tests/tests.py +142 -0
  35. wbmailing/urls.py +90 -0
  36. wbmailing/viewsets/__init__.py +32 -0
  37. wbmailing/viewsets/analytics.py +110 -0
  38. wbmailing/viewsets/buttons/__init__.py +10 -0
  39. wbmailing/viewsets/buttons/mailing_lists.py +91 -0
  40. wbmailing/viewsets/buttons/mails.py +98 -0
  41. wbmailing/viewsets/display/__init__.py +16 -0
  42. wbmailing/viewsets/display/mailing_lists.py +175 -0
  43. wbmailing/viewsets/display/mails.py +318 -0
  44. wbmailing/viewsets/endpoints/__init__.py +8 -0
  45. wbmailing/viewsets/endpoints/mailing_lists.py +86 -0
  46. wbmailing/viewsets/endpoints/mails.py +51 -0
  47. wbmailing/viewsets/mailing_lists.py +320 -0
  48. wbmailing/viewsets/mails.py +425 -0
  49. wbmailing/viewsets/menu/__init__.py +5 -0
  50. wbmailing/viewsets/menu/mailing_lists.py +37 -0
  51. wbmailing/viewsets/menu/mails.py +25 -0
  52. wbmailing/viewsets/titles/__init__.py +17 -0
  53. wbmailing/viewsets/titles/mailing_lists.py +63 -0
  54. wbmailing/viewsets/titles/mails.py +55 -0
  55. wbmailing-2.2.1.dist-info/METADATA +5 -0
  56. wbmailing-2.2.1.dist-info/RECORD +57 -0
  57. wbmailing-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,49 @@
1
+ from datetime import datetime, timedelta
2
+ from unittest.mock import patch
3
+
4
+ import pytest
5
+ from django.db.models import Q
6
+ from django.utils import timezone
7
+ from faker import Faker
8
+ from wbmailing.factories import MailingListSubscriberChangeRequestFactory
9
+ from wbmailing.models import MassMail
10
+ from wbmailing.models.mailing_lists import MailingListEmailContactThroughModel
11
+ from wbmailing.tasks import (
12
+ check_and_remove_expired_mailinglist_subscription,
13
+ periodic_send_mass_mail_as_tasks,
14
+ )
15
+
16
+ fake = Faker()
17
+
18
+
19
+ @pytest.mark.django_db
20
+ class TestSpecificTasks:
21
+ @pytest.mark.parametrize("expiration_date", [fake.past_date()])
22
+ def test_check_and_remove_expired_mailinglist_subscription(self, expiration_date, mailing_list, email_contact):
23
+ request = MailingListSubscriberChangeRequestFactory(
24
+ expiration_date=expiration_date, email_contact=email_contact, mailing_list=mailing_list
25
+ )
26
+ request.approve()
27
+ request.save()
28
+ assert request.relationship.status == MailingListEmailContactThroughModel.Status.SUBSCRIBED
29
+
30
+ check_and_remove_expired_mailinglist_subscription()
31
+ request.refresh_from_db()
32
+ assert request.relationship.status == MailingListEmailContactThroughModel.Status.UNSUBSCRIBED
33
+ request.refresh_from_db()
34
+ assert request.expiration_date is None
35
+
36
+ @patch("wbmailing.models.mails.send_mail_task.delay")
37
+ def test_periodic_tasks(self, send_mail_task, mass_mail_factory):
38
+ mass_mail_factory(status=MassMail.Status.PENDING, send_at=None)
39
+ send_date = timezone.now() - timedelta(minutes=15)
40
+ mass_mail_factory(status=MassMail.Status.SEND_LATER, send_at=send_date)
41
+ assert (
42
+ MassMail.objects.filter(Q(status=MassMail.Status.SEND_LATER) & Q(send_at__lte=datetime.now())).count() == 1
43
+ )
44
+ periodic_send_mass_mail_as_tasks()
45
+ send_mail_task.assert_called()
46
+ assert send_mail_task.call_count == 1
47
+ assert (
48
+ MassMail.objects.filter(Q(status=MassMail.Status.SEND_LATER) & Q(send_at__lte=datetime.now())).count() == 0
49
+ )
@@ -0,0 +1,216 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+ from django.core import mail
5
+ from rest_framework import status
6
+ from rest_framework.test import APIRequestFactory
7
+ from wbcore.contrib.directory.factories import EmailContactFactory
8
+ from wbcore.contrib.directory.viewsets import (
9
+ CompanyModelViewSet,
10
+ EntryModelViewSet,
11
+ PersonModelViewSet,
12
+ )
13
+ from wbcore.signals.instance_buttons import add_instance_button
14
+ from wbcore.test.utils import get_kwargs, get_model_factory
15
+ from wbmailing.factories import (
16
+ CustomMassMailFactory,
17
+ MailEventFactory,
18
+ MailFactory,
19
+ MailingListEmailContactThroughModelFactory,
20
+ MailingListFactory,
21
+ MailingListSubscriberChangeRequestFactory,
22
+ MassMailFactory,
23
+ ToEmailMailFactory,
24
+ )
25
+ from wbmailing.models import (
26
+ MailEvent,
27
+ MailingListEmailContactThroughModel,
28
+ MailingListSubscriberChangeRequest,
29
+ )
30
+ from wbmailing.viewsets import (
31
+ EmailContactMailingListModelViewSet,
32
+ MailingListEntryModelViewSet,
33
+ MailingListSubscriberChangeRequestModelViewSet,
34
+ MailModelViewSet,
35
+ MailStatusMassMailModelViewSet,
36
+ ManageMailingListSubscriptions,
37
+ MassMailModelViewSet,
38
+ UnsubscribeView,
39
+ )
40
+
41
+
42
+ @pytest.mark.django_db
43
+ class TestMailStatusMassMailViewSet:
44
+ @pytest.fixture()
45
+ def mass_mail(self, user):
46
+ email = EmailContactFactory.create()
47
+ ml = MailingListFactory.create(email_contacts=[email])
48
+ mass_mail = MassMailFactory.create(mailing_lists=[ml], status="SENT")
49
+ mail = MailFactory.create(mass_mail=mass_mail)
50
+ MailEventFactory.create(mail=mail, event_type=MailEvent.EventType.BOUNCED, recipient=email.address)
51
+ return mass_mail
52
+
53
+ @patch("wbmailing.models.mails.send_mail_as_task.delay")
54
+ @pytest.mark.parametrize("mvs", [MailStatusMassMailModelViewSet])
55
+ def test_resendbouncedmails(self, send_mail_as_task, mvs, mass_mail, user):
56
+ request = APIRequestFactory().get("")
57
+ request.user = user
58
+ kwargs = {"mass_mail_id": mass_mail.id}
59
+
60
+ response = mvs(kwargs=kwargs).resendbouncedmails(request=request, mass_mail_id=mass_mail.id)
61
+ assert response.status_code == status.HTTP_200_OK
62
+ assert response.data
63
+ send_mail_as_task.assert_called()
64
+ assert send_mail_as_task.call_count == 1
65
+
66
+ @pytest.mark.parametrize("mvs", [MailStatusMassMailModelViewSet])
67
+ def test_get_custom_buttons(self, mvs, mass_mail, user):
68
+ request = APIRequestFactory().get("")
69
+ request.user = user
70
+ # obj = mail_factory()
71
+ kwargs = {"mass_mail_id": mass_mail.id}
72
+ mvs.kwargs = kwargs
73
+ response = (
74
+ mvs(kwargs=kwargs).button_config_class(view=mvs, request=request, instance=mass_mail).get_custom_buttons()
75
+ )
76
+ assert response
77
+
78
+
79
+ @pytest.mark.django_db
80
+ class TestSpecificViewsets:
81
+ @pytest.mark.parametrize(
82
+ "mvs, factory",
83
+ [
84
+ (EmailContactMailingListModelViewSet, MailingListEmailContactThroughModelFactory),
85
+ ],
86
+ )
87
+ def test_delete(self, mvs, factory, user):
88
+ request = APIRequestFactory().get("")
89
+ request.user = user
90
+ obj = factory()
91
+ kwargs = get_kwargs(obj, mvs, request)
92
+ response = mvs(kwargs=kwargs).delete(request=request, mailing_list_id=kwargs["mailing_list_id"], pk=obj.pk)
93
+ assert response.status_code == status.HTTP_200_OK
94
+ assert response.data
95
+
96
+ @pytest.mark.parametrize(
97
+ "mvs, factory", [(MailingListEntryModelViewSet, MailingListEmailContactThroughModelFactory)]
98
+ )
99
+ def test_delete_unsubscriber_email_contacts(self, mvs, factory, mailing_list_factory, user):
100
+ request = APIRequestFactory().get("")
101
+ request.user = user
102
+ mlscr = MailingListSubscriberChangeRequestFactory()
103
+ obj = factory()
104
+ kwargs = get_kwargs(obj, mvs, request)
105
+ response = mvs(kwargs=kwargs).unsubscribe(
106
+ request=request, entry_id=mlscr.email_contact.entry.id, pk=mlscr.mailing_list.pk
107
+ )
108
+ assert response.status_code == status.HTTP_200_OK
109
+ assert response.data
110
+
111
+ @pytest.mark.parametrize("myview", [PersonModelViewSet, CompanyModelViewSet, EntryModelViewSet])
112
+ def test_entry_adding_instance_buttons(self, myview, user):
113
+ remote_buttons = add_instance_button.send(myview, many=True)
114
+ custom_instance_buttons = set([button for _, button in remote_buttons])
115
+ assert custom_instance_buttons
116
+
117
+ # @pytest.mark.parametrize("mvs", [(MassMailModelViewSet)])
118
+ # def test_get_messages(self, mvs, mass_mail_factory, mail_factory, mail_event_factory, user):
119
+ # request = APIRequestFactory().get("")
120
+ # request.user = user
121
+ # obj = mass_mail_factory(status = MassMail.Status.SENT)
122
+ # qs = mvs().get_queryset()
123
+ # mail = mail_factory(mass_mail=qs[0])
124
+ # me = mail_event_factory(mail = mail, event_type=MailEvent.EventType.CLICKED)
125
+
126
+ # msg = mvs().get_messages(request, instance=qs[0])
127
+ # assert msg
128
+
129
+ @pytest.mark.parametrize("mvs, factory", [(MassMailModelViewSet, CustomMassMailFactory)])
130
+ def test_sendtestmail(self, mvs, factory, user):
131
+ request = APIRequestFactory().get("")
132
+ request.user = user
133
+ obj = factory()
134
+ EmailContactFactory(address=request.user.email)
135
+ # request.POST = request.POST.copy()
136
+ # request.POST['to_test_email'] = "lemissan@atonra.ch"
137
+ nb_mail_send = len(mail.outbox)
138
+ request.query_params = {}
139
+ mvs.request = request
140
+ mvs.kwargs = get_kwargs(obj, mvs, request)
141
+ mvs.kwargs["pk"] = obj.pk
142
+ response = mvs().sendtestmail(request)
143
+ assert response.status_code == status.HTTP_200_OK
144
+ assert response.data
145
+ assert len(mail.outbox) == nb_mail_send + 1
146
+
147
+ @pytest.mark.parametrize("mvs, factory", [(MailModelViewSet, ToEmailMailFactory)])
148
+ def test_get_queryset_mail(self, mvs, factory, user):
149
+ request = APIRequestFactory().get("")
150
+ request.user = user
151
+ request.user.is_superuser = False
152
+ request.user.save()
153
+ obj = factory()
154
+ kwargs = get_kwargs(obj, mvs, request)
155
+ vs = mvs.as_view({"get": "list"})
156
+ response = vs(request, **kwargs)
157
+ assert response.status_code == status.HTTP_403_FORBIDDEN
158
+
159
+ @patch("wbmailing.models.mails.send_mail_as_task.delay")
160
+ def test_resend(self, send_mail_as_task, user):
161
+ request = APIRequestFactory().get("")
162
+ request.user = user
163
+ obj = ToEmailMailFactory()
164
+ request.query_params = {}
165
+ MailModelViewSet.request = request
166
+ MailModelViewSet.kwargs = get_kwargs(obj, MailModelViewSet, request)
167
+ MailModelViewSet.kwargs["pk"] = obj.pk
168
+ response = MailModelViewSet().resend(request)
169
+ assert response.status_code == status.HTTP_200_OK
170
+ assert response.data
171
+ send_mail_as_task.assert_called()
172
+ assert send_mail_as_task.call_count == 1
173
+
174
+ @pytest.mark.parametrize("user__is_superuser", [True])
175
+ def test_get_custom_buttons_approveall(self, user):
176
+ request = APIRequestFactory().get("")
177
+ request.user = user
178
+ factory = get_model_factory(MailingListSubscriberChangeRequestModelViewSet().get_serializer_class().Meta.model)
179
+ for i in range(2):
180
+ obj = factory()
181
+ kwargs = get_kwargs(obj, MailingListSubscriberChangeRequestModelViewSet, request)
182
+ assert (
183
+ MailingListSubscriberChangeRequest.objects.filter(
184
+ status=MailingListSubscriberChangeRequest.Status.PENDING
185
+ ).count()
186
+ == 2
187
+ )
188
+ response = (
189
+ MailingListSubscriberChangeRequestModelViewSet(kwargs=kwargs)
190
+ .button_config_class(view=MailingListSubscriberChangeRequestModelViewSet, request=request, instance=obj)
191
+ .get_custom_buttons()
192
+ )
193
+ assert response
194
+
195
+ @pytest.mark.parametrize(
196
+ "mvs, factory", [(ManageMailingListSubscriptions, MailingListEmailContactThroughModelFactory)]
197
+ )
198
+ def test_ManageMailingListSubscriptions(self, mvs, factory, user):
199
+ request = APIRequestFactory().get("")
200
+ request.user = user
201
+ through_model = factory()
202
+ response = mvs().get(request, through_model.email_contact.id)
203
+ assert response.status_code == status.HTTP_200_OK
204
+ assert response.content
205
+
206
+ @pytest.mark.parametrize("mvs, factory", [(UnsubscribeView, MailingListEmailContactThroughModelFactory)])
207
+ def test_UnsubscribeView(self, mvs, factory, user):
208
+ request = APIRequestFactory().get("")
209
+ request.user = user
210
+ through_model = factory()
211
+ mailing_list = through_model.mailing_list
212
+ response = mvs().get(request, through_model.email_contact.id, mailing_list.id)
213
+ assert response.status_code == status.HTTP_302_FOUND
214
+ assert response.url
215
+ through_model.refresh_from_db()
216
+ assert through_model.status == MailingListEmailContactThroughModel.Status.UNSUBSCRIBED
@@ -0,0 +1,142 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+ from django.contrib.admin import AdminSite
5
+ from django.core import mail
6
+ from django.forms.models import model_to_dict
7
+ from django.template import Context, Template
8
+ from rest_framework import status
9
+ from rest_framework.serializers import ValidationError
10
+ from rest_framework.test import APIRequestFactory
11
+ from wbcore.contrib.directory.factories import EmailContactFactory
12
+ from wbcore.test import GenerateTest, default_config
13
+ from wbcore.test.utils import get_kwargs, get_model_factory
14
+ from wbmailing.admin import MailAdmin, MassMailAdmin
15
+ from wbmailing.factories import ToEmailMailFactory
16
+ from wbmailing.models import Mail, MailingListSubscriberChangeRequest, MassMail
17
+ from wbmailing.models.mails import send_mail_as_task
18
+ from wbmailing.serializers import MailingListSubscriberChangeRequestModelSerializer
19
+ from wbmailing.templatetags.mailing_tags import strip, stripAndsplit
20
+ from wbmailing.viewsets import MailingListSubscriberChangeRequestModelViewSet
21
+
22
+ config = {}
23
+ for key, value in default_config.items():
24
+ config[key] = list(
25
+ filter(
26
+ lambda x: x.__module__.startswith("wbmailing")
27
+ and x.__name__
28
+ not in [
29
+ "ClientsBarChartViewSet",
30
+ "CountryBarChartViewSet",
31
+ "RegionBarChartViewSet",
32
+ "MailStatusBarChartViewSet",
33
+ "MailClickBarChartViewSet",
34
+ "AbstractRawDataChartViewSet",
35
+ ],
36
+ value,
37
+ )
38
+ )
39
+
40
+
41
+ @pytest.mark.django_db
42
+ @GenerateTest(config)
43
+ class TestProject:
44
+ pass
45
+
46
+
47
+ @pytest.mark.django_db
48
+ class TestSpecificAdmin:
49
+ def test_send_test_mail(self, mass_mail_factory, user):
50
+ request = APIRequestFactory().get("")
51
+ request.user = user
52
+ EmailContactFactory(address=request.user.email)
53
+ mass_mail_factory()
54
+ qs = MassMail.objects.all()
55
+ mma = MassMailAdmin(MassMail, AdminSite())
56
+ nb_mail_send = len(mail.outbox)
57
+ mma.send_test_mail(request, qs)
58
+ assert len(mail.outbox) == nb_mail_send + 1
59
+
60
+ @patch("wbmailing.models.mails.send_mail_as_task.delay")
61
+ def test_MailAdmin(self, send_mail_as_task, user):
62
+ request = APIRequestFactory().get("")
63
+ request.user = user
64
+ ToEmailMailFactory()
65
+ qs = Mail.objects.all()
66
+
67
+ ma = MailAdmin(Mail, AdminSite())
68
+ ma.send_mails(request, qs)
69
+ send_mail_as_task.assert_called()
70
+ assert send_mail_as_task.call_count == 1
71
+
72
+ def test_send_mail_as_task(self):
73
+ obj = ToEmailMailFactory.create()
74
+ context = {}
75
+ if obj.to_email.count() == 1:
76
+ context = {"salutation": obj.to_email.first().entry.salutation}
77
+ rendered_subject = Template(obj.subject).render(Context(context))
78
+ msg = {
79
+ "subject": rendered_subject,
80
+ "body": obj.body,
81
+ "from_email": obj.from_email,
82
+ "to": list(obj.to_email.values_list("address", flat=True)),
83
+ "bcc": list(obj.bcc_email.values_list("address", flat=True)),
84
+ "cc": list(obj.cc_email.values_list("address", flat=True)),
85
+ "mail_id": obj.id,
86
+ }
87
+ if obj.mass_mail:
88
+ msg["mass_mail_id"] = obj.mass_mail.id
89
+ assert len(mail.outbox) == 0
90
+ send_mail_as_task(**msg)
91
+ assert len(mail.outbox) == 1
92
+
93
+ def test_stripAndsplit(self):
94
+ string = "diego loic is the best man"
95
+ result = stripAndsplit(string, " ")
96
+ assert len(result) == 6
97
+
98
+ def test_strip(self):
99
+ string = "diego/loic/is/the/best/man"
100
+ string = strip(string)
101
+ result = stripAndsplit(string, " ")
102
+ assert len(result) == 1
103
+
104
+ def test_validate_email_contact(self, mailing_list, mailing_list_subscriber_change_request_factory, user):
105
+ request = APIRequestFactory().post("")
106
+ request.user = user
107
+ request.parser_context = {}
108
+ request.data = {"crm_profile": request.user.profile.id}
109
+ serializer = MailingListSubscriberChangeRequestModelSerializer(
110
+ mailing_list_subscriber_change_request_factory(mailing_list=mailing_list), context={"request": request}
111
+ )
112
+ data = model_to_dict(mailing_list_subscriber_change_request_factory.build(mailing_list=mailing_list))
113
+ del data["email_contact"]
114
+ assert serializer.validate(data) # this is fine because the instance is still in the serializer
115
+
116
+ serializer = MailingListSubscriberChangeRequestModelSerializer(context={"request": request})
117
+ with pytest.raises((ValidationError)):
118
+ serializer.validate(data)
119
+
120
+ @pytest.mark.parametrize("mvs", [MailingListSubscriberChangeRequestModelViewSet])
121
+ def test_approveall(self, mvs, user):
122
+ request = APIRequestFactory().get("")
123
+ request.user = user
124
+ factory = get_model_factory(mvs().get_serializer_class().Meta.model)
125
+ for i in range(2):
126
+ obj = factory()
127
+ kwargs = get_kwargs(obj, mvs, request)
128
+ assert (
129
+ MailingListSubscriberChangeRequest.objects.filter(
130
+ status=MailingListSubscriberChangeRequest.Status.PENDING
131
+ ).count()
132
+ == 2
133
+ )
134
+ response = mvs(kwargs=kwargs).approveall(request=request)
135
+ assert response.status_code == status.HTTP_200_OK
136
+ assert response.data
137
+ assert (
138
+ MailingListSubscriberChangeRequest.objects.filter(
139
+ status=MailingListSubscriberChangeRequest.Status.APPROVED
140
+ ).count()
141
+ == 2
142
+ )
wbmailing/urls.py ADDED
@@ -0,0 +1,90 @@
1
+ from django.urls import include, path
2
+ from wbcore.routers import WBCoreRouter
3
+ from wbmailing import viewsets
4
+
5
+ router = WBCoreRouter()
6
+ router.register(r"mailinglist", viewsets.MailingListModelViewSet)
7
+ router.register(r"mailrepresentation", viewsets.MailRepresentationViewSet, basename="mailrepresentation")
8
+ router.register(
9
+ r"mailinglistrepresentation", viewsets.MailingListRepresentationViewSet, basename="mailinglistrepresentation"
10
+ )
11
+ router.register(
12
+ r"mailtemplaterepresentation", viewsets.MailTemplateRepresentationViewSet, basename="mailtemplaterepresentation"
13
+ )
14
+ router.register(r"massmailrepresentation", viewsets.MassMailRepresentationViewSet, basename="massmailrepresentation")
15
+
16
+
17
+ router.register(r"massmail", viewsets.MassMailModelViewSet)
18
+ router.register(r"mailevent", viewsets.MailEventModelViewSet)
19
+ router.register(r"mail", viewsets.MailModelViewSet)
20
+ router.register(r"mailtemplate", viewsets.MailTemplateModelViewSet)
21
+ router.register(
22
+ r"mailinglistsubscriberchangerequest",
23
+ viewsets.MailingListSubscriberChangeRequestModelViewSet,
24
+ basename="mailinglistsubscriberchangerequest",
25
+ )
26
+ router.register(
27
+ "mailinglistemailcontact",
28
+ viewsets.MailingListEmailContactThroughModelModelViewSet,
29
+ basename="mailinglistemailcontact",
30
+ )
31
+
32
+ entry_router = WBCoreRouter()
33
+ entry_router.register(r"mailinglist", viewsets.MailingListEntryModelViewSet, basename="entry-mailinglist")
34
+
35
+
36
+ entry_router.register(
37
+ r"mailinglistsubscriberchangerequest",
38
+ viewsets.MailingListSubscriberRequestEntryModelViewSet,
39
+ basename="entry-mailinglistsubscriberchangerequest",
40
+ )
41
+
42
+ mailinglist_router = WBCoreRouter()
43
+ mailinglist_router.register(
44
+ r"mailinglistsubscriberchangerequest",
45
+ viewsets.MailingListSubscriberRequestMailingListModelViewSet,
46
+ basename="mailing_list-mailinglistsubscriberchangerequest",
47
+ )
48
+ mailinglist_router.register(
49
+ r"email_contacts",
50
+ viewsets.EmailContactMailingListModelViewSet,
51
+ basename="mailing_list-email_contacts",
52
+ )
53
+ mailinglist_router.register(
54
+ r"maileventchart", viewsets.MailMailingListChartViewSet, basename="mailing_list-maileventchart"
55
+ )
56
+
57
+
58
+ mail_router = WBCoreRouter()
59
+ mail_router.register(r"mailevent", viewsets.MailEventMailModelViewSet, basename="mail-mailevent")
60
+
61
+ massmail_router = WBCoreRouter()
62
+ massmail_router.register(r"mailstatus", viewsets.MailStatusMassMailModelViewSet, basename="massmail-mailstatus")
63
+ massmail_router.register(
64
+ r"mailstatusbarchart", viewsets.MailStatusBarChartViewSet, basename="massmail-mailstatusbarchart"
65
+ )
66
+ massmail_router.register(
67
+ r"mailclickbarchart", viewsets.MailClickBarChartViewSet, basename="massmail-mailclickbarchart"
68
+ )
69
+ massmail_router.register(r"clientsbarchart", viewsets.ClientsBarChartViewSet, basename="massmail-clientsbarchart")
70
+ massmail_router.register(r"countrybarchart", viewsets.CountryBarChartViewSet, basename="massmail-countrybarchart")
71
+ massmail_router.register(r"regionbarchart", viewsets.RegionBarChartViewSet, basename="massmail-regionbarchart")
72
+ massmail_router.register("mailevent", viewsets.MailEventMassMailMailModelViewSet, basename="massmail-mailevent")
73
+
74
+ urlpatterns = [
75
+ path("", include(router.urls)),
76
+ path("entry/<int:entry_id>/", include(entry_router.urls)),
77
+ path("mailing_list/<int:mailing_list_id>/", include(mailinglist_router.urls)),
78
+ path("mail/<int:mail_id>/", include(mail_router.urls)),
79
+ path("massmail/<int:mass_mail_id>/", include(massmail_router.urls)),
80
+ path(
81
+ "manage_mailing_list_subscriptions/<int:email_contact_id>/",
82
+ viewsets.ManageMailingListSubscriptions.as_view(),
83
+ name="manage_mailing_list_subscriptions",
84
+ ),
85
+ path(
86
+ "unsubscribe/<int:email_contact_id>/<int:mailing_list_id>/",
87
+ viewsets.UnsubscribeView.as_view(),
88
+ name="unsubscribe",
89
+ ),
90
+ ]
@@ -0,0 +1,32 @@
1
+ from .analytics import (
2
+ ClientsBarChartViewSet,
3
+ CountryBarChartViewSet,
4
+ MailClickBarChartViewSet,
5
+ MailStatusBarChartViewSet,
6
+ RegionBarChartViewSet,
7
+ )
8
+ from .mailing_lists import (
9
+ EmailContactMailingListModelViewSet,
10
+ MailingListEmailContactThroughModelModelViewSet,
11
+ MailingListEntryModelViewSet,
12
+ MailingListModelViewSet,
13
+ MailingListRepresentationViewSet,
14
+ MailingListSubscriberChangeRequestModelViewSet,
15
+ MailingListSubscriberRequestEntryModelViewSet,
16
+ MailingListSubscriberRequestMailingListModelViewSet,
17
+ ManageMailingListSubscriptions,
18
+ UnsubscribeView,
19
+ )
20
+ from .mails import (
21
+ MailEventModelViewSet,
22
+ MailEventMailModelViewSet,
23
+ MailEventMassMailMailModelViewSet,
24
+ MailMailingListChartViewSet,
25
+ MailModelViewSet,
26
+ MailRepresentationViewSet,
27
+ MailStatusMassMailModelViewSet,
28
+ MailTemplateModelViewSet,
29
+ MailTemplateRepresentationViewSet,
30
+ MassMailModelViewSet,
31
+ MassMailRepresentationViewSet,
32
+ )
@@ -0,0 +1,110 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from django.db.models import QuerySet
7
+ from wbcore import viewsets
8
+ from wbmailing.models import Mail, MailEvent
9
+
10
+ if TYPE_CHECKING:
11
+ from plotly.graph_objs._figure import Figure
12
+
13
+
14
+ class MailStatusBarChartViewSet(viewsets.ChartViewSet):
15
+ IDENTIFIER = "wbmailing:mailstatus-barchart"
16
+ queryset = MailEvent.objects.all()
17
+
18
+ def get_queryset(self) -> QuerySet[Mail]:
19
+ mass_mail_id = self.kwargs["mass_mail_id"]
20
+
21
+ return super().get_queryset().filter(mail__mass_mail_id=mass_mail_id)
22
+
23
+ def get_plotly(self, queryset: QuerySet[Mail]) -> "Figure":
24
+ df = pd.DataFrame(queryset.values("event_type"))
25
+ df["count"] = 1
26
+
27
+ df = df.groupby(["event_type"])["count"].count().reset_index().sort_values(by="count")
28
+ fig = px.bar(df, x="count", y="event_type", orientation="h")
29
+ fig.update_layout(
30
+ yaxis={"showticklabels": True, "title": None},
31
+ xaxis={"title": "Number"},
32
+ title={"text": "Mail Status", "x": 0.5},
33
+ )
34
+ return fig
35
+
36
+
37
+ class MailClickBarChartViewSet(viewsets.ChartViewSet):
38
+ IDENTIFIER = "wbmailing:mailclick-barchart"
39
+ queryset = MailEvent.objects.all()
40
+
41
+ def get_queryset(self) -> QuerySet[MailEvent]:
42
+ mass_mail_id = self.kwargs["mass_mail_id"]
43
+
44
+ return super().get_queryset().filter(mail__mass_mail_id=mass_mail_id, event_type=MailEvent.EventType.CLICKED)
45
+
46
+ def get_plotly(self, queryset: QuerySet[MailEvent]) -> "Figure":
47
+ df = pd.DataFrame(queryset.values("click_url"))
48
+ df["count"] = 1
49
+ df = df.groupby(["click_url"])["count"].count().reset_index().sort_values(by="count")
50
+ fig = go.Figure(layout=dict(template="plotly")) # noqa THis is necessary to prevent some racing condition
51
+ fig = px.bar(df, x="count", y="click_url", text="click_url", orientation="h")
52
+ fig.update_layout(
53
+ yaxis={"visible": False, "showticklabels": False},
54
+ xaxis={"title": "Number of clicks"},
55
+ title={"text": "Clicked URLs", "x": 0.5},
56
+ )
57
+ return fig
58
+
59
+
60
+ class AbstractRawDataChartViewSet(viewsets.ChartViewSet):
61
+ queryset = MailEvent.objects.all()
62
+ ANALYTICS_PROPERTY: str
63
+
64
+ def get_queryset(self) -> QuerySet[MailEvent]:
65
+ mass_mail_id = self.kwargs["mass_mail_id"]
66
+
67
+ return super().get_queryset().filter(mail__mass_mail_id=mass_mail_id, event_type=MailEvent.EventType.OPENED)
68
+
69
+ def get_plotly(self, queryset: QuerySet[MailEvent]) -> "Figure":
70
+ df = pd.DataFrame(queryset.values(self.ANALYTICS_PROPERTY))
71
+ df["count"] = 1
72
+ df = df.groupby(self.ANALYTICS_PROPERTY)["count"].count().reset_index().sort_values(by="count")
73
+ fig = go.Figure()
74
+ if not df.empty:
75
+ fig = px.bar(df, x=self.ANALYTICS_PROPERTY, y="count")
76
+ fig.update_layout(
77
+ xaxis={"showticklabels": True, "title": None},
78
+ yaxis={"showticklabels": True, "title": "Number"},
79
+ )
80
+ return fig
81
+
82
+
83
+ class ClientsBarChartViewSet(AbstractRawDataChartViewSet):
84
+ IDENTIFIER = "wbmailing:client-barchart"
85
+ ANALYTICS_PROPERTY = "raw_data__Client__Name"
86
+
87
+ def get_plotly(self, queryset: QuerySet[MailEvent]) -> "Figure":
88
+ fig = super().get_plotly(queryset)
89
+ fig.update_layout(title={"text": "Platforms", "x": 0.5})
90
+ return fig
91
+
92
+
93
+ class CountryBarChartViewSet(AbstractRawDataChartViewSet):
94
+ IDENTIFIER = "wbmailing:country-barchart"
95
+ ANALYTICS_PROPERTY = "raw_data__Geo__Country"
96
+
97
+ def get_plotly(self, queryset: QuerySet[MailEvent]) -> "Figure":
98
+ fig = super().get_plotly(queryset)
99
+ fig.update_layout(title={"text": "Countries", "x": 0.5})
100
+ return fig
101
+
102
+
103
+ class RegionBarChartViewSet(AbstractRawDataChartViewSet):
104
+ IDENTIFIER = "wbmailing:region-barchart"
105
+ ANALYTICS_PROPERTY = "raw_data__Geo__Region"
106
+
107
+ def get_plotly(self, queryset: QuerySet[MailEvent]) -> "Figure":
108
+ fig = super().get_plotly(queryset)
109
+ fig.update_layout(title={"text": "Regions", "x": 0.5})
110
+ return fig
@@ -0,0 +1,10 @@
1
+ from .mailing_lists import (
2
+ MailingListButtonConfig,
3
+ MailingListEmailContactThroughModelButtonConfig,
4
+ MailingListSubcriptionRequestButtonConfig,
5
+ )
6
+ from .mails import (
7
+ MailButtonConfig,
8
+ MailStatusMassMailButtonConfig,
9
+ MassMailButtonConfig,
10
+ )