django-codenerix-email 4.0.29__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 +143 -3
- {django_codenerix_email-4.0.29.dist-info → django_codenerix_email-4.0.30.dist-info}/METADATA +4 -3
- {django_codenerix_email-4.0.29.dist-info → django_codenerix_email-4.0.30.dist-info}/RECORD +16 -11
- {django_codenerix_email-4.0.29.dist-info → django_codenerix_email-4.0.30.dist-info}/LICENSE +0 -0
- {django_codenerix_email-4.0.29.dist-info → django_codenerix_email-4.0.30.dist-info}/WHEEL +0 -0
- {django_codenerix_email-4.0.29.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,
|
|
@@ -165,7 +178,6 @@ class EmailMessageList(GenList):
|
|
|
165
178
|
search_filter_button = True
|
|
166
179
|
datetime_filter = "updated"
|
|
167
180
|
default_ordering = ["-created"]
|
|
168
|
-
static_partial_row = "codenerix_email/partials/emailmessages_rows.html"
|
|
169
181
|
gentranslate = {
|
|
170
182
|
"sending": _("Sending"),
|
|
171
183
|
"sent": _("Sent"),
|
|
@@ -176,6 +188,90 @@ class EmailMessageList(GenList):
|
|
|
176
188
|
"menu": ["codenerix_email", "emailmessages"],
|
|
177
189
|
"bread": [_("Emails"), _("Email Messages")],
|
|
178
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
|
+
}
|
|
179
275
|
|
|
180
276
|
|
|
181
277
|
class EmailMessageCreate(GenCreate):
|
|
@@ -191,6 +287,14 @@ class EmailMessageDetails(GenDetail):
|
|
|
191
287
|
model = EmailMessage
|
|
192
288
|
groups = EmailMessageForm.__groups_details__()
|
|
193
289
|
show_details = True
|
|
290
|
+
tabs = [
|
|
291
|
+
{
|
|
292
|
+
"id": "receiveds",
|
|
293
|
+
"name": _("Received"),
|
|
294
|
+
"ws": "CDNX_emails_emailreceiveds_sublist",
|
|
295
|
+
"rows": "base",
|
|
296
|
+
}
|
|
297
|
+
]
|
|
194
298
|
|
|
195
299
|
|
|
196
300
|
class EmailMessageUpdate(GenUpdate):
|
|
@@ -205,3 +309,39 @@ class EmailMessageUpdateModal(GenUpdateModal, EmailMessageUpdate):
|
|
|
205
309
|
|
|
206
310
|
class EmailMessageDelete(GenDelete):
|
|
207
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
|
{django_codenerix_email-4.0.29.dist-info → django_codenerix_email-4.0.30.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-codenerix-email
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.30
|
|
4
4
|
Summary: Codenerix Email is a module that enables CODENERIX to set send emails in a general manner.
|
|
5
5
|
Home-page: https://github.com/codenerix/django-codenerix-email
|
|
6
6
|
Author: Juan Miguel Taboada Godoy <juanmi@juanmitaboada.com>, Juan Soler Ruiz <soleronline@gmail.com>
|
|
@@ -24,8 +24,9 @@ Classifier: Programming Language :: Python :: 3.5
|
|
|
24
24
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
25
25
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
26
26
|
License-File: LICENSE
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist: django-codenerix
|
|
27
|
+
Requires-Dist: IMAPClient (>=3.0.1)
|
|
28
|
+
Requires-Dist: django-codenerix (>=5.0.40)
|
|
29
|
+
Requires-Dist: django-codenerix-extensions (>=4.0.4)
|
|
29
30
|
|
|
30
31
|
======================
|
|
31
32
|
django-codenerix-email
|