wagtail-newsletter-django-backend 0.5.10__tar.gz → 0.6.1__tar.gz
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.
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/PKG-INFO +2 -2
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/pyproject.toml +2 -2
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/__init__.py +1 -1
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/admin_viewsets.py +2 -2
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/campaign_backend.py +30 -36
- wagtail_newsletter_django_backend-0.6.1/src/wagtail_newsletter_django_backend/migrations/0004_remove_audience_from_email_address_and_more.py +39 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/models.py +37 -64
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/LICENSE +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/README.md +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/admin.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/apps.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/forms.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/management/__init__.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/management/commands/__init__.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/management/commands/send_scheduled_campaigns.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/migrations/0001_initial.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/migrations/0002_alter_audience_name_alter_audiencesegment_name_and_more.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/migrations/0003_alter_campaign_audience_segment.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/migrations/__init__.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/templates/wagtail_newsletter_django_backend/manage.html +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/templates/wagtail_newsletter_django_backend/manage_tag.html +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/templates/wagtail_newsletter_django_backend/unsubscribe.html +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/templates/wagtail_newsletter_django_backend/unsubscribe_tag.html +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/templatetags/__init__.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/templatetags/wagtail_newsletter_django_backend_tags.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/tests.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/urls.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/views.py +0 -0
- {wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/src/wagtail_newsletter_django_backend/wagtail_hooks.py +0 -0
{wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wagtail-newsletter-django-backend
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: An internal Django backend to wagtail-newsletter.
|
|
5
5
|
Author-email: "Taylor C. Richberger" <taylor@axfive.net>
|
|
6
6
|
Maintainer-email: "Taylor C. Richberger" <taylor@axfive.net>
|
|
@@ -9,7 +9,7 @@ Description-Content-Type: text/markdown
|
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: Django >=5.2, <6.1
|
|
11
11
|
Requires-Dist: wagtail >=7.1, <7.4
|
|
12
|
-
Requires-Dist: wagtail-newsletter >=0.2.
|
|
12
|
+
Requires-Dist: wagtail-newsletter >=0.2.4, <0.3.0
|
|
13
13
|
Requires-Dist: html2text >= 2025.4.15
|
|
14
14
|
Project-URL: documentation, https://wagtail-newsletter-django-backend.readthedocs.io/en/latest/
|
|
15
15
|
Project-URL: repository, https://forge.axfive.net/Taylor/wagtail-newsletter-django-backend
|
{wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/pyproject.toml
RENAMED
|
@@ -8,8 +8,8 @@ license = {text = 'AGPL-3.0-or-later'}
|
|
|
8
8
|
dependencies = [
|
|
9
9
|
'Django >=5.2, <6.1',
|
|
10
10
|
'wagtail >=7.1, <7.4',
|
|
11
|
-
# The wagtail newsletter folks pushed a breaking change with 0.2.4, unfortunately
|
|
12
|
-
"wagtail-newsletter >=0.2.
|
|
11
|
+
# The wagtail newsletter folks pushed a breaking change with 0.2.4, unfortunately, so we have to use 0.2.4 as the minimum
|
|
12
|
+
"wagtail-newsletter >=0.2.4, <0.3.0",
|
|
13
13
|
"html2text >= 2025.4.15",
|
|
14
14
|
]
|
|
15
15
|
|
|
@@ -5,7 +5,7 @@ from .models import Audience, AudienceSegment, Subscriber, Campaign
|
|
|
5
5
|
@final
|
|
6
6
|
class AudienceViewSet(ModelViewSet):
|
|
7
7
|
model = Audience
|
|
8
|
-
form_fields = ['site', 'name', 'description'
|
|
8
|
+
form_fields = ['site', 'name', 'description']
|
|
9
9
|
list_display = ['site', 'name', 'description'] # pyright: ignore[reportAssignmentType]
|
|
10
10
|
icon = 'group'
|
|
11
11
|
add_to_admin_menu = False
|
|
@@ -35,7 +35,7 @@ class SubscriberViewSet(ModelViewSet):
|
|
|
35
35
|
@final
|
|
36
36
|
class CampaignViewSet(ModelViewSet):
|
|
37
37
|
model = Campaign
|
|
38
|
-
form_fields = ['subject', 'audience_segment', 'send_at', 'sent_at', 'html']
|
|
38
|
+
form_fields = ['subject', 'audience_segment', 'send_at', 'sent_at', 'html', 'from_email_address', 'from_email_name']
|
|
39
39
|
list_display = ['subject', 'audience_segment'] # pyright: ignore[reportAssignmentType]
|
|
40
40
|
icon = 'mail'
|
|
41
41
|
add_to_admin_menu = False
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
1
2
|
from datetime import datetime
|
|
2
3
|
from typing import Any, cast, override
|
|
3
4
|
from django.db import transaction
|
|
@@ -8,11 +9,12 @@ from html2text import HTML2Text
|
|
|
8
9
|
from django.core.mail import get_connection
|
|
9
10
|
from django.core.mail.message import EmailMessage, EmailMultiAlternatives
|
|
10
11
|
from wagtail_newsletter import campaign_backends as wncb, audiences as wna, models as wnm
|
|
12
|
+
from wagtail.models import Site
|
|
11
13
|
import re
|
|
12
14
|
from . import models
|
|
13
15
|
|
|
14
|
-
_unsubscribe_re = re.compile(r'\[\[unsubscribe\]\]
|
|
15
|
-
_manage_re = re.compile(r'\[\[manage\]\]
|
|
16
|
+
_unsubscribe_re = re.compile(r'\[\[unsubscribe\]\]')
|
|
17
|
+
_manage_re = re.compile(r'\[\[manage\]\]')
|
|
16
18
|
|
|
17
19
|
h2t = HTML2Text()
|
|
18
20
|
class CampaignBackend(wncb.CampaignBackend):
|
|
@@ -27,7 +29,7 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
27
29
|
name=audience.name,
|
|
28
30
|
member_count=audience.subscriber_set.filter(verified=True).count(),
|
|
29
31
|
)
|
|
30
|
-
for audience in models.Audience.objects.all()
|
|
32
|
+
for audience in cast(Iterable[models.Audience], models.Audience.objects.all())
|
|
31
33
|
]
|
|
32
34
|
|
|
33
35
|
@override
|
|
@@ -35,7 +37,7 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
35
37
|
self,
|
|
36
38
|
audience_id: str
|
|
37
39
|
) -> "list[wna.AudienceSegment]":
|
|
38
|
-
audience = models.Audience.objects.get(id=int(audience_id))
|
|
40
|
+
audience = cast(models.Audience, models.Audience.objects.get(id=int(audience_id)))
|
|
39
41
|
return [
|
|
40
42
|
wna.AudienceSegment(
|
|
41
43
|
id=f'{audience_id}/{audience_segment.id}',
|
|
@@ -43,7 +45,7 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
43
45
|
name=audience_segment.name,
|
|
44
46
|
member_count=audience_segment.subscribers.filter(verified=True).count(),
|
|
45
47
|
)
|
|
46
|
-
for audience_segment in audience.audience_segment_set.all()
|
|
48
|
+
for audience_segment in cast(Iterable[models.AudienceSegment], audience.audience_segment_set.all())
|
|
47
49
|
]
|
|
48
50
|
|
|
49
51
|
@override
|
|
@@ -60,33 +62,39 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
60
62
|
with transaction.atomic():
|
|
61
63
|
audience_segment: models.AudienceSegment | None = None
|
|
62
64
|
if recipients is not None and recipients.segment:
|
|
63
|
-
audience_segment = models.AudienceSegment.objects.get(id=int(recipients.segment.split('/')[-1]))
|
|
65
|
+
audience_segment = cast(models.AudienceSegment, models.AudienceSegment.objects.get(id=int(recipients.segment.split('/')[-1])))
|
|
64
66
|
|
|
65
67
|
campaign: models.Campaign | None
|
|
66
68
|
if campaign_id:
|
|
67
69
|
int_campaign_id = int(campaign_id)
|
|
68
|
-
campaign = models.Campaign.objects.filter(id=int_campaign_id).first()
|
|
70
|
+
campaign = cast(models.Campaign | None, models.Campaign.objects.filter(id=int_campaign_id).first())
|
|
69
71
|
if campaign is None:
|
|
70
72
|
# No campaign exists with that ID anymore; recreate it.
|
|
71
|
-
campaign = models.Campaign.objects.create(
|
|
73
|
+
campaign = cast(models.Campaign, models.Campaign.objects.create(
|
|
72
74
|
id=int_campaign_id,
|
|
73
75
|
subject=subject,
|
|
74
76
|
html=html.strip(),
|
|
75
77
|
audience_segment=audience_segment,
|
|
76
|
-
|
|
78
|
+
from_email_address=reply_to,
|
|
79
|
+
from_email_name=from_name,
|
|
80
|
+
))
|
|
77
81
|
else:
|
|
78
82
|
campaign.subject = subject
|
|
79
83
|
campaign.html = html.strip()
|
|
84
|
+
campaign.from_email_address = reply_to
|
|
85
|
+
campaign.from_email_name = from_name
|
|
80
86
|
if audience_segment is not None:
|
|
81
87
|
campaign.audience_segment = audience_segment
|
|
82
88
|
campaign.full_clean()
|
|
83
89
|
campaign.save()
|
|
84
90
|
else:
|
|
85
|
-
campaign = models.Campaign.objects.create(
|
|
91
|
+
campaign = cast(models.Campaign, models.Campaign.objects.create(
|
|
86
92
|
subject=subject,
|
|
87
93
|
html=html.strip(),
|
|
88
94
|
audience_segment=audience_segment,
|
|
89
|
-
|
|
95
|
+
from_email_address=reply_to,
|
|
96
|
+
from_email_name=from_name,
|
|
97
|
+
))
|
|
90
98
|
return str(campaign.id)
|
|
91
99
|
|
|
92
100
|
@override
|
|
@@ -95,17 +103,9 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
95
103
|
|
|
96
104
|
@override
|
|
97
105
|
def send_test_email(self, *, campaign_id: str, email: str) -> None:
|
|
98
|
-
campaign = models.Campaign.objects.get(id=int(campaign_id))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
audience = campaign.audience_segment.audience
|
|
102
|
-
else:
|
|
103
|
-
# Build empty audience. Will have no subscribers, and no smtp data.
|
|
104
|
-
# This will not work without default data in the config.
|
|
105
|
-
audience = models.Audience()
|
|
106
|
-
|
|
107
|
-
from_address = audience.from_address()
|
|
108
|
-
user, password = audience.smtp_auth()
|
|
106
|
+
campaign = cast(models.Campaign, models.Campaign.objects.get(id=int(campaign_id)))
|
|
107
|
+
|
|
108
|
+
from_address = campaign.from_address()
|
|
109
109
|
|
|
110
110
|
_ = send_mail(
|
|
111
111
|
subject=campaign.subject,
|
|
@@ -113,21 +113,19 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
113
113
|
from_email=from_address,
|
|
114
114
|
recipient_list=[email],
|
|
115
115
|
html_message=campaign.html,
|
|
116
|
-
auth_user=user,
|
|
117
|
-
auth_password=password,
|
|
118
116
|
)
|
|
119
117
|
|
|
120
118
|
@override
|
|
121
119
|
def send_campaign(self, campaign_id: str) -> None:
|
|
122
120
|
with transaction.atomic():
|
|
123
|
-
campaign = models.Campaign.objects.get(id=int(campaign_id))
|
|
121
|
+
campaign = cast(models.Campaign, models.Campaign.objects.get(id=int(campaign_id)))
|
|
124
122
|
if campaign.audience_segment is None:
|
|
125
123
|
raise RuntimeError("Campaign can't be sent without an audience segment")
|
|
126
124
|
|
|
127
|
-
audience = campaign.audience_segment.audience
|
|
125
|
+
audience = cast(models.Audience, campaign.audience_segment.audience)
|
|
128
126
|
|
|
129
|
-
site = audience.site
|
|
130
|
-
hostname = site.hostname
|
|
127
|
+
site = cast(Site, audience.site)
|
|
128
|
+
hostname = cast(str, site.hostname)
|
|
131
129
|
match site.port:
|
|
132
130
|
case 80:
|
|
133
131
|
url_base = f'http://{hostname}'
|
|
@@ -136,13 +134,9 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
136
134
|
case port:
|
|
137
135
|
url_base = f'http://{hostname}:{port}'
|
|
138
136
|
|
|
139
|
-
from_address =
|
|
140
|
-
user, password = audience.smtp_auth()
|
|
137
|
+
from_address = campaign.from_address()
|
|
141
138
|
|
|
142
|
-
connection: Any = get_connection(
|
|
143
|
-
username=user,
|
|
144
|
-
password=password,
|
|
145
|
-
)
|
|
139
|
+
connection: Any = get_connection()
|
|
146
140
|
|
|
147
141
|
def subscriber_message(subscriber: models.Subscriber) -> EmailMessage:
|
|
148
142
|
unsubscribe_url = url_base + reverse('wagtail_newsletter_django_backend:newsletter_subscriber_unsubscribe', kwargs={'key': subscriber.key})
|
|
@@ -187,7 +181,7 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
187
181
|
@override
|
|
188
182
|
def schedule_campaign(self, campaign_id: str, schedule_time: datetime) -> None:
|
|
189
183
|
with transaction.atomic():
|
|
190
|
-
campaign = models.Campaign.objects.get(id=int(campaign_id))
|
|
184
|
+
campaign = cast(models.Campaign, models.Campaign.objects.get(id=int(campaign_id)))
|
|
191
185
|
campaign.send_at = schedule_time
|
|
192
186
|
campaign.full_clean()
|
|
193
187
|
campaign.save()
|
|
@@ -195,7 +189,7 @@ class CampaignBackend(wncb.CampaignBackend):
|
|
|
195
189
|
@override
|
|
196
190
|
def unschedule_campaign(self, campaign_id: str) -> None:
|
|
197
191
|
with transaction.atomic():
|
|
198
|
-
campaign = models.Campaign.objects.get(id=int(campaign_id))
|
|
192
|
+
campaign = cast(models.Campaign, models.Campaign.objects.get(id=int(campaign_id)))
|
|
199
193
|
campaign.send_at = None
|
|
200
194
|
campaign.full_clean()
|
|
201
195
|
campaign.save()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Generated by Django 5.2.5 on 2026-02-04 04:16
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('wagtail_newsletter_django_backend', '0003_alter_campaign_audience_segment'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RemoveField(
|
|
14
|
+
model_name='audience',
|
|
15
|
+
name='from_email_address',
|
|
16
|
+
),
|
|
17
|
+
migrations.RemoveField(
|
|
18
|
+
model_name='audience',
|
|
19
|
+
name='from_email_name',
|
|
20
|
+
),
|
|
21
|
+
migrations.RemoveField(
|
|
22
|
+
model_name='audience',
|
|
23
|
+
name='smtp_password',
|
|
24
|
+
),
|
|
25
|
+
migrations.RemoveField(
|
|
26
|
+
model_name='audience',
|
|
27
|
+
name='smtp_user',
|
|
28
|
+
),
|
|
29
|
+
migrations.AddField(
|
|
30
|
+
model_name='campaign',
|
|
31
|
+
name='from_email_address',
|
|
32
|
+
field=models.EmailField(blank=True, default=None, help_text='The From address for the email. If not present, this will default to WAGTAIL_NEWSLETTER_REPLY_TO.', max_length=254, null=True),
|
|
33
|
+
),
|
|
34
|
+
migrations.AddField(
|
|
35
|
+
model_name='campaign',
|
|
36
|
+
name='from_email_name',
|
|
37
|
+
field=models.CharField(blank=True, default=None, help_text='The display name portion of the from email. If not present, this will default to WAGTAIL_NEWSLETTER_FROM_NAME.', max_length=256, null=True),
|
|
38
|
+
),
|
|
39
|
+
]
|
|
@@ -15,16 +15,12 @@ import string
|
|
|
15
15
|
from wagtail_newsletter import campaign_backends as wncb
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
-
from django.db.models.manager import RelatedManager
|
|
18
|
+
from django.db.models.manager import RelatedManager, Manager
|
|
19
19
|
|
|
20
20
|
@lru_cache
|
|
21
|
-
def _default_from_parts() -> tuple[str | None, str
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if email:
|
|
25
|
-
name, email = parseaddr(email)
|
|
26
|
-
email = email or None
|
|
27
|
-
name = name or None
|
|
21
|
+
def _default_from_parts() -> tuple[str | None, str]:
|
|
22
|
+
name = cast(str | None, getattr(settings, 'WAGTAIL_NEWSLETTER_FROM_NAME', None))
|
|
23
|
+
email = cast(str, settings.WAGTAIL_NEWSLETTER_REPLY_TO)
|
|
28
24
|
return name, email
|
|
29
25
|
|
|
30
26
|
class SaveDict(TypedDict):
|
|
@@ -56,33 +52,6 @@ class Audience(models.Model):
|
|
|
56
52
|
site: 'models.ForeignKey[Site]' = models.ForeignKey(Site, blank=False, null=False, on_delete=models.CASCADE)
|
|
57
53
|
name: 'models.CharField[str]' = models.CharField(max_length=64, blank=False, null=False)
|
|
58
54
|
description: 'models.TextField[str]' = models.TextField(blank=True, null=False)
|
|
59
|
-
smtp_user: 'models.CharField[str | None]' = models.CharField(
|
|
60
|
-
max_length=256,
|
|
61
|
-
blank=True,
|
|
62
|
-
null=True,
|
|
63
|
-
default=None,
|
|
64
|
-
help_text='The SMTP login user. If absent, this will not be supplied to send_mail, and EMAIL_HOST_USER will be used instead',
|
|
65
|
-
)
|
|
66
|
-
smtp_password: 'models.CharField[str | None]' = models.CharField(
|
|
67
|
-
max_length=256,
|
|
68
|
-
blank=True,
|
|
69
|
-
null=True,
|
|
70
|
-
default=None,
|
|
71
|
-
help_text='The SMTP login password. If absent, this will not be supplied to send_mail, and EMAIL_HOST_PASSWORD will be used instead',
|
|
72
|
-
)
|
|
73
|
-
from_email_address: 'models.EmailField[str | None]' = models.EmailField(
|
|
74
|
-
blank=True,
|
|
75
|
-
null=True,
|
|
76
|
-
default=None,
|
|
77
|
-
help_text='The From address for the email. If not present, this will not be supplied to send_mail, and DEFAULT_FROM_EMAIL will be used instead',
|
|
78
|
-
)
|
|
79
|
-
from_email_name: 'models.CharField[str | None]' = models.CharField(
|
|
80
|
-
max_length=256,
|
|
81
|
-
blank=True,
|
|
82
|
-
null=True,
|
|
83
|
-
default=None,
|
|
84
|
-
help_text='The display name portion of the from email. This will be wrapped around the from email address.',
|
|
85
|
-
)
|
|
86
55
|
|
|
87
56
|
created_at: 'models.DateTimeField[datetime]' = models.DateTimeField(auto_now_add=True)
|
|
88
57
|
updated_at: 'models.DateTimeField[datetime]' = models.DateTimeField(auto_now=True)
|
|
@@ -99,35 +68,6 @@ class Audience(models.Model):
|
|
|
99
68
|
def __str__(self) -> str:
|
|
100
69
|
return self.name
|
|
101
70
|
|
|
102
|
-
_NULLABLE_FIELDS: Sequence[str] = ('smtp_user', 'smtp_password', 'from_email_address', 'from_email_name')
|
|
103
|
-
|
|
104
|
-
@override
|
|
105
|
-
def save(self, **kwargs: Unpack[SaveDict]) -> None: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
106
|
-
patch_blanks(self, kwargs.get('update_fields'), Audience._NULLABLE_FIELDS)
|
|
107
|
-
return super().save(**kwargs)
|
|
108
|
-
|
|
109
|
-
def from_address(self) -> str:
|
|
110
|
-
default_from_name, default_from_email = _default_from_parts()
|
|
111
|
-
from_email = self.from_email_address or default_from_email
|
|
112
|
-
if from_email is None:
|
|
113
|
-
raise RuntimeError('from email address must be set')
|
|
114
|
-
from_name = self.from_email_name or default_from_name
|
|
115
|
-
if from_name:
|
|
116
|
-
return formataddr((from_name, from_email))
|
|
117
|
-
else:
|
|
118
|
-
return from_email
|
|
119
|
-
|
|
120
|
-
def smtp_auth(self) -> tuple[str, str]:
|
|
121
|
-
smtp_user = self.smtp_user or cast(str | None, settings.EMAIL_HOST_USER)
|
|
122
|
-
smtp_password = self.smtp_password or cast(str | None, settings.EMAIL_HOST_PASSWORD)
|
|
123
|
-
if smtp_user is None:
|
|
124
|
-
raise RuntimeError('smtp_user must be set')
|
|
125
|
-
|
|
126
|
-
if smtp_password is None:
|
|
127
|
-
raise RuntimeError('smtp_password must be set')
|
|
128
|
-
|
|
129
|
-
return smtp_user, smtp_password
|
|
130
|
-
|
|
131
71
|
class AudienceSegment(models.Model):
|
|
132
72
|
pk: int
|
|
133
73
|
id: int # pyright: ignore[reportUninitializedInstanceVariable]
|
|
@@ -245,6 +185,8 @@ class Subscription(models.Model):
|
|
|
245
185
|
raise ValidationError('The audience_segment and subscriber must have the same audience')
|
|
246
186
|
|
|
247
187
|
class Campaign(models.Model):
|
|
188
|
+
objects: 'Manager[Campaign]'
|
|
189
|
+
|
|
248
190
|
pk: int
|
|
249
191
|
id: int # pyright: ignore[reportUninitializedInstanceVariable]
|
|
250
192
|
|
|
@@ -254,9 +196,40 @@ class Campaign(models.Model):
|
|
|
254
196
|
html: 'models.TextField[str]' = models.TextField(blank=False, null=False)
|
|
255
197
|
audience_segment: 'models.ForeignKey[AudienceSegment | None]' = models.ForeignKey(AudienceSegment, blank=False, null=True, on_delete=models.SET_NULL)
|
|
256
198
|
|
|
199
|
+
from_email_address: 'models.EmailField[str | None]' = models.EmailField(
|
|
200
|
+
blank=True,
|
|
201
|
+
null=True,
|
|
202
|
+
default=None,
|
|
203
|
+
help_text='The From address for the email. If not present, this will default to WAGTAIL_NEWSLETTER_REPLY_TO.',
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
from_email_name: 'models.CharField[str | None]' = models.CharField(
|
|
207
|
+
max_length=256,
|
|
208
|
+
blank=True,
|
|
209
|
+
null=True,
|
|
210
|
+
default=None,
|
|
211
|
+
help_text='The display name portion of the from email. If not present, this will default to WAGTAIL_NEWSLETTER_FROM_NAME.',
|
|
212
|
+
)
|
|
213
|
+
|
|
257
214
|
created_at: 'models.DateTimeField[datetime]' = models.DateTimeField(auto_now_add=True)
|
|
258
215
|
updated_at: 'models.DateTimeField[datetime]' = models.DateTimeField(auto_now=True)
|
|
259
216
|
|
|
217
|
+
_NULLABLE_FIELDS: Sequence[str] = ('from_email_address', 'from_email_name')
|
|
218
|
+
|
|
219
|
+
@override
|
|
220
|
+
def save(self, **kwargs: Unpack[SaveDict]) -> None: # pyright: ignore[reportIncompatibleMethodOverride]
|
|
221
|
+
patch_blanks(self, kwargs.get('update_fields'), Campaign._NULLABLE_FIELDS)
|
|
222
|
+
return super().save(**kwargs)
|
|
223
|
+
|
|
224
|
+
def from_address(self) -> str:
|
|
225
|
+
default_from_name, default_from_email = _default_from_parts()
|
|
226
|
+
from_email = cast(str | None, self.from_email_address) or default_from_email
|
|
227
|
+
from_name = cast(str | None, self.from_email_name) or default_from_name
|
|
228
|
+
if from_name:
|
|
229
|
+
return formataddr((from_name, from_email))
|
|
230
|
+
else:
|
|
231
|
+
return from_email
|
|
232
|
+
|
|
260
233
|
@property
|
|
261
234
|
def is_sent(self) -> bool:
|
|
262
235
|
return self.sent_at is not None
|
{wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/LICENSE
RENAMED
|
File without changes
|
{wagtail_newsletter_django_backend-0.5.10 → wagtail_newsletter_django_backend-0.6.1}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|