django-codenerix-email 4.0.28__py2.py3-none-any.whl → 4.0.30__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.
- codenerix_email/__init__.py +1 -1
- codenerix_email/forms.py +59 -1
- codenerix_email/management/commands/__pycache__/recv_emails.cpython-310.pyc +0 -0
- codenerix_email/management/commands/recv_emails.py +629 -0
- codenerix_email/migrations/0014_emailreceived.py +95 -0
- codenerix_email/migrations/__pycache__/0014_emailreceived.cpython-310.pyc +0 -0
- codenerix_email/models.py +149 -2
- codenerix_email/static/codenerix_email/{partials/emailmessages_rows.html → emailmessages_rows.html} +1 -0
- codenerix_email/static/codenerix_email/emailreceiveds_rows.html +24 -0
- codenerix_email/urls.py +102 -16
- codenerix_email/views.py +160 -3
- {django_codenerix_email-4.0.28.dist-info → django_codenerix_email-4.0.30.dist-info}/METADATA +4 -3
- {django_codenerix_email-4.0.28.dist-info → django_codenerix_email-4.0.30.dist-info}/RECORD +16 -11
- {django_codenerix_email-4.0.28.dist-info → django_codenerix_email-4.0.30.dist-info}/LICENSE +0 -0
- {django_codenerix_email-4.0.28.dist-info → django_codenerix_email-4.0.30.dist-info}/WHEEL +0 -0
- {django_codenerix_email-4.0.28.dist-info → django_codenerix_email-4.0.30.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Generated by Django 5.2.6 on 2025-09-24 09:21
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("codenerix_email", "0013_emailmessage_headers"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name="EmailReceived",
|
|
16
|
+
fields=[
|
|
17
|
+
(
|
|
18
|
+
"id",
|
|
19
|
+
models.AutoField(
|
|
20
|
+
auto_created=True,
|
|
21
|
+
primary_key=True,
|
|
22
|
+
serialize=False,
|
|
23
|
+
verbose_name="ID",
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
(
|
|
27
|
+
"created",
|
|
28
|
+
models.DateTimeField(auto_now_add=True, verbose_name="Created"),
|
|
29
|
+
),
|
|
30
|
+
(
|
|
31
|
+
"updated",
|
|
32
|
+
models.DateTimeField(auto_now=True, verbose_name="Updated"),
|
|
33
|
+
),
|
|
34
|
+
("imap_id", models.IntegerField(default=0, verbose_name="IMAP ID")),
|
|
35
|
+
(
|
|
36
|
+
"eid",
|
|
37
|
+
models.CharField(
|
|
38
|
+
help_text="Unique Message-ID header",
|
|
39
|
+
max_length=512,
|
|
40
|
+
unique=True,
|
|
41
|
+
verbose_name="Message ID",
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
("efrom", models.EmailField(max_length=254, verbose_name="From")),
|
|
45
|
+
("eto", models.EmailField(max_length=254, verbose_name="To")),
|
|
46
|
+
("subject", models.CharField(max_length=256, verbose_name="Subject")),
|
|
47
|
+
(
|
|
48
|
+
"headers",
|
|
49
|
+
models.JSONField(blank=True, null=True, verbose_name="Headers"),
|
|
50
|
+
),
|
|
51
|
+
("body_text", models.TextField(blank=True, verbose_name="Body (Text)")),
|
|
52
|
+
("body_html", models.TextField(blank=True, verbose_name="Body (HTML)")),
|
|
53
|
+
(
|
|
54
|
+
"date_received",
|
|
55
|
+
models.DateTimeField(
|
|
56
|
+
auto_now_add=True, verbose_name="Date received"
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
(
|
|
60
|
+
"bounce_type",
|
|
61
|
+
models.CharField(
|
|
62
|
+
blank=True,
|
|
63
|
+
choices=[("S", "Soft"), ("H", "Hard")],
|
|
64
|
+
default=None,
|
|
65
|
+
max_length=1,
|
|
66
|
+
null=True,
|
|
67
|
+
verbose_name="Bounce Type",
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
"bounce_reason",
|
|
72
|
+
models.CharField(
|
|
73
|
+
blank=True,
|
|
74
|
+
default=None,
|
|
75
|
+
max_length=512,
|
|
76
|
+
null=True,
|
|
77
|
+
verbose_name="Bounce Reason",
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
(
|
|
81
|
+
"email",
|
|
82
|
+
models.ForeignKey(
|
|
83
|
+
null=True,
|
|
84
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
85
|
+
related_name="receiveds",
|
|
86
|
+
to="codenerix_email.emailmessage",
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
],
|
|
90
|
+
options={
|
|
91
|
+
"abstract": False,
|
|
92
|
+
"default_permissions": ("add", "change", "delete", "view", "list"),
|
|
93
|
+
},
|
|
94
|
+
),
|
|
95
|
+
]
|
codenerix_email/models.py
CHANGED
|
@@ -24,6 +24,7 @@ import re
|
|
|
24
24
|
import ssl
|
|
25
25
|
import smtplib
|
|
26
26
|
from uuid import uuid4
|
|
27
|
+
from typing import Optional
|
|
27
28
|
|
|
28
29
|
from django.utils import timezone
|
|
29
30
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -32,8 +33,10 @@ from django.template import Context, Template
|
|
|
32
33
|
from django.core.exceptions import ValidationError
|
|
33
34
|
from django.conf import settings
|
|
34
35
|
from django.db.models import Q
|
|
36
|
+
from django.utils.safestring import SafeString
|
|
35
37
|
|
|
36
38
|
from codenerix.models import CodenerixModel
|
|
39
|
+
from codenerix.helpers import obj_to_html
|
|
37
40
|
from codenerix_lib.debugger import Debugger
|
|
38
41
|
from codenerix.lib.genmail import ( # noqa: N817
|
|
39
42
|
EmailMessage as EM,
|
|
@@ -48,6 +51,13 @@ CONTENT_SUBTYPES = (
|
|
|
48
51
|
(CONTENT_SUBTYPE_HTML, _("HTML Web")),
|
|
49
52
|
)
|
|
50
53
|
|
|
54
|
+
BOUNCE_SOFT = "S"
|
|
55
|
+
BOUNCE_HARD = "H"
|
|
56
|
+
BOUNCE_TYPES = (
|
|
57
|
+
(BOUNCE_SOFT, _("Soft")),
|
|
58
|
+
(BOUNCE_HARD, _("Hard")),
|
|
59
|
+
)
|
|
60
|
+
|
|
51
61
|
|
|
52
62
|
def ensure_header(headers, key, value, headers_keys=None):
|
|
53
63
|
if headers_keys is None:
|
|
@@ -174,6 +184,18 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
174
184
|
def __unicode__(self):
|
|
175
185
|
return "{} ({})".format(self.eto, self.pk)
|
|
176
186
|
|
|
187
|
+
@property
|
|
188
|
+
def bounces_soft(self):
|
|
189
|
+
return self.receiveds.filter(bounce_type=BOUNCE_SOFT).count()
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def bounces_hard(self):
|
|
193
|
+
return self.receiveds.filter(bounce_type=BOUNCE_HARD).count()
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def bounces_total(self):
|
|
197
|
+
return self.receiveds.filter(bounce_type__isnull=False).count()
|
|
198
|
+
|
|
177
199
|
def clean(self):
|
|
178
200
|
if not isinstance(self.headers, dict):
|
|
179
201
|
raise ValidationError(_("HEADERS must be a Dictionary"))
|
|
@@ -183,7 +205,7 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
183
205
|
self.opened = timezone.now()
|
|
184
206
|
self.save()
|
|
185
207
|
|
|
186
|
-
def get_headers(self):
|
|
208
|
+
def get_headers(self, legacy=False):
|
|
187
209
|
|
|
188
210
|
# Get headers
|
|
189
211
|
headers = self.headers or {}
|
|
@@ -218,6 +240,36 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
218
240
|
headers_keys,
|
|
219
241
|
)
|
|
220
242
|
|
|
243
|
+
# Prepare message Tracking info
|
|
244
|
+
if "X-Codenerix-Tracking-ID".lower() not in headers_keys:
|
|
245
|
+
ets = int(timezone.now().timestamp())
|
|
246
|
+
ensure_header(
|
|
247
|
+
headers,
|
|
248
|
+
"X-Codenerix-Tracking-ID",
|
|
249
|
+
self.uuid.hex,
|
|
250
|
+
headers_keys,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Prepare Return-Path
|
|
254
|
+
if "Return-Path".lower() not in headers_keys:
|
|
255
|
+
if not legacy:
|
|
256
|
+
email_bounce = getattr(
|
|
257
|
+
settings,
|
|
258
|
+
"CLIENT_EMAIL_RETURN_PATH",
|
|
259
|
+
settings.CLIENT_EMAIL_FROM,
|
|
260
|
+
)
|
|
261
|
+
else:
|
|
262
|
+
email_bounce = getattr(
|
|
263
|
+
settings, "EMAIL_RETURN_PATH", settings.EMAIL_FROM
|
|
264
|
+
)
|
|
265
|
+
ensure_header(
|
|
266
|
+
headers,
|
|
267
|
+
"Return-Path",
|
|
268
|
+
email_bounce,
|
|
269
|
+
headers_keys,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
print(headers)
|
|
221
273
|
# Return headers
|
|
222
274
|
return headers
|
|
223
275
|
|
|
@@ -405,7 +457,7 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
405
457
|
from_email=self.efrom,
|
|
406
458
|
to=[self.eto],
|
|
407
459
|
connection=connection,
|
|
408
|
-
headers=self.get_headers(),
|
|
460
|
+
headers=self.get_headers(legacy),
|
|
409
461
|
)
|
|
410
462
|
email.content_subtype = self.content_subtype
|
|
411
463
|
for at in self.attachments.all():
|
|
@@ -515,6 +567,101 @@ class EmailAttachment(CodenerixModel):
|
|
|
515
567
|
return fields
|
|
516
568
|
|
|
517
569
|
|
|
570
|
+
class EmailReceived(CodenerixModel):
|
|
571
|
+
imap_id = models.IntegerField(
|
|
572
|
+
_("IMAP ID"), blank=False, null=False, default=0
|
|
573
|
+
)
|
|
574
|
+
eid = models.CharField(
|
|
575
|
+
_("Message ID"),
|
|
576
|
+
max_length=512,
|
|
577
|
+
unique=True,
|
|
578
|
+
help_text=_("Unique Message-ID header"),
|
|
579
|
+
)
|
|
580
|
+
efrom = models.EmailField(_("From"), blank=False, null=False)
|
|
581
|
+
eto = models.EmailField(_("To"), blank=False, null=False)
|
|
582
|
+
subject = models.CharField(
|
|
583
|
+
_("Subject"), max_length=256, blank=False, null=False
|
|
584
|
+
)
|
|
585
|
+
headers = models.JSONField(_("Headers"), blank=True, null=True)
|
|
586
|
+
body_text = models.TextField(_("Body (Text)"), blank=True, null=False)
|
|
587
|
+
body_html = models.TextField(_("Body (HTML)"), blank=True, null=False)
|
|
588
|
+
date_received = models.DateTimeField(_("Date received"), auto_now_add=True)
|
|
589
|
+
|
|
590
|
+
email = models.ForeignKey(
|
|
591
|
+
EmailMessage,
|
|
592
|
+
on_delete=models.CASCADE,
|
|
593
|
+
blank=False,
|
|
594
|
+
null=True,
|
|
595
|
+
related_name="receiveds",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
bounce_type = models.CharField(
|
|
599
|
+
_("Bounce Type"),
|
|
600
|
+
max_length=1,
|
|
601
|
+
choices=BOUNCE_TYPES,
|
|
602
|
+
blank=True,
|
|
603
|
+
null=True,
|
|
604
|
+
default=None,
|
|
605
|
+
)
|
|
606
|
+
bounce_reason = models.CharField(
|
|
607
|
+
_("Bounce Reason"), max_length=512, blank=True, null=True, default=None
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
def __fields__(self, info):
|
|
611
|
+
fields = []
|
|
612
|
+
fields.append(("date_received", _("Date received")))
|
|
613
|
+
fields.append(("bounce_type", None))
|
|
614
|
+
fields.append(("get_bounce_type_display", _("Bounce Type")))
|
|
615
|
+
fields.append(("efrom", _("From")))
|
|
616
|
+
fields.append(("eto", _("To")))
|
|
617
|
+
fields.append(("subject", _("Subject")))
|
|
618
|
+
fields.append(("email__uuid", _("Related email")))
|
|
619
|
+
fields.append(("email__pk", None))
|
|
620
|
+
fields.append(("imap_id", _("IMAP ID")))
|
|
621
|
+
return fields
|
|
622
|
+
|
|
623
|
+
def __searchF__(self, info): # noqa: N802
|
|
624
|
+
def bounce_filter(key):
|
|
625
|
+
if key == "-":
|
|
626
|
+
return Q(bounce_type__isnull=False)
|
|
627
|
+
else:
|
|
628
|
+
return Q(bounce_type=key)
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
"efrom": (_("From"), lambda x: Q(efrom__icontains=x), "input"),
|
|
632
|
+
"eto": (_("To"), lambda x: Q(eto__icontains=x), "input"),
|
|
633
|
+
"email__uuid": (
|
|
634
|
+
_("Related email"),
|
|
635
|
+
lambda x: Q(email__uuid__icontains=x),
|
|
636
|
+
"input",
|
|
637
|
+
),
|
|
638
|
+
"get_bounce_type_display": (
|
|
639
|
+
_("Bounce Type"),
|
|
640
|
+
bounce_filter,
|
|
641
|
+
[
|
|
642
|
+
(BOUNCE_SOFT, _("Soft")),
|
|
643
|
+
(BOUNCE_HARD, _("Hard")),
|
|
644
|
+
(None, _("No Bounce")),
|
|
645
|
+
("-", _("Only Bounces")),
|
|
646
|
+
],
|
|
647
|
+
),
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
def __prettyfy__(self, obj) -> Optional[SafeString]:
|
|
651
|
+
if obj is None:
|
|
652
|
+
return None
|
|
653
|
+
|
|
654
|
+
# Convert to HTML
|
|
655
|
+
(kind, html) = obj_to_html(obj)
|
|
656
|
+
|
|
657
|
+
# Return as HTML
|
|
658
|
+
return SafeString(f" <b[ {kind} ]></b><br/>") + html
|
|
659
|
+
|
|
660
|
+
@property
|
|
661
|
+
def headers_pretty(self) -> Optional[SafeString]:
|
|
662
|
+
return self.__prettyfy__(self.headers)
|
|
663
|
+
|
|
664
|
+
|
|
518
665
|
class EmailTemplate(CodenerixModel):
|
|
519
666
|
cid = models.CharField(
|
|
520
667
|
_("CID"), unique=True, max_length=30, blank=False, null=False
|
codenerix_email/static/codenerix_email/{partials/emailmessages_rows.html → emailmessages_rows.html}
RENAMED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
</td>
|
|
15
15
|
<td>{{row.eto|codenerix}}</td>
|
|
16
16
|
<td><nobr>{{row.subject|codenerix}}</nobr></td>
|
|
17
|
+
<td class="text-nowrap">{{row.bounces_total_count|codenerix}}</td>
|
|
17
18
|
<td><center>{{row.retries|codenerix}}</center></td>
|
|
18
19
|
<td class="text-nowrap">{{row.next_retry|codenerix}}</td>
|
|
19
20
|
<td><center>{{row.pk|codenerix}}</center></td>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<td
|
|
2
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
3
|
+
class="text-nowrap">{{row.date_received|codenerix}}</td>
|
|
4
|
+
<td
|
|
5
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
6
|
+
class="text-nowrap">{{row.get_bounce_type_display|codenerix}}</td>
|
|
7
|
+
<td
|
|
8
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
9
|
+
class="text-nowrap">{{row.efrom|codenerix}}</td>
|
|
10
|
+
<td
|
|
11
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
12
|
+
class="text-nowrap">{{row.eto|codenerix}}</td>
|
|
13
|
+
<td
|
|
14
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
15
|
+
class="text-nowrap">{{row.subject|codenerix}}</td>
|
|
16
|
+
<td
|
|
17
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
18
|
+
ng-click="$event.stopPropagation();"
|
|
19
|
+
class="text-nowrap">
|
|
20
|
+
<a href='/codenerix_email/emailmessages#/{{row.email__pk}}'>{{row.email__uuid|codenerix}}</a>
|
|
21
|
+
</td>
|
|
22
|
+
<td
|
|
23
|
+
ng-class="{'info': (!row.email__pk) && (!row.bounce_type), 'warning': row.bounce_type=='S', 'danger': row.bounce_type=='H', 'text-warning': row.bounce_type=='S', 'text-danger': row.bounce_type=='H'}"
|
|
24
|
+
class="text-nowrap">{{row.imap_id|codenerix}}</td>
|
codenerix_email/urls.py
CHANGED
|
@@ -19,23 +19,109 @@
|
|
|
19
19
|
# limitations under the License.
|
|
20
20
|
|
|
21
21
|
from django.urls import re_path
|
|
22
|
-
from codenerix_email.views import
|
|
23
|
-
|
|
22
|
+
from codenerix_email.views import (
|
|
23
|
+
EmailTemplateList,
|
|
24
|
+
EmailTemplateCreate,
|
|
25
|
+
EmailTemplateCreateModal,
|
|
26
|
+
EmailTemplateUpdate,
|
|
27
|
+
EmailTemplateUpdateModal,
|
|
28
|
+
EmailTemplateDelete,
|
|
29
|
+
)
|
|
30
|
+
from codenerix_email.views import (
|
|
31
|
+
EmailMessageList,
|
|
32
|
+
EmailMessageCreate,
|
|
33
|
+
EmailMessageCreateModal,
|
|
34
|
+
EmailMessageUpdate,
|
|
35
|
+
EmailMessageUpdateModal,
|
|
36
|
+
EmailMessageDelete,
|
|
37
|
+
EmailMessageDetails,
|
|
38
|
+
)
|
|
39
|
+
from codenerix_email.views import (
|
|
40
|
+
EmailReceivedList,
|
|
41
|
+
EmailReceivedDetails,
|
|
42
|
+
EmailReceivedSubList,
|
|
43
|
+
)
|
|
24
44
|
|
|
25
45
|
|
|
26
46
|
urlpatterns = [
|
|
27
|
-
re_path(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
re_path(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
re_path(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
re_path(
|
|
48
|
+
r"^emailtemplates$",
|
|
49
|
+
EmailTemplateList.as_view(),
|
|
50
|
+
name="CDNX_emails_emailtemplates_list",
|
|
51
|
+
),
|
|
52
|
+
re_path(
|
|
53
|
+
r"^emailtemplates/add$",
|
|
54
|
+
EmailTemplateCreate.as_view(),
|
|
55
|
+
name="CDNX_emails_emailtemplates_add",
|
|
56
|
+
),
|
|
57
|
+
re_path(
|
|
58
|
+
r"^emailtemplates/addmodal$",
|
|
59
|
+
EmailTemplateCreateModal.as_view(),
|
|
60
|
+
name="CDNX_emails_emailtemplates_addmodal",
|
|
61
|
+
),
|
|
62
|
+
re_path(
|
|
63
|
+
r"^emailtemplates/(?P<pk>\w+)/edit$",
|
|
64
|
+
EmailTemplateUpdate.as_view(),
|
|
65
|
+
name="CDNX_emails_emailtemplates_edit",
|
|
66
|
+
),
|
|
67
|
+
re_path(
|
|
68
|
+
r"^emailtemplates/(?P<pk>\w+)/editmodal$",
|
|
69
|
+
EmailTemplateUpdateModal.as_view(),
|
|
70
|
+
name="CDNX_emails_emailtemplates_editmodal",
|
|
71
|
+
),
|
|
72
|
+
re_path(
|
|
73
|
+
r"^emailtemplates/(?P<pk>\w+)/delete$",
|
|
74
|
+
EmailTemplateDelete.as_view(),
|
|
75
|
+
name="CDNX_emails_emailtemplates_delete",
|
|
76
|
+
),
|
|
77
|
+
re_path(
|
|
78
|
+
r"^emailmessages$",
|
|
79
|
+
EmailMessageList.as_view(),
|
|
80
|
+
name="CDNX_emails_emailmessages_list",
|
|
81
|
+
),
|
|
82
|
+
re_path(
|
|
83
|
+
r"^emailmessages/add$",
|
|
84
|
+
EmailMessageCreate.as_view(),
|
|
85
|
+
name="CDNX_emails_emailmessages_add",
|
|
86
|
+
),
|
|
87
|
+
re_path(
|
|
88
|
+
r"^emailmessages/addmodal$",
|
|
89
|
+
EmailMessageCreateModal.as_view(),
|
|
90
|
+
name="CDNX_emails_emailmessages_addmodal",
|
|
91
|
+
),
|
|
92
|
+
re_path(
|
|
93
|
+
r"^emailmessages/(?P<pk>\w+)$",
|
|
94
|
+
EmailMessageDetails.as_view(),
|
|
95
|
+
name="CDNX_emails_emailmessages_details",
|
|
96
|
+
),
|
|
97
|
+
re_path(
|
|
98
|
+
r"^emailmessages/(?P<pk>\w+)/edit$",
|
|
99
|
+
EmailMessageUpdate.as_view(),
|
|
100
|
+
name="CDNX_emails_emailmessages_edit",
|
|
101
|
+
),
|
|
102
|
+
re_path(
|
|
103
|
+
r"^emailmessages/(?P<pk>\w+)/editmodal$",
|
|
104
|
+
EmailMessageUpdateModal.as_view(),
|
|
105
|
+
name="CDNX_emails_emailmessages_editmodal",
|
|
106
|
+
),
|
|
107
|
+
re_path(
|
|
108
|
+
r"^emailmessages/(?P<pk>\w+)/delete$",
|
|
109
|
+
EmailMessageDelete.as_view(),
|
|
110
|
+
name="CDNX_emails_emailmessages_delete",
|
|
111
|
+
),
|
|
112
|
+
re_path(
|
|
113
|
+
r"^emailreceiveds$",
|
|
114
|
+
EmailReceivedList.as_view(),
|
|
115
|
+
name="CDNX_emails_emailreceiveds_list",
|
|
116
|
+
),
|
|
117
|
+
re_path(
|
|
118
|
+
r"^emailreceiveds/(?P<pk>\w+)$",
|
|
119
|
+
EmailReceivedDetails.as_view(),
|
|
120
|
+
name="CDNX_emails_emailreceiveds_details",
|
|
121
|
+
),
|
|
122
|
+
re_path(
|
|
123
|
+
r"^emailreceiveds/(?P<pk>\w+)/sublist$",
|
|
124
|
+
EmailReceivedSubList.as_view(),
|
|
125
|
+
name="CDNX_emails_emailreceiveds_sublist",
|
|
126
|
+
),
|
|
41
127
|
]
|
codenerix_email/views.py
CHANGED
|
@@ -28,6 +28,7 @@ from django.shortcuts import get_object_or_404
|
|
|
28
28
|
from django.utils.translation import gettext as _
|
|
29
29
|
from django.conf import settings
|
|
30
30
|
from django.http import HttpRequest, Http404
|
|
31
|
+
from django.db.models import Q, Count
|
|
31
32
|
|
|
32
33
|
from codenerix.multiforms import MultiForm # type: ignore
|
|
33
34
|
from codenerix.views import ( # type: ignore
|
|
@@ -38,9 +39,21 @@ from codenerix.views import ( # type: ignore
|
|
|
38
39
|
GenUpdateModal,
|
|
39
40
|
GenDelete,
|
|
40
41
|
GenDetail,
|
|
42
|
+
SearchFilters,
|
|
43
|
+
)
|
|
44
|
+
from codenerix_email.models import (
|
|
45
|
+
EmailTemplate,
|
|
46
|
+
EmailMessage,
|
|
47
|
+
EmailReceived,
|
|
48
|
+
MODELS,
|
|
49
|
+
BOUNCE_SOFT,
|
|
50
|
+
BOUNCE_HARD,
|
|
51
|
+
)
|
|
52
|
+
from codenerix_email.forms import (
|
|
53
|
+
EmailTemplateForm,
|
|
54
|
+
EmailMessageForm,
|
|
55
|
+
EmailReceivedForm,
|
|
41
56
|
)
|
|
42
|
-
from codenerix_email.models import EmailTemplate, EmailMessage, MODELS
|
|
43
|
-
from codenerix_email.forms import EmailTemplateForm, EmailMessageForm
|
|
44
57
|
|
|
45
58
|
formsfull: Dict[
|
|
46
59
|
str,
|
|
@@ -115,6 +128,23 @@ class EmailTemplateList(GenList):
|
|
|
115
128
|
"bread": [_("Emails"), _("Email Template")],
|
|
116
129
|
}
|
|
117
130
|
|
|
131
|
+
def __fields__(self, info):
|
|
132
|
+
fields = []
|
|
133
|
+
fields.append(("pk", _("PK"), 100))
|
|
134
|
+
fields.append(("cid", _("CID"), 100))
|
|
135
|
+
fields.append(("efrom", _("From"), 100))
|
|
136
|
+
fields.append(
|
|
137
|
+
(
|
|
138
|
+
f"{self.language}__subject",
|
|
139
|
+
_("Subject"),
|
|
140
|
+
100,
|
|
141
|
+
None,
|
|
142
|
+
"shorttext:150",
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
fields.append(("content_subtype", _("Content Subtype"), 100))
|
|
146
|
+
return fields
|
|
147
|
+
|
|
118
148
|
|
|
119
149
|
class EmailTemplateCreate(MultiForm, GenCreate):
|
|
120
150
|
model = EmailTemplate
|
|
@@ -148,7 +178,6 @@ class EmailMessageList(GenList):
|
|
|
148
178
|
search_filter_button = True
|
|
149
179
|
datetime_filter = "updated"
|
|
150
180
|
default_ordering = ["-created"]
|
|
151
|
-
static_partial_row = "codenerix_email/partials/emailmessages_rows.html"
|
|
152
181
|
gentranslate = {
|
|
153
182
|
"sending": _("Sending"),
|
|
154
183
|
"sent": _("Sent"),
|
|
@@ -159,6 +188,90 @@ class EmailMessageList(GenList):
|
|
|
159
188
|
"menu": ["codenerix_email", "emailmessages"],
|
|
160
189
|
"bread": [_("Emails"), _("Email Messages")],
|
|
161
190
|
}
|
|
191
|
+
annotations = {
|
|
192
|
+
# "bounces_soft_count": Count(
|
|
193
|
+
# "receiveds__pk", filter=Q(receiveds__bounce_type=BOUNCE_SOFT)
|
|
194
|
+
# ),
|
|
195
|
+
# "bounces_hard_count": Count(
|
|
196
|
+
# "receiveds__pk", filter=Q(receiveds__bounce_type=BOUNCE_HARD)
|
|
197
|
+
# ),
|
|
198
|
+
"bounces_total_count": Count(
|
|
199
|
+
"receiveds__pk", filter=Q(receiveds__bounce_type__isnull=False)
|
|
200
|
+
),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def __fields__(self, info):
|
|
204
|
+
fields = []
|
|
205
|
+
fields.append(("sending", None))
|
|
206
|
+
fields.append(("error", None))
|
|
207
|
+
fields.append(("sent", _("Send")))
|
|
208
|
+
fields.append(("priority", _("Priority")))
|
|
209
|
+
fields.append(("created", _("Created")))
|
|
210
|
+
fields.append(("updated", _("Updated")))
|
|
211
|
+
fields.append(("opened", _("Opened")))
|
|
212
|
+
# fields.append(("efrom", _("From")))
|
|
213
|
+
fields.append(("eto", _("To")))
|
|
214
|
+
fields.append(("subject", _("Subject")))
|
|
215
|
+
fields.append(("bounces_total_count", _("Bounces")))
|
|
216
|
+
# fields.append(("bounces_soft_count", _("Soft bounces")))
|
|
217
|
+
# fields.append(("bounces_hard_count", _("Hard bounces")))
|
|
218
|
+
fields.append(("retries", _("Retries")))
|
|
219
|
+
fields.append(("next_retry", _("Next retry")))
|
|
220
|
+
fields.append(("pk", _("ID")))
|
|
221
|
+
fields.append(("uuid", _("UUID")))
|
|
222
|
+
fields.append(("unsubscribe_url", _("Unsubscribe")))
|
|
223
|
+
fields.append(("content_subtype", _("Content Subtype")))
|
|
224
|
+
return fields
|
|
225
|
+
|
|
226
|
+
def __searchF__(self, info): # noqa: N802
|
|
227
|
+
def mailstatus(x):
|
|
228
|
+
if x == "D":
|
|
229
|
+
return Q(error=False, sent=True)
|
|
230
|
+
elif x == "P":
|
|
231
|
+
return Q(error=False, sent=False, sending=False)
|
|
232
|
+
elif x == "S":
|
|
233
|
+
return Q(error=False, sent=False, sending=True)
|
|
234
|
+
elif x == "E":
|
|
235
|
+
return Q(error=True)
|
|
236
|
+
else:
|
|
237
|
+
return Q()
|
|
238
|
+
|
|
239
|
+
mailoptions = [
|
|
240
|
+
("D", _("Sent")), # Sent - Done
|
|
241
|
+
("P", _("Pending")), # Pending - Pending
|
|
242
|
+
("S", _("Sending")), # Sending - Sending
|
|
243
|
+
("E", _("Error")), # Error - Error
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
"sent": (_("Sent"), lambda x: mailstatus(x), mailoptions),
|
|
248
|
+
"uuid": (_("UUID"), lambda x: Q(uuid__icontains=x), "input"),
|
|
249
|
+
"priority": (_("Priority"), lambda x: Q(priority=x), "input"),
|
|
250
|
+
"opened": (
|
|
251
|
+
_("Opened"),
|
|
252
|
+
lambda x: ~Q(opened__isnull=x),
|
|
253
|
+
[(True, _("Yes")), (False, _("No"))],
|
|
254
|
+
),
|
|
255
|
+
# "efrom": (_("From"), lambda x: Q(efrom__icontains=x), "input"),
|
|
256
|
+
"eto": (_("To"), lambda x: Q(eto__icontains=x), "input"),
|
|
257
|
+
"retries": (_("Retries"), lambda x: Q(retries=x), "input"),
|
|
258
|
+
"pk": (_("ID"), lambda x: Q(pk=x), "input"),
|
|
259
|
+
"bounces_total_count": (
|
|
260
|
+
_("Bounces"),
|
|
261
|
+
SearchFilters.number("bounces_total_count"),
|
|
262
|
+
"input",
|
|
263
|
+
),
|
|
264
|
+
"bounces_soft_count": (
|
|
265
|
+
_("Soft bounces"),
|
|
266
|
+
SearchFilters.number("bounces_soft_count"),
|
|
267
|
+
"input",
|
|
268
|
+
),
|
|
269
|
+
"bounces_hard_count": (
|
|
270
|
+
_("Hard bounces"),
|
|
271
|
+
SearchFilters.number("bounces_hard_count"),
|
|
272
|
+
"input",
|
|
273
|
+
),
|
|
274
|
+
}
|
|
162
275
|
|
|
163
276
|
|
|
164
277
|
class EmailMessageCreate(GenCreate):
|
|
@@ -174,6 +287,14 @@ class EmailMessageDetails(GenDetail):
|
|
|
174
287
|
model = EmailMessage
|
|
175
288
|
groups = EmailMessageForm.__groups_details__()
|
|
176
289
|
show_details = True
|
|
290
|
+
tabs = [
|
|
291
|
+
{
|
|
292
|
+
"id": "receiveds",
|
|
293
|
+
"name": _("Received"),
|
|
294
|
+
"ws": "CDNX_emails_emailreceiveds_sublist",
|
|
295
|
+
"rows": "base",
|
|
296
|
+
}
|
|
297
|
+
]
|
|
177
298
|
|
|
178
299
|
|
|
179
300
|
class EmailMessageUpdate(GenUpdate):
|
|
@@ -188,3 +309,39 @@ class EmailMessageUpdateModal(GenUpdateModal, EmailMessageUpdate):
|
|
|
188
309
|
|
|
189
310
|
class EmailMessageDelete(GenDelete):
|
|
190
311
|
model = EmailMessage
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ############################################
|
|
315
|
+
# EmailReceived
|
|
316
|
+
class EmailReceivedList(GenList):
|
|
317
|
+
model = EmailReceived
|
|
318
|
+
show_details = True
|
|
319
|
+
search_filter_button = True
|
|
320
|
+
datetime_filter = "updated"
|
|
321
|
+
default_ordering = ["-created"]
|
|
322
|
+
readonly = True
|
|
323
|
+
gentranslate = {
|
|
324
|
+
"sending": _("Sending"),
|
|
325
|
+
"sent": _("Sent"),
|
|
326
|
+
"notsent": _("Not sent!"),
|
|
327
|
+
"waiting": _("Waiting"),
|
|
328
|
+
}
|
|
329
|
+
extra_context = {
|
|
330
|
+
"menu": ["codenerix_email", "emailreceiveds"],
|
|
331
|
+
"bread": [_("Emails"), _("Email Messages")],
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class EmailReceivedDetails(GenDetail):
|
|
336
|
+
model = EmailReceived
|
|
337
|
+
groups = EmailReceivedForm.__groups_details__()
|
|
338
|
+
show_details = True
|
|
339
|
+
readonly = True
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class EmailReceivedSubList(EmailReceivedList):
|
|
343
|
+
def __limitQ__(self, info): # noqa: N802
|
|
344
|
+
limit = {}
|
|
345
|
+
pk = info.kwargs.get("pk", None)
|
|
346
|
+
limit["receiveds"] = Q(email__pk=pk)
|
|
347
|
+
return limit
|