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
wbmailing/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
wbmailing/admin.py ADDED
@@ -0,0 +1,74 @@
1
+ from django.contrib import admin
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ from .models import (
5
+ Mail,
6
+ MailEvent,
7
+ MailingList,
8
+ MailingListEmailContactThroughModel,
9
+ MailingListSubscriberChangeRequest,
10
+ MailTemplate,
11
+ MassMail,
12
+ )
13
+
14
+
15
+ class MailingListEmailContactThroughInlineAdmin(admin.TabularInline):
16
+ model = MailingListEmailContactThroughModel
17
+ fk_name = "mailing_list"
18
+ autocomplete_fields = ["email_contact"]
19
+
20
+
21
+ @admin.register(MailingList)
22
+ class MailingListAdmin(admin.ModelAdmin):
23
+ autocomplete_fields = ["email_contacts"]
24
+ search_fields = ["title"]
25
+ inlines = [
26
+ MailingListEmailContactThroughInlineAdmin,
27
+ ]
28
+
29
+
30
+ @admin.register(MassMail)
31
+ class MassMailAdmin(admin.ModelAdmin):
32
+ autocomplete_fields = ["creator"]
33
+
34
+ def send_test_mail(self, request, queryset):
35
+ for mass_mail in queryset:
36
+ mass_mail.send_test_mail(request.user)
37
+
38
+ actions = [send_test_mail]
39
+
40
+
41
+ @admin.register(MailingListSubscriberChangeRequest)
42
+ class MailingListSubscriberChangeRequestAdmin(admin.ModelAdmin):
43
+ list_display = ("email_contact", "mailing_list", "status")
44
+ autocomplete_fields = ["email_contact", "requester"]
45
+
46
+
47
+ admin.site.register(MailEvent)
48
+
49
+
50
+ class MailEventInline(admin.TabularInline):
51
+ model = MailEvent
52
+ fields = ("timestamp", "event_type", "reject_reason", "recipient", "user_agent", "tags", "description")
53
+ readonly_fields = ("timestamp", "event_type", "reject_reason", "recipient", "user_agent", "tags", "description")
54
+ extra = 0
55
+ can_delete = False
56
+
57
+
58
+ @admin.register(Mail)
59
+ class MailAdmin(admin.ModelAdmin):
60
+ def send_mails(self, request, queryset):
61
+ for mail in queryset:
62
+ mail.resend()
63
+
64
+ send_mails.short_description = _("Send Emails")
65
+ actions = [send_mails]
66
+ search_fields = ["mass_mail__subject", "from_email", "to_email__address", "subject", "message_ids"]
67
+ autocomplete_fields = ["to_email", "cc_email", "bcc_email"]
68
+
69
+ inlines = [MailEventInline]
70
+
71
+
72
+ @admin.register(MailTemplate)
73
+ class MailTemplateAdmin(admin.ModelAdmin):
74
+ pass
wbmailing/apps.py ADDED
@@ -0,0 +1,14 @@
1
+ from django.apps import AppConfig
2
+ from django.db.models.signals import post_migrate
3
+
4
+
5
+ class WbmailingConfig(AppConfig):
6
+ name = "wbmailing"
7
+
8
+ def ready(self) -> None:
9
+ from wbmailing.management import initialize_task
10
+
11
+ post_migrate.connect(
12
+ initialize_task,
13
+ dispatch_uid="wbmailing.initialize_task",
14
+ )
wbmailing/backend.py ADDED
@@ -0,0 +1,131 @@
1
+ from datetime import timedelta
2
+
3
+ from anymail.backends.mailgun import EmailBackend as AnymailMailgunBackend
4
+ from anymail.backends.mailjet import EmailBackend as AnymailMailjetBackend
5
+ from anymail.backends.mandrill import EmailBackend as AnymailMandrillBackend
6
+ from anymail.backends.postmark import EmailBackend as AnymailPostmarkBackend
7
+ from anymail.backends.sendgrid import EmailBackend as AnymailSendgridBackend
8
+ from anymail.backends.sendinblue import EmailBackend as AnymailSendinblueBackend
9
+ from anymail.exceptions import AnymailError
10
+ from django.conf import settings
11
+ from django.core.mail.backends.console import EmailBackend as ConsoleBackend
12
+ from django.utils import timezone
13
+ from sentry_sdk import capture_message
14
+ from wbcore.utils.html import convert_html2text
15
+ from wbmailing.models import Mail, MailEvent
16
+
17
+
18
+ class SendMessagesMixin:
19
+ def _process_msg(self, message):
20
+ mail = message.mail if hasattr(message, "mail") else None
21
+ mass_mail = message.mass_mail if hasattr(message, "mass_mail") else None
22
+ if mail:
23
+ event_type = MailEvent.EventType.RESENT
24
+ else:
25
+ if not hasattr(message, "silent_mail") or (hasattr(message, "silent_mail") and not message.silent_mail):
26
+ mail = Mail.create_mail_from_mailmessage(message, user=getattr(message, "user", None))
27
+ event_type = MailEvent.EventType.CREATED
28
+ if mass_mail:
29
+ message.tags = [f"massmail-{mass_mail.id}"]
30
+ else:
31
+ message.tags = [f"mail-{mail.id}"]
32
+ if mail and mail.body:
33
+ # We reset the body text and html field with what might have been computed in create_mail_from_mailmessage
34
+ message.body = convert_html2text(mail.body)
35
+ message.alternatives = []
36
+ message.attach_alternative(mail.body, "text/html")
37
+ return event_type, mail
38
+
39
+ def send_messages(self, email_messages):
40
+ """
41
+ Sends one or more EmailMessage objects and returns the number of email
42
+ messages sent.
43
+ """
44
+ # This API is specified by Django's core BaseEmailBackend
45
+ # (so you can't change it to, e.g., return detailed status).
46
+ # Subclasses shouldn't need to override.
47
+ from wbmailing.models import MailEvent
48
+
49
+ num_sent = 0
50
+ if not email_messages:
51
+ return num_sent
52
+
53
+ created_session = self.open()
54
+
55
+ try:
56
+ for message in email_messages:
57
+ try:
58
+ event_type, mail = self._process_msg(message)
59
+ sent = self._send(message)
60
+ if mail:
61
+ MailEvent.objects.create(
62
+ mail=mail, event_type=event_type, timestamp=timezone.now() - timedelta(seconds=1)
63
+ )
64
+ mail.message_ids.append(message.anymail_status.message_id)
65
+ mail.last_send = timezone.now()
66
+ mail.save()
67
+ except AnymailError as e:
68
+ capture_message(e)
69
+ if self.fail_silently:
70
+ sent = False
71
+ else:
72
+ raise
73
+ if sent:
74
+ num_sent += 1
75
+ finally:
76
+ if created_session:
77
+ self.close()
78
+
79
+ return num_sent
80
+
81
+
82
+ class PostmarkEmailBackend(SendMessagesMixin, AnymailPostmarkBackend):
83
+ def send_messages(self, email_messages):
84
+ if settings.WBMAILING_POSTMARK_BROADCAST_STREAM_ID:
85
+ for msg in email_messages:
86
+ if hasattr(msg, "mass_mail") and msg.mass_mail:
87
+ msg.esp_extra = {"MessageStream": settings.WBMAILING_POSTMARK_BROADCAST_STREAM_ID}
88
+
89
+ return super().send_messages(email_messages)
90
+
91
+
92
+ class SendgridEmailBackend(SendMessagesMixin, AnymailSendgridBackend):
93
+ pass
94
+
95
+
96
+ class MailgunEmailBackend(SendMessagesMixin, AnymailMailgunBackend):
97
+ pass
98
+
99
+
100
+ class MailjetEmailBackend(SendMessagesMixin, AnymailMailjetBackend):
101
+ pass
102
+
103
+
104
+ class MandrillEmailBackend(SendMessagesMixin, AnymailMandrillBackend):
105
+ pass
106
+
107
+
108
+ class SendinblueEmailBackend(SendMessagesMixin, AnymailSendinblueBackend):
109
+ pass
110
+
111
+
112
+ class ConsoleEmailBackend(SendMessagesMixin, ConsoleBackend):
113
+ def send_messages(self, email_messages):
114
+ """Write all messages to the stream in a thread-safe way."""
115
+ if not email_messages:
116
+ return
117
+ msg_count = 0
118
+ with self._lock:
119
+ try:
120
+ stream_created = self.open()
121
+ for message in email_messages:
122
+ self._process_msg(message)
123
+ self.write_message(message)
124
+ self.stream.flush() # flush after each message
125
+ msg_count += 1
126
+ if stream_created:
127
+ self.close()
128
+ except Exception:
129
+ if not self.fail_silently:
130
+ raise
131
+ return msg_count
wbmailing/celery.py ADDED
File without changes
@@ -0,0 +1,35 @@
1
+ from re import fullmatch
2
+
3
+ from django.forms import ValidationError
4
+ from django.utils.translation import gettext as _
5
+ from dynamic_preferences.preferences import Section
6
+ from dynamic_preferences.registries import global_preferences_registry
7
+ from dynamic_preferences.types import BooleanPreference, StringPreference
8
+
9
+ mailing_section = Section("wbmailing")
10
+
11
+
12
+ @global_preferences_registry.register
13
+ class DefaultSourceMailPreference(StringPreference):
14
+ section = mailing_section
15
+ name = "default_source_mail"
16
+ default = "info@stainly-bench.com"
17
+
18
+ verbose_name = _("Default Source Mail Preference")
19
+ help_text = _("The default address used to send emails from")
20
+
21
+ def validate(self, value):
22
+ if not fullmatch(r"[^@]+@[^@]+\.[^@]+", value):
23
+ raise ValidationError(_("Not a valid email format"))
24
+
25
+
26
+ @global_preferences_registry.register
27
+ class AutomaticallyApproveUnsubscriptionRequestFromHardBound(BooleanPreference):
28
+ section = mailing_section
29
+ name = "automatically_approve_unsubscription_request_from_hard_bounce"
30
+ default = False
31
+
32
+ verbose_name = _("Automatically approve unsubscription request from hard bounce")
33
+ help_text = _(
34
+ "Automatically approve unsubscription request from hard bounce received from the ESP tracking system"
35
+ )
wbmailing/factories.py ADDED
@@ -0,0 +1,211 @@
1
+ import factory
2
+ import pytz
3
+ from wbcore.contrib.directory.factories import EmailContactFactory
4
+ from wbmailing.models import (
5
+ Mail,
6
+ MailEvent,
7
+ MailingList,
8
+ MailingListEmailContactThroughModel,
9
+ MailingListSubscriberChangeRequest,
10
+ MailTemplate,
11
+ MassMail,
12
+ )
13
+
14
+
15
+ class MailingListSubscriberChangeRequestFactory(factory.django.DjangoModelFactory):
16
+ class Meta:
17
+ model = MailingListSubscriberChangeRequest
18
+
19
+ expiration_date = factory.Faker("date_object")
20
+ status = MailingListSubscriberChangeRequest.Status.PENDING
21
+ type = MailingListSubscriberChangeRequest.Type.SUBSCRIBING
22
+ email_contact = factory.SubFactory("wbcore.contrib.directory.factories.EmailContactFactory")
23
+ mailing_list = factory.SubFactory("wbmailing.factories.MailingListFactory")
24
+ requester = factory.SubFactory("wbcore.contrib.authentication.factories.AuthenticatedPersonFactory")
25
+ approver = factory.SubFactory("wbcore.contrib.authentication.factories.AuthenticatedPersonFactory")
26
+ reason = factory.Faker("text", max_nb_chars=256)
27
+
28
+
29
+ class ApprovedMailingListSubscriberChangeRequest(MailingListSubscriberChangeRequestFactory):
30
+ status = MailingListSubscriberChangeRequest.Status.APPROVED
31
+
32
+
33
+ class MailingListFactory(factory.django.DjangoModelFactory):
34
+ class Meta:
35
+ model = MailingList
36
+
37
+ title = factory.Faker("text", max_nb_chars=64)
38
+ is_public = False
39
+
40
+ @factory.post_generation
41
+ def email_contacts(self, create, extracted, **kwargs):
42
+ if not create:
43
+ return
44
+ if extracted:
45
+ for email_contact in extracted:
46
+ MailingListEmailContactThroughModel.objects.create(
47
+ mailing_list=self,
48
+ email_contact=email_contact,
49
+ status=MailingListEmailContactThroughModel.Status.SUBSCRIBED,
50
+ )
51
+
52
+
53
+ class EmailContactMailingListFactory(MailingListFactory):
54
+ @factory.post_generation
55
+ def email_contacts(self, create, extracted, **kwargs):
56
+ mlscr = MailingListSubscriberChangeRequestFactory()
57
+ self.email_contacts.add(mlscr.email_contact)
58
+
59
+
60
+ class MailingListEmailContactFactory(EmailContactFactory):
61
+ @factory.post_generation
62
+ def subscriptions(self, create, extracted, **kwargs):
63
+ ml = MailingListFactory()
64
+ MailingListEmailContactThroughModel.objects.create(
65
+ mailing_list=ml, email_contact=self, status=MailingListEmailContactThroughModel.Status.SUBSCRIBED
66
+ )
67
+
68
+
69
+ class UnsubscribedMailingListEmailContactFactory(EmailContactFactory):
70
+ @factory.post_generation
71
+ def subscriptions(self, create, extracted, **kwargs):
72
+ ml = MailingListFactory()
73
+ MailingListEmailContactThroughModel.objects.create(
74
+ mailing_list=ml, email_contact=self, status=MailingListEmailContactThroughModel.Status.UNSUBSCRIBED
75
+ )
76
+
77
+
78
+ class MailingListEmailContactThroughModelFactory(factory.django.DjangoModelFactory):
79
+ email_contact = factory.SubFactory("wbcore.contrib.directory.factories.EmailContactFactory")
80
+ mailing_list = factory.SubFactory("wbmailing.factories.MailingListFactory")
81
+ status = MailingListEmailContactThroughModel.Status.SUBSCRIBED
82
+
83
+ class Meta:
84
+ model = MailingListEmailContactThroughModel
85
+
86
+
87
+ class MassMailFactory(factory.django.DjangoModelFactory):
88
+ class Meta:
89
+ model = MassMail
90
+
91
+ # status = #defaut = DRAFT
92
+ from_email = factory.Faker("email")
93
+ template = factory.SubFactory("wbmailing.factories.MailTemplateFactory")
94
+
95
+ @factory.post_generation
96
+ def mailing_lists(self, create, extracted, **kwargs):
97
+ if not create:
98
+ return
99
+ if extracted:
100
+ for mailing_list in extracted:
101
+ self.mailing_lists.add(mailing_list)
102
+
103
+ subject = factory.Faker("text", max_nb_chars=64)
104
+ body = factory.Faker("paragraph", nb_sentences=5)
105
+
106
+ @factory.post_generation
107
+ def attachments(self, create, extracted, **kwargs):
108
+ if not create:
109
+ return
110
+ if extracted:
111
+ for attachment in extracted:
112
+ self.attach_document(attachment)
113
+
114
+ # body_json = #JSONField(null=True, blank=True)
115
+ created = factory.Faker("date_time", tzinfo=pytz.utc)
116
+ creator = factory.SubFactory("wbcore.contrib.directory.factories.PersonFactory")
117
+ send_at = factory.Faker("date_time_between", start_date="now", end_date="+30y", tzinfo=pytz.utc)
118
+
119
+
120
+ class CustomMassMailFactory(MassMailFactory):
121
+ @factory.post_generation
122
+ def mailing_lists(self, create, extracted, **kwargs):
123
+ ml = MailingListFactory()
124
+ self.mailing_lists.add(ml)
125
+
126
+
127
+ class CustomMassMailEmailContactFactory(EmailContactFactory):
128
+ @factory.post_generation
129
+ def subscriptions(self, create, extracted, **kwargs):
130
+ ml = MailingListFactory.create()
131
+ ml.add_to_mailinglist(self)
132
+ MassMailFactory.create(mailing_lists=[ml])
133
+
134
+
135
+ class MailFactory(factory.django.DjangoModelFactory):
136
+ class Meta:
137
+ model = Mail
138
+
139
+ created = factory.Faker("date_time", tzinfo=pytz.utc)
140
+ last_send = factory.Faker("date_time", tzinfo=pytz.utc)
141
+ template = factory.SubFactory("wbmailing.factories.MailTemplateFactory")
142
+ message_ids = factory.List([factory.Faker("pystr")])
143
+ mass_mail = factory.SubFactory("wbmailing.factories.MassMailFactory")
144
+ from_email = factory.Faker("email")
145
+
146
+ @factory.post_generation
147
+ def to_email(self, create, extracted, **kwargs):
148
+ if not create:
149
+ return
150
+ if extracted:
151
+ for email in extracted:
152
+ self.to_email.add(email)
153
+
154
+ @factory.post_generation
155
+ def cc_email(self, create, extracted, **kwargs):
156
+ if not create:
157
+ return
158
+ if extracted:
159
+ for email in extracted:
160
+ self.cc_email.add(email)
161
+
162
+ @factory.post_generation
163
+ def bcc_email(self, create, extracted, **kwargs):
164
+ if not create:
165
+ return
166
+ if extracted:
167
+ for email in extracted:
168
+ self.bcc_email.add(email)
169
+
170
+ subject = factory.Faker("text", max_nb_chars=64)
171
+ body = factory.Faker("paragraph", nb_sentences=5)
172
+ # body_json = # JSONField(null=True, blank=True)
173
+
174
+ @factory.post_generation
175
+ def attachments(self, create, extracted, **kwargs):
176
+ if not create:
177
+ return
178
+ if extracted:
179
+ for attachment in extracted:
180
+ self.attach_document(attachment)
181
+
182
+
183
+ class ToEmailMailFactory(MailFactory):
184
+ @factory.post_generation
185
+ def to_email(self, create, extracted, **kwargs):
186
+ ec = EmailContactFactory.create()
187
+ self.to_email.add(ec)
188
+
189
+
190
+ class MailEventFactory(factory.django.DjangoModelFactory):
191
+ class Meta:
192
+ model = MailEvent
193
+
194
+ mail = factory.SubFactory("wbmailing.factories.MailFactory")
195
+ timestamp = factory.Faker("date_time", tzinfo=pytz.utc)
196
+ # event_type = # default=EventType.CREATED
197
+ reject_reason = "" # default=null # RejectReason.choices
198
+ description = factory.Faker("paragraph", nb_sentences=2)
199
+ recipient = factory.Faker("email")
200
+ click_url = factory.Faker("image_url")
201
+ ip = factory.Faker("ipv4")
202
+ user_agent = factory.Faker("first_name")
203
+ # raw_data = JSONField(default=dict, null=True, blank=True, verbose_name="Raw Data")
204
+
205
+
206
+ class MailTemplateFactory(factory.django.DjangoModelFactory):
207
+ class Meta:
208
+ model = MailTemplate
209
+
210
+ title = factory.Faker("text", max_nb_chars=64)
211
+ template = factory.Faker("paragraph", nb_sentences=5)
@@ -0,0 +1,8 @@
1
+ from .mailing_lists import (
2
+ EmailContactMailingListFilterSet,
3
+ MailingListEmailContactThroughModelModelFilterSet,
4
+ MailingListFilterSet,
5
+ MailingListSubscriberChangeRequestFilterSet,
6
+ MailStatusMassMailFilterSet,
7
+ )
8
+ from .mails import MailFilter, MassMailFilterSet
@@ -0,0 +1,84 @@
1
+ from django.apps import apps
2
+ from django.db.models import Exists, OuterRef
3
+ from django.utils.translation import gettext_lazy as _
4
+ from wbcore import filters as wb_filters
5
+ from wbcore.contrib.directory.models import EmailContact, Entry
6
+ from wbmailing import models
7
+
8
+
9
+ class MailingListFilterSet(wb_filters.FilterSet):
10
+ not_factsheet_mailinglist = wb_filters.BooleanFilter(
11
+ label=_("No Factsheet Mailing Lists"), method="boolean_not_factsheet_mailinglist", default=True
12
+ )
13
+ negative_entry = wb_filters.ModelChoiceFilter(
14
+ label=_("Unsubscribed mailing lists for user"),
15
+ queryset=Entry.objects.all(),
16
+ endpoint=Entry.get_representation_endpoint(),
17
+ value_key=Entry.get_representation_value_key(),
18
+ label_key=Entry.get_representation_label_key(),
19
+ method="get_notsubscribed_mailing_list_for_entry",
20
+ )
21
+
22
+ def get_notsubscribed_mailing_list_for_entry(self, queryset, name, value):
23
+ if value:
24
+ already_subscribed_subquery = models.MailingListEmailContactThroughModel.objects.filter(
25
+ mailing_list=OuterRef("pk"),
26
+ email_contact__in=value.emails.all(),
27
+ status__in=[models.MailingListEmailContactThroughModel.Status.SUBSCRIBED],
28
+ )
29
+ return queryset.annotate(already_subscribed_subquery=Exists(already_subscribed_subquery)).filter(
30
+ already_subscribed_subquery=False
31
+ )
32
+
33
+ return queryset
34
+
35
+ def boolean_not_factsheet_mailinglist(self, queryset, name, value):
36
+ if apps.is_installed("wbreport"):
37
+ if value is False:
38
+ return queryset.filter(reports__isnull=False).distinct()
39
+ elif value is True:
40
+ return queryset.filter(reports__isnull=True).distinct()
41
+ return queryset
42
+
43
+ class Meta:
44
+ model = models.MailingList
45
+ fields = {"email_contacts": ["exact"], "is_public": ["exact"]}
46
+
47
+
48
+ class MailingListSubscriberChangeRequestFilterSet(wb_filters.FilterSet):
49
+ class Meta:
50
+ model = models.MailingListSubscriberChangeRequest
51
+ fields = {
52
+ "email_contact": ["exact"],
53
+ "mailing_list": ["exact"],
54
+ "requester": ["exact"],
55
+ "created": ["gte", "exact", "lte"],
56
+ }
57
+
58
+
59
+ class MailStatusMassMailFilterSet(wb_filters.FilterSet):
60
+ status = wb_filters.ChoiceFilter(label=_("Status"), choices=models.MailEvent.EventType.choices)
61
+
62
+ class Meta:
63
+ model = EmailContact
64
+ fields = {"entry": ["exact"]}
65
+
66
+
67
+ class MailingListEmailContactThroughModelModelFilterSet(wb_filters.FilterSet):
68
+ expiration_date = wb_filters.DateTimeRangeFilter(
69
+ label=_("Expiration Date"),
70
+ method=wb_filters.DateRangeFilter.base_date_range_filter_method,
71
+ )
72
+ is_pending_request_change = wb_filters.BooleanFilter(
73
+ label=_("Pending Change"), lookup_expr="exact", field_name="is_pending_request_change"
74
+ )
75
+
76
+ class Meta:
77
+ model = models.MailingListEmailContactThroughModel
78
+ fields = {"status": ["exact"]}
79
+
80
+
81
+ class EmailContactMailingListFilterSet(MailingListEmailContactThroughModelModelFilterSet):
82
+ class Meta:
83
+ model = models.MailingListEmailContactThroughModel
84
+ fields = {"email_contact": ["exact"], "status": ["exact"]}
@@ -0,0 +1,74 @@
1
+ from django.apps import apps
2
+ from django.utils.translation import gettext_lazy as _
3
+ from wbcore import filters as wb_filters
4
+ from wbmailing import models
5
+
6
+
7
+ class MassMailFilterSet(wb_filters.FilterSet):
8
+ is_factsheet_massmail = wb_filters.BooleanFilter(
9
+ label=_("Is Factsheet Mass Mail"), method="boolean_is_factsheet_massmail", default=False
10
+ )
11
+
12
+ def boolean_is_factsheet_massmail(self, queryset, name, value):
13
+ if apps.is_installed("wbreport"):
14
+ if value is True:
15
+ return queryset.filter(mailing_lists__reports__isnull=False).distinct()
16
+ elif value is False:
17
+ return queryset.filter(mailing_lists__reports__isnull=True).distinct()
18
+ return queryset
19
+
20
+ class Meta:
21
+ model = models.MassMail
22
+ fields = {
23
+ "subject": ["exact", "icontains"],
24
+ "from_email": ["exact", "icontains"],
25
+ "template": ["exact"],
26
+ "mailing_lists": ["exact"],
27
+ "created": ["gte", "exact", "lte"],
28
+ "creator": ["exact"],
29
+ "status": ["exact"],
30
+ }
31
+
32
+
33
+ class MailFilter(wb_filters.FilterSet):
34
+ never_open = wb_filters.BooleanFilter(label=_("Never Opened"), method="boolean_never_open")
35
+
36
+ event_type = wb_filters.MultipleChoiceFilter(
37
+ label=_("Events"), method="filter_events", choices=models.MailEvent.EventType.choices
38
+ )
39
+ rejected_reason = wb_filters.MultipleChoiceFilter(
40
+ label=_("Rejection Reasons"), method="filter_rejected_reason", choices=models.MailEvent.RejectReason.choices
41
+ )
42
+ is_massmail_mail = wb_filters.BooleanFilter(label=_("Mass Mail"), method="boolean_is_massmail")
43
+ status = wb_filters.ChoiceFilter(label=_("Status"), choices=models.MailEvent.EventType.choices)
44
+
45
+ class Meta:
46
+ model = models.Mail
47
+ fields = {
48
+ "mass_mail": ["exact"],
49
+ "from_email": ["exact"],
50
+ "to_email": ["exact"],
51
+ "created": ["gte", "exact", "lte"],
52
+ }
53
+
54
+ def boolean_is_massmail(self, queryset, name, value):
55
+ if value is True:
56
+ return queryset.filter(mass_mail__isnull=False)
57
+ elif value is False:
58
+ return queryset.filter(mass_mail__isnull=True)
59
+ return queryset
60
+
61
+ def boolean_never_open(self, queryset, name, value):
62
+ if value:
63
+ return queryset.exclude(events__event_type=models.MailEvent.EventType.OPENED)
64
+ return queryset
65
+
66
+ def filter_events(self, queryset, name, value):
67
+ if value:
68
+ return queryset.filter(events__event_type=value)
69
+ return queryset
70
+
71
+ def filter_rejected_reason(self, queryset, name, value):
72
+ if value:
73
+ return queryset.filter(events__reject_reason=value)
74
+ return queryset
@@ -0,0 +1,22 @@
1
+ from django_celery_beat.models import IntervalSchedule, PeriodicTask
2
+ from django.db import DEFAULT_DB_ALIAS
3
+ from django.apps import apps as global_apps
4
+
5
+
6
+ def initialize_task(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, apps=global_apps, **kwargs):
7
+ PeriodicTask.objects.update_or_create(
8
+ task="wbmailing.tasks.periodic_send_mass_mail_as_tasks",
9
+ defaults={
10
+ "name": "Mailing: Periodically send scheduled mass mails",
11
+ "interval": IntervalSchedule.objects.get_or_create(every=120, period=IntervalSchedule.SECONDS)[0],
12
+ "crontab": None,
13
+ },
14
+ )
15
+ PeriodicTask.objects.update_or_create(
16
+ task="wbmailing.tasks.check_and_remove_expired_mailinglist_subscription",
17
+ defaults={
18
+ "name": "Mailing: Remove expired contact from mailing list",
19
+ "interval": IntervalSchedule.objects.get_or_create(every=1, period=IntervalSchedule.DAYS)[0],
20
+ "crontab": None,
21
+ },
22
+ )