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,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
|
wbmailing/tests/tests.py
ADDED
|
@@ -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
|
+
)
|