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.
@@ -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
@@ -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 EmailTemplateList, EmailTemplateCreate, EmailTemplateCreateModal, EmailTemplateUpdate, EmailTemplateUpdateModal, EmailTemplateDelete
23
- from codenerix_email.views import EmailMessageList, EmailMessageCreate, EmailMessageCreateModal, EmailMessageUpdate, EmailMessageUpdateModal, EmailMessageDelete, EmailMessageDetails
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(r'^emailtemplates$', EmailTemplateList.as_view(), name='CDNX_emails_emailtemplates_list'),
28
- re_path(r'^emailtemplates/add$', EmailTemplateCreate.as_view(), name='CDNX_emails_emailtemplates_add'),
29
- re_path(r'^emailtemplates/addmodal$', EmailTemplateCreateModal.as_view(), name='CDNX_emails_emailtemplates_addmodal'),
30
- re_path(r'^emailtemplates/(?P<pk>\w+)/edit$', EmailTemplateUpdate.as_view(), name='CDNX_emails_emailtemplates_edit'),
31
- re_path(r'^emailtemplates/(?P<pk>\w+)/editmodal$', EmailTemplateUpdateModal.as_view(), name='CDNX_emails_emailtemplates_editmodal'),
32
- re_path(r'^emailtemplates/(?P<pk>\w+)/delete$', EmailTemplateDelete.as_view(), name='CDNX_emails_emailtemplates_delete'),
33
-
34
- re_path(r'^emailmessages$', EmailMessageList.as_view(), name='CDNX_emails_emailmessages_list'),
35
- re_path(r'^emailmessages/add$', EmailMessageCreate.as_view(), name='CDNX_emails_emailmessages_add'),
36
- re_path(r'^emailmessages/addmodal$', EmailMessageCreateModal.as_view(), name='CDNX_emails_emailmessages_addmodal'),
37
- re_path(r'^emailmessages/(?P<pk>\w+)$', EmailMessageDetails.as_view(), name='CDNX_emails_emailmessages_details'),
38
- re_path(r'^emailmessages/(?P<pk>\w+)/edit$', EmailMessageUpdate.as_view(), name='CDNX_emails_emailmessages_edit'),
39
- re_path(r'^emailmessages/(?P<pk>\w+)/editmodal$', EmailMessageUpdateModal.as_view(), name='CDNX_emails_emailmessages_editmodal'),
40
- re_path(r'^emailmessages/(?P<pk>\w+)/delete$', EmailMessageDelete.as_view(), name='CDNX_emails_emailmessages_delete'),
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-codenerix-email
3
- Version: 4.0.29
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: django-codenerix (>=5.0.10)
28
- Requires-Dist: django-codenerix-extensions (>=4.0.3)
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