django-codenerix-email 4.0.10__py2.py3-none-any.whl → 4.0.12__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 +21 -6
- codenerix_email/management/commands/__pycache__/__init__.cpython-311.pyc +0 -0
- codenerix_email/management/commands/__pycache__/send_emails.cpython-311.pyc +0 -0
- codenerix_email/management/commands/send_emails.py +93 -44
- codenerix_email/migrations/0010_emailmessage_content_subtype_and_more.py +35 -0
- codenerix_email/migrations/0011_alter_emailmessage_content_subtype_and_more.py +33 -0
- codenerix_email/migrations/__pycache__/0010_emailmessage_content_subtype_and_more.cpython-311.pyc +0 -0
- codenerix_email/migrations/__pycache__/0011_alter_emailmessage_content_subtype_and_more.cpython-311.pyc +0 -0
- codenerix_email/models.py +223 -46
- codenerix_email/static/codenerix_email/partials/emailmessages_rows.html +20 -9
- codenerix_email/views.py +2 -1
- {django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.dist-info}/METADATA +1 -1
- {django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.dist-info}/RECORD +17 -11
- {django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.dist-info}/LICENSE +0 -0
- {django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.dist-info}/WHEEL +0 -0
- {django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.dist-info}/top_level.txt +0 -0
codenerix_email/__init__.py
CHANGED
codenerix_email/forms.py
CHANGED
|
@@ -36,9 +36,18 @@ class EmailTemplateForm(GenModelForm):
|
|
|
36
36
|
(
|
|
37
37
|
_("Details"),
|
|
38
38
|
12,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
(
|
|
40
|
+
None,
|
|
41
|
+
3,
|
|
42
|
+
["cid", 12],
|
|
43
|
+
["content_subtype", 12],
|
|
44
|
+
),
|
|
45
|
+
(
|
|
46
|
+
None,
|
|
47
|
+
9,
|
|
48
|
+
["efrom", 9],
|
|
49
|
+
),
|
|
50
|
+
),
|
|
42
51
|
]
|
|
43
52
|
|
|
44
53
|
@staticmethod
|
|
@@ -81,20 +90,26 @@ class EmailMessageForm(GenModelForm):
|
|
|
81
90
|
_("Details"),
|
|
82
91
|
6,
|
|
83
92
|
["uuid", 3],
|
|
93
|
+
["created", 3],
|
|
94
|
+
["updated", 3],
|
|
84
95
|
["efrom", 3],
|
|
85
96
|
["eto", 3],
|
|
86
97
|
["subject", 3],
|
|
87
|
-
["
|
|
98
|
+
["opened", 3],
|
|
99
|
+
),
|
|
100
|
+
(
|
|
101
|
+
_("System"),
|
|
102
|
+
6,
|
|
88
103
|
["sending", 3],
|
|
89
104
|
["sent", 3],
|
|
90
105
|
["error", 3],
|
|
91
106
|
["retries", 3],
|
|
92
|
-
["
|
|
107
|
+
["priority", 3],
|
|
93
108
|
["log", 3],
|
|
94
109
|
),
|
|
95
110
|
(
|
|
96
111
|
_("Body"),
|
|
97
|
-
|
|
112
|
+
12,
|
|
98
113
|
["body", 3],
|
|
99
114
|
),
|
|
100
115
|
]
|
|
Binary file
|
|
@@ -70,29 +70,58 @@ class Command(BaseCommand, Debugger):
|
|
|
70
70
|
default=False,
|
|
71
71
|
help="Clear the sending status to all the Queue",
|
|
72
72
|
)
|
|
73
|
+
# Named (optional) arguments
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--verbose",
|
|
76
|
+
action="store_true",
|
|
77
|
+
dest="verbose",
|
|
78
|
+
default=False,
|
|
79
|
+
help="Enable verbose mode",
|
|
80
|
+
)
|
|
81
|
+
# Named (optional) arguments
|
|
82
|
+
parser.add_argument(
|
|
83
|
+
"--now",
|
|
84
|
+
action="store_true",
|
|
85
|
+
dest="now",
|
|
86
|
+
default=False,
|
|
87
|
+
help="Send now, do not wait the retry time",
|
|
88
|
+
)
|
|
89
|
+
# Named (optional) arguments
|
|
90
|
+
parser.add_argument(
|
|
91
|
+
"--all",
|
|
92
|
+
action="store_true",
|
|
93
|
+
dest="all",
|
|
94
|
+
default=False,
|
|
95
|
+
help="Send all, do not do on buckets",
|
|
96
|
+
)
|
|
73
97
|
|
|
74
98
|
def handle(self, *args, **options):
|
|
75
99
|
|
|
100
|
+
# Get user configuration
|
|
101
|
+
daemon = bool(options["daemon"] or options["d"])
|
|
102
|
+
clear = bool(options["clear"] or options["c"])
|
|
103
|
+
bucket_size = getattr(settings, "CLIENT_EMAIL_BUCKETS", 10)
|
|
104
|
+
verbose = bool(options.get("verbose", False))
|
|
105
|
+
sendnow = bool(options.get("now", False))
|
|
106
|
+
doall = bool(options.get("all", False))
|
|
107
|
+
|
|
76
108
|
# Autoconfigure Debugger
|
|
77
109
|
self.set_name("CODENERIX-EMAIL")
|
|
78
110
|
self.set_debug()
|
|
79
111
|
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"Starting a queue of {} emails".format(BUCKET_SIZE),
|
|
94
|
-
color="blue",
|
|
95
|
-
)
|
|
112
|
+
# Daemon
|
|
113
|
+
if verbose:
|
|
114
|
+
if daemon:
|
|
115
|
+
self.debug(
|
|
116
|
+
"Starting command in DAEMON mode with a "
|
|
117
|
+
f"queue of {bucket_size} emails",
|
|
118
|
+
color="cyan",
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
self.debug(
|
|
122
|
+
"Starting a queue of {} emails".format(bucket_size),
|
|
123
|
+
color="blue",
|
|
124
|
+
)
|
|
96
125
|
|
|
97
126
|
# In if requested set sending status for all the list to False
|
|
98
127
|
if clear:
|
|
@@ -110,8 +139,20 @@ class Command(BaseCommand, Debugger):
|
|
|
110
139
|
sent=False,
|
|
111
140
|
sending=False,
|
|
112
141
|
error=False,
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# If we do not have to send now we have to wait for the next retry
|
|
145
|
+
if not sendnow:
|
|
146
|
+
emails = emails.filter(
|
|
147
|
+
next_retry__lte=timezone.now(),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Order emails by priority and next retry
|
|
151
|
+
emails = emails.order_by("priority", "next_retry")
|
|
152
|
+
|
|
153
|
+
# Send in buckets if we are not doing them all
|
|
154
|
+
if not doall:
|
|
155
|
+
emails = emails[0 : bucket_size + 1]
|
|
115
156
|
|
|
116
157
|
# Check if there are emails to process
|
|
117
158
|
if emails:
|
|
@@ -126,20 +167,22 @@ class Command(BaseCommand, Debugger):
|
|
|
126
167
|
|
|
127
168
|
# For each email
|
|
128
169
|
for email in emails:
|
|
129
|
-
|
|
130
|
-
"Sending to {}".format(email.eto),
|
|
131
|
-
color="white",
|
|
132
|
-
tail=False,
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
# Check if we have connection
|
|
136
|
-
if not connection:
|
|
170
|
+
if verbose:
|
|
137
171
|
self.debug(
|
|
138
|
-
"
|
|
139
|
-
color="
|
|
140
|
-
head=False,
|
|
172
|
+
"Sending to {}".format(email.eto),
|
|
173
|
+
color="white",
|
|
141
174
|
tail=False,
|
|
142
175
|
)
|
|
176
|
+
|
|
177
|
+
# Check if we have connection
|
|
178
|
+
if not connection:
|
|
179
|
+
if verbose:
|
|
180
|
+
self.debug(
|
|
181
|
+
" - Connecting",
|
|
182
|
+
color="yellow",
|
|
183
|
+
head=False,
|
|
184
|
+
tail=False,
|
|
185
|
+
)
|
|
143
186
|
connection = email.connect()
|
|
144
187
|
|
|
145
188
|
# Send the email
|
|
@@ -154,20 +197,26 @@ class Command(BaseCommand, Debugger):
|
|
|
154
197
|
email.log = error
|
|
155
198
|
email.save()
|
|
156
199
|
self.error(error)
|
|
157
|
-
if
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
200
|
+
if verbose:
|
|
201
|
+
if email.sent:
|
|
202
|
+
self.debug(" -> SENT", color="green", head=False)
|
|
203
|
+
else:
|
|
204
|
+
self.debug(
|
|
205
|
+
" -> ERROR",
|
|
206
|
+
color="red",
|
|
207
|
+
head=False,
|
|
208
|
+
tail=False,
|
|
209
|
+
)
|
|
210
|
+
self.debug(
|
|
211
|
+
" ({} retries left)".format(
|
|
212
|
+
getattr(
|
|
213
|
+
settings, "CLIENT_EMAIL_RETRIES", 10
|
|
214
|
+
)
|
|
215
|
+
- email.retries
|
|
216
|
+
),
|
|
217
|
+
color="cyan",
|
|
218
|
+
head=False,
|
|
219
|
+
)
|
|
171
220
|
|
|
172
221
|
# Delete all that have been sent
|
|
173
222
|
if not getattr(settings, "CLIENT_EMAIL_HISTORY", True):
|
|
@@ -184,7 +233,7 @@ class Command(BaseCommand, Debugger):
|
|
|
184
233
|
self.debug("Exited by user request!", color="green")
|
|
185
234
|
break
|
|
186
235
|
|
|
187
|
-
|
|
236
|
+
elif verbose:
|
|
188
237
|
# No emails to send
|
|
189
238
|
self.debug(
|
|
190
239
|
"No emails to be sent at this moment in the queue!",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Generated by Django 5.0.6 on 2024-06-04 10:55
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("codenerix_email", "0009_emailmessage_opened_emailmessage_uuid"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="emailmessage",
|
|
15
|
+
name="content_subtype",
|
|
16
|
+
field=models.CharField(
|
|
17
|
+
blank=True,
|
|
18
|
+
choices=[("plain", "Plain"), ("html", "HTML")],
|
|
19
|
+
default="html",
|
|
20
|
+
max_length=256,
|
|
21
|
+
verbose_name="Content Subtype",
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
migrations.AddField(
|
|
25
|
+
model_name="emailtemplate",
|
|
26
|
+
name="content_subtype",
|
|
27
|
+
field=models.CharField(
|
|
28
|
+
blank=True,
|
|
29
|
+
choices=[("plain", "Plain"), ("html", "HTML")],
|
|
30
|
+
default="html",
|
|
31
|
+
max_length=256,
|
|
32
|
+
verbose_name="Content Subtype",
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 5.0.6 on 2024-06-04 11:05
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("codenerix_email", "0010_emailmessage_content_subtype_and_more"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="emailmessage",
|
|
15
|
+
name="content_subtype",
|
|
16
|
+
field=models.CharField(
|
|
17
|
+
choices=[("plain", "Plain"), ("html", "HTML Web")],
|
|
18
|
+
default="html",
|
|
19
|
+
max_length=256,
|
|
20
|
+
verbose_name="Content Subtype",
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
migrations.AlterField(
|
|
24
|
+
model_name="emailtemplate",
|
|
25
|
+
name="content_subtype",
|
|
26
|
+
field=models.CharField(
|
|
27
|
+
choices=[("plain", "Plain"), ("html", "HTML Web")],
|
|
28
|
+
default="html",
|
|
29
|
+
max_length=256,
|
|
30
|
+
verbose_name="Content Subtype",
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
]
|
codenerix_email/migrations/__pycache__/0010_emailmessage_content_subtype_and_more.cpython-311.pyc
ADDED
|
Binary file
|
|
Binary file
|
codenerix_email/models.py
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
18
|
# See the License for the specific language governing permissions and
|
|
19
19
|
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
# type: ignore
|
|
20
22
|
|
|
21
23
|
import re
|
|
22
24
|
import ssl
|
|
@@ -29,14 +31,22 @@ from django.db import models
|
|
|
29
31
|
from django.template import Context, Template
|
|
30
32
|
from django.core.exceptions import ValidationError
|
|
31
33
|
from django.conf import settings
|
|
34
|
+
from django.db.models import Q
|
|
32
35
|
|
|
33
|
-
from codenerix.models import CodenerixModel
|
|
36
|
+
from codenerix.models import CodenerixModel
|
|
34
37
|
from codenerix_lib.debugger import Debugger
|
|
35
|
-
from codenerix.lib.genmail import ( #
|
|
38
|
+
from codenerix.lib.genmail import ( # noqa: N817
|
|
36
39
|
EmailMessage as EM,
|
|
37
40
|
get_connection,
|
|
38
41
|
)
|
|
39
|
-
from codenerix.fields import WysiwygAngularField
|
|
42
|
+
from codenerix.fields import WysiwygAngularField
|
|
43
|
+
|
|
44
|
+
CONTENT_SUBTYPE_PLAIN = "plain"
|
|
45
|
+
CONTENT_SUBTYPE_HTML = "html"
|
|
46
|
+
CONTENT_SUBTYPES = (
|
|
47
|
+
(CONTENT_SUBTYPE_PLAIN, _("Plain")),
|
|
48
|
+
(CONTENT_SUBTYPE_HTML, _("HTML Web")),
|
|
49
|
+
)
|
|
40
50
|
|
|
41
51
|
|
|
42
52
|
class EmailMessage(CodenerixModel, Debugger):
|
|
@@ -74,24 +84,78 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
74
84
|
opened = models.DateTimeField(
|
|
75
85
|
_("Opened"), null=True, blank=True, default=None
|
|
76
86
|
)
|
|
87
|
+
content_subtype = models.CharField(
|
|
88
|
+
_("Content Subtype"),
|
|
89
|
+
max_length=256,
|
|
90
|
+
choices=CONTENT_SUBTYPES,
|
|
91
|
+
blank=False,
|
|
92
|
+
null=False,
|
|
93
|
+
default=CONTENT_SUBTYPE_HTML,
|
|
94
|
+
)
|
|
77
95
|
|
|
78
96
|
def __fields__(self, info):
|
|
79
97
|
fields = []
|
|
80
|
-
fields.append(("sending", None
|
|
81
|
-
fields.append(("error", None
|
|
82
|
-
fields.append(("sent", _("Send")
|
|
83
|
-
fields.append(("priority", _("Priority")
|
|
84
|
-
fields.append(("
|
|
85
|
-
fields.append(("
|
|
86
|
-
fields.append(("
|
|
87
|
-
fields.append(("
|
|
88
|
-
fields.append(("
|
|
89
|
-
fields.append(("
|
|
90
|
-
fields.append(("
|
|
91
|
-
fields.append(("
|
|
92
|
-
fields.append(("
|
|
98
|
+
fields.append(("sending", None))
|
|
99
|
+
fields.append(("error", None))
|
|
100
|
+
fields.append(("sent", _("Send")))
|
|
101
|
+
fields.append(("priority", _("Priority")))
|
|
102
|
+
fields.append(("updated", _("Updated")))
|
|
103
|
+
fields.append(("opened", _("Opened")))
|
|
104
|
+
fields.append(("efrom", _("From")))
|
|
105
|
+
fields.append(("eto", _("To")))
|
|
106
|
+
fields.append(("subject", _("Subject")))
|
|
107
|
+
fields.append(("retries", _("Retries")))
|
|
108
|
+
fields.append(("next_retry", _("Next retry")))
|
|
109
|
+
fields.append(("pk", _("ID")))
|
|
110
|
+
fields.append(("content_subtype", _("Content Subtype")))
|
|
111
|
+
fields.append(("uuid", _("UUID")))
|
|
93
112
|
return fields
|
|
94
113
|
|
|
114
|
+
def __searchQ__(self, info, search): # noqa: N802
|
|
115
|
+
answer = super().__searchQ__(info, search)
|
|
116
|
+
answer["uuid"] = Q(uuid__icontains=search)
|
|
117
|
+
answer["priority"] = Q(priority=search)
|
|
118
|
+
answer["efrom"] = Q(efrom__icontains=search)
|
|
119
|
+
answer["eto"] = Q(eto__icontains=search)
|
|
120
|
+
answer["retries"] = Q(retries=search)
|
|
121
|
+
answer["pk"] = Q(pk=search)
|
|
122
|
+
return answer
|
|
123
|
+
|
|
124
|
+
def __searchF__(self, info): # noqa: N802
|
|
125
|
+
def mailstatus(x):
|
|
126
|
+
if x == "D":
|
|
127
|
+
return Q(error=False, sent=True)
|
|
128
|
+
elif x == "P":
|
|
129
|
+
return Q(error=False, sent=False, sending=False)
|
|
130
|
+
elif x == "S":
|
|
131
|
+
return Q(error=False, sent=False, sending=True)
|
|
132
|
+
elif x == "E":
|
|
133
|
+
return Q(error=True)
|
|
134
|
+
else:
|
|
135
|
+
return Q()
|
|
136
|
+
|
|
137
|
+
mailoptions = [
|
|
138
|
+
("D", _("Sent")), # Sent - Done
|
|
139
|
+
("P", _("Pending")), # Pending - Pending
|
|
140
|
+
("S", _("Sending")), # Sending - Sending
|
|
141
|
+
("E", _("Error")), # Error - Error
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
"sent": (_("Sent"), lambda x: mailstatus(x), mailoptions),
|
|
146
|
+
"uuid": (_("UUID"), lambda x: Q(uuid__icontains=x), "input"),
|
|
147
|
+
"priority": (_("Priority"), lambda x: Q(priority=x), "input"),
|
|
148
|
+
"opened": (
|
|
149
|
+
_("Opened"),
|
|
150
|
+
lambda x: ~Q(opened__isnull=x),
|
|
151
|
+
[(True, _("Yes")), (False, _("No"))],
|
|
152
|
+
),
|
|
153
|
+
"efrom": (_("From"), lambda x: Q(efrom__icontains=x), "input"),
|
|
154
|
+
"eto": (_("To"), lambda x: Q(eto__icontains=x), "input"),
|
|
155
|
+
"retries": (_("Retries"), lambda x: Q(retries=x), "input"),
|
|
156
|
+
"pk": (_("ID"), lambda x: Q(pk=x), "input"),
|
|
157
|
+
}
|
|
158
|
+
|
|
95
159
|
def __unicode__(self):
|
|
96
160
|
return "{} ({})".format(self.eto, self.pk)
|
|
97
161
|
|
|
@@ -100,9 +164,63 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
100
164
|
self.opened = timezone.now()
|
|
101
165
|
self.save()
|
|
102
166
|
|
|
103
|
-
|
|
167
|
+
@classmethod
|
|
168
|
+
def process_queue(
|
|
169
|
+
cls, connection=None, legacy=False, silent=True, debug=False
|
|
170
|
+
):
|
|
171
|
+
"""
|
|
172
|
+
This method will process all the emails in the queue
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
# Process queue
|
|
176
|
+
emails = cls.objects.filter(
|
|
177
|
+
sent=False, error=False, next_retry__lte=timezone.now()
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Do we have to send emails
|
|
181
|
+
if emails.count():
|
|
182
|
+
|
|
183
|
+
# Get connection if not connected yet
|
|
184
|
+
if connection is None:
|
|
185
|
+
|
|
186
|
+
# Connect
|
|
187
|
+
connection = cls.internal_connect(legacy)
|
|
188
|
+
|
|
189
|
+
# Configure them as sending
|
|
190
|
+
emails.update(sending=True)
|
|
191
|
+
|
|
192
|
+
# Send them
|
|
193
|
+
for email in emails.order_by("priority"):
|
|
194
|
+
try:
|
|
195
|
+
email.send(
|
|
196
|
+
connection=connection,
|
|
197
|
+
legacy=legacy,
|
|
198
|
+
silent=silent,
|
|
199
|
+
debug=debug,
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
|
|
203
|
+
# Los the error into this email
|
|
204
|
+
if email.log is None:
|
|
205
|
+
email.log = ""
|
|
206
|
+
email.log += f"Error: {e}\n"
|
|
207
|
+
email.sending = False
|
|
208
|
+
email.save()
|
|
209
|
+
email.warning(f"Error at EmailMessage<{{email.pk}}>: {e}")
|
|
210
|
+
|
|
211
|
+
# Set all emails to not sending, since we are stopping now
|
|
212
|
+
emails.update(sending=False)
|
|
213
|
+
|
|
214
|
+
# Re-raise the exception
|
|
215
|
+
raise
|
|
216
|
+
|
|
217
|
+
emails.update(sending=False)
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def internal_connect(cls, legacy=False):
|
|
104
221
|
"""
|
|
105
|
-
This class will return a connection instance, you can disconnect it
|
|
222
|
+
This class will return a connection instance, you can disconnect it
|
|
223
|
+
with connection.close()
|
|
106
224
|
"""
|
|
107
225
|
|
|
108
226
|
if not legacy:
|
|
@@ -119,15 +237,29 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
119
237
|
use_tls = settings.EMAIL_USE_TLS
|
|
120
238
|
|
|
121
239
|
# Remember last connection data
|
|
122
|
-
|
|
240
|
+
connect_info = {
|
|
241
|
+
"host": host,
|
|
242
|
+
"port": port,
|
|
243
|
+
"use_tls": use_tls,
|
|
244
|
+
"legacy": legacy,
|
|
245
|
+
}
|
|
123
246
|
# Get connection
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
247
|
+
return (
|
|
248
|
+
get_connection(
|
|
249
|
+
host=host,
|
|
250
|
+
port=port,
|
|
251
|
+
username=username,
|
|
252
|
+
password=password,
|
|
253
|
+
use_tls=use_tls,
|
|
254
|
+
),
|
|
255
|
+
connect_info,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def connect(self, legacy=False):
|
|
259
|
+
(connection, self.__connect_info) = EmailMessage.internal_connect(
|
|
260
|
+
legacy
|
|
130
261
|
)
|
|
262
|
+
return connection
|
|
131
263
|
|
|
132
264
|
def send(
|
|
133
265
|
self,
|
|
@@ -135,7 +267,7 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
135
267
|
legacy=False,
|
|
136
268
|
silent=True,
|
|
137
269
|
debug=False,
|
|
138
|
-
content_subtype=
|
|
270
|
+
content_subtype=None,
|
|
139
271
|
):
|
|
140
272
|
|
|
141
273
|
# Autoconfigure Debugger
|
|
@@ -143,6 +275,16 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
143
275
|
self.set_name("EmailMessage")
|
|
144
276
|
self.set_debug()
|
|
145
277
|
|
|
278
|
+
# Warn about subtype
|
|
279
|
+
if content_subtype:
|
|
280
|
+
self.warning(
|
|
281
|
+
_(
|
|
282
|
+
"Programming ERROR: You are using content_subtype, this "
|
|
283
|
+
"value has been DEPRECATED and will be remove in future "
|
|
284
|
+
"versions."
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
|
|
146
288
|
# Get connection if not connected yet
|
|
147
289
|
if connection is None:
|
|
148
290
|
# Connect
|
|
@@ -155,6 +297,7 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
155
297
|
self.set_name("EmailMessage->{}".format(self.eto))
|
|
156
298
|
|
|
157
299
|
# Manually open the connection
|
|
300
|
+
error = None
|
|
158
301
|
try:
|
|
159
302
|
connection.open()
|
|
160
303
|
except (
|
|
@@ -163,8 +306,6 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
163
306
|
TimeoutError,
|
|
164
307
|
) as e:
|
|
165
308
|
connection = None
|
|
166
|
-
if self.log is None:
|
|
167
|
-
self.log = ""
|
|
168
309
|
exceptiontxt = str(type(e)).split(".")[-1].split("'")[0]
|
|
169
310
|
ci = getattr(self, "__connect_info", {})
|
|
170
311
|
error = "{}: {} [HOST={}:{} TLS={}]\n".format(
|
|
@@ -175,12 +316,14 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
175
316
|
ci.get("use_tls", "-"),
|
|
176
317
|
)
|
|
177
318
|
self.warning(error)
|
|
178
|
-
self.log
|
|
179
|
-
|
|
319
|
+
if self.log is None:
|
|
320
|
+
self.log = ""
|
|
321
|
+
self.log += f"{error}\n"
|
|
322
|
+
# We will not retry anymore (for now)
|
|
180
323
|
self.sending = False
|
|
181
324
|
# We make lower this email's priority
|
|
182
325
|
self.priority += 1
|
|
183
|
-
# Set new retry
|
|
326
|
+
# Set we just made a new retry
|
|
184
327
|
self.retries += 1
|
|
185
328
|
self.next_retry = timezone.now() + timezone.timedelta(
|
|
186
329
|
seconds=getattr(
|
|
@@ -204,7 +347,7 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
204
347
|
[self.eto],
|
|
205
348
|
connection=connection,
|
|
206
349
|
)
|
|
207
|
-
email.content_subtype = content_subtype
|
|
350
|
+
email.content_subtype = self.content_subtype
|
|
208
351
|
for at in self.attachments.all():
|
|
209
352
|
with open(at.path) as f:
|
|
210
353
|
email.attach(at.filename, f.read(), at.mime)
|
|
@@ -212,6 +355,7 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
212
355
|
# send list emails
|
|
213
356
|
retries = 1
|
|
214
357
|
while retries + 1:
|
|
358
|
+
error = None
|
|
215
359
|
try:
|
|
216
360
|
if connection.send_messages([email]):
|
|
217
361
|
# We are done
|
|
@@ -219,27 +363,46 @@ class EmailMessage(CodenerixModel, Debugger):
|
|
|
219
363
|
self.sending = False
|
|
220
364
|
break
|
|
221
365
|
except ssl.SSLError as e:
|
|
222
|
-
error = "SSLError: {}\n"
|
|
366
|
+
error = f"SSLError: {e}\n"
|
|
223
367
|
self.warning(error)
|
|
224
|
-
self.log
|
|
368
|
+
if self.log is None:
|
|
369
|
+
self.log = ""
|
|
370
|
+
self.log += f"{error}\n"
|
|
225
371
|
except smtplib.SMTPServerDisconnected as e:
|
|
226
|
-
error = "SMTPServerDisconnected: {}\n"
|
|
227
|
-
self.
|
|
228
|
-
|
|
372
|
+
error = f"SMTPServerDisconnected: {e}\n"
|
|
373
|
+
if self.log is None:
|
|
374
|
+
self.log = ""
|
|
375
|
+
self.log += f"{error}\n"
|
|
376
|
+
try:
|
|
377
|
+
connection.open()
|
|
378
|
+
error = None
|
|
379
|
+
except (
|
|
380
|
+
smtplib.SMTPAuthenticationError,
|
|
381
|
+
OSError,
|
|
382
|
+
TimeoutError,
|
|
383
|
+
) as e:
|
|
384
|
+
self.warning(error)
|
|
385
|
+
error = f"SMTPServerReconnect: {e}\n"
|
|
386
|
+
self.warning(error)
|
|
387
|
+
if self.log is None:
|
|
388
|
+
self.log = ""
|
|
389
|
+
self.log += f"{error}\n"
|
|
229
390
|
except smtplib.SMTPException as e:
|
|
230
|
-
error = "SMTPException: {}\n"
|
|
391
|
+
error = f"SMTPException: {e}\n"
|
|
231
392
|
self.warning(error)
|
|
232
|
-
self.log
|
|
393
|
+
if self.log is None:
|
|
394
|
+
self.log = ""
|
|
395
|
+
self.log += f"{error}\n"
|
|
233
396
|
finally:
|
|
234
397
|
# One chance less
|
|
235
398
|
retries -= 1
|
|
236
399
|
# Check if this is the last try
|
|
237
400
|
if not retries:
|
|
238
|
-
# We will not retry anymore
|
|
401
|
+
# We will not retry anymore (fow now)
|
|
239
402
|
self.sending = False
|
|
240
403
|
# We make lower this email's priority
|
|
241
404
|
self.priority += 1
|
|
242
|
-
# Set new retry
|
|
405
|
+
# Set we just made a new retry
|
|
243
406
|
self.retries += 1
|
|
244
407
|
self.next_retry = (
|
|
245
408
|
timezone.now()
|
|
@@ -293,12 +456,21 @@ class EmailTemplate(CodenerixModel):
|
|
|
293
456
|
_("CID"), unique=True, max_length=30, blank=False, null=False
|
|
294
457
|
)
|
|
295
458
|
efrom = models.TextField(_("From"), blank=True, null=False)
|
|
459
|
+
content_subtype = models.CharField(
|
|
460
|
+
_("Content Subtype"),
|
|
461
|
+
max_length=256,
|
|
462
|
+
choices=CONTENT_SUBTYPES,
|
|
463
|
+
blank=False,
|
|
464
|
+
null=False,
|
|
465
|
+
default=CONTENT_SUBTYPE_HTML,
|
|
466
|
+
)
|
|
296
467
|
|
|
297
468
|
def __fields__(self, info):
|
|
298
469
|
fields = []
|
|
299
470
|
fields.append(("pk", _("PK"), 100))
|
|
300
471
|
fields.append(("cid", _("CID"), 100))
|
|
301
472
|
fields.append(("efrom", _("From"), 100))
|
|
473
|
+
fields.append(("content_subtype", _("Content Subtype"), 100))
|
|
302
474
|
return fields
|
|
303
475
|
|
|
304
476
|
def __str__(self):
|
|
@@ -313,7 +485,8 @@ class EmailTemplate(CodenerixModel):
|
|
|
313
485
|
Usages:
|
|
314
486
|
EmailTemplate.get('PACO', ctx) => EmailMessage(): le falta el eto
|
|
315
487
|
> cid: PACO
|
|
316
|
-
EmailTemplate.get(pk='PACO', context=ctx) => EmailMessage(): le
|
|
488
|
+
EmailTemplate.get(pk='PACO', context=ctx) => EmailMessage(): le
|
|
489
|
+
falta el eto
|
|
317
490
|
> pk: PACO (we don't have CID)
|
|
318
491
|
"""
|
|
319
492
|
if cid:
|
|
@@ -337,6 +510,7 @@ class EmailTemplate(CodenerixModel):
|
|
|
337
510
|
)
|
|
338
511
|
e.body = Template(getattr(self, lang).body).render(Context(context))
|
|
339
512
|
e.efrom = Template(self.efrom).render(Context(context))
|
|
513
|
+
e.content_subtype = self.content_subtype
|
|
340
514
|
return e
|
|
341
515
|
|
|
342
516
|
def clean(self):
|
|
@@ -345,7 +519,8 @@ class EmailTemplate(CodenerixModel):
|
|
|
345
519
|
if len(re.findall(r"[A-Za-z0-9]+", self.cid)) != 1:
|
|
346
520
|
raise ValidationError(
|
|
347
521
|
_(
|
|
348
|
-
"CID can contains only number and letters with
|
|
522
|
+
"CID can contains only number and letters with "
|
|
523
|
+
"no spaces"
|
|
349
524
|
)
|
|
350
525
|
)
|
|
351
526
|
|
|
@@ -378,8 +553,10 @@ for info in MODELS:
|
|
|
378
553
|
field = info[0]
|
|
379
554
|
model = info[1]
|
|
380
555
|
for lang_code in settings.LANGUAGES_DATABASES:
|
|
381
|
-
query = "class {}Text{}(GenText):\n"
|
|
382
|
-
query +=
|
|
383
|
-
field
|
|
556
|
+
query = f"class {model}Text{lang_code}(GenText):\n"
|
|
557
|
+
query += (
|
|
558
|
+
f" {field} = models.OneToOneField({model}, "
|
|
559
|
+
f"on_delete=models.CASCADE, blank=False, null=False, "
|
|
560
|
+
f"related_name='{lang_code.lower()}')\n"
|
|
384
561
|
)
|
|
385
562
|
exec(query)
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
<td>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<center>
|
|
3
|
+
<span class='fa fa-refresh fa-spin fa-fw text-info' ng-if='!row.error && row.sending' alt='{{data.meta.gentranslate.sending}}...' title='{{data.meta.gentranslate.sending}}...'></span>
|
|
4
|
+
<span class='fa fa-envelope-o text-success' ng-if='!row.error && !row.sending && row.sent' alt='{{data.meta.gentranslate.sent}}' title='{{data.meta.gentranslate.sent}}'></span>
|
|
5
|
+
<span class='fa fa-envelope-open-o' ng-if='!row.error && !row.sending && !row.sent' alt='{{data.meta.gentranslate.waiting}}' title='{{data.meta.gentranslate.waiting}}'></span>
|
|
6
|
+
<span ng-if='row.error' alt='{{data.meta.gentranslate.notsent}}' title='{{data.meta.gentranslate.notsent}}' codenerix-html-compile="false|codenerix"></span>
|
|
7
|
+
</center>
|
|
6
8
|
</td>
|
|
7
|
-
<td>{{row.priority|codenerix}}</td>
|
|
8
|
-
<td>{{row.
|
|
9
|
-
<td class="text-nowrap">{{row.created|codenerix}}</td>
|
|
9
|
+
<td><center>{{row.priority|codenerix}}</center></td>
|
|
10
|
+
<td class="text-nowrap">{{row.updated|codenerix}}</td>
|
|
10
11
|
<td>
|
|
11
12
|
<span title="{{row.opened}}" codenerix-html-compile="row.opened|codenerix:'bool'"><span>
|
|
12
13
|
</td>
|
|
13
14
|
<td>{{row.efrom|codenerix}}</td>
|
|
14
15
|
<td>{{row.eto|codenerix}}</td>
|
|
15
16
|
<td>{{row.subject|codenerix}}</td>
|
|
16
|
-
<td>{{row.retries|codenerix}}</td>
|
|
17
|
+
<td><center>{{row.retries|codenerix}}</center></td>
|
|
17
18
|
<td class="text-nowrap">{{row.next_retry|codenerix}}</td>
|
|
18
|
-
<td>{{row.pk|codenerix}}</td>
|
|
19
|
+
<td><center>{{row.pk|codenerix}}</center></td>
|
|
20
|
+
<td>
|
|
21
|
+
<center>
|
|
22
|
+
<button class="btn" title="{{row.content_subtype|codenerix}}">
|
|
23
|
+
<i ng-show="row.content_subtype=='plain'" class="fa fa-file-text-o" aria-hidden="true"></i>
|
|
24
|
+
<i ng-show="row.content_subtype=='html'" class="fa fa-code" aria-hidden="true"></i>
|
|
25
|
+
<i ng-hide="(row.content_subtype=='plain') || (row.content_subtype=='html')"class="fa fa-question-circle-o" aria-hidden="true"></i>
|
|
26
|
+
</button>
|
|
27
|
+
</center>
|
|
28
|
+
</td>
|
|
29
|
+
<td>{{row.uuid|codenerix}}</td>
|
codenerix_email/views.py
CHANGED
|
@@ -144,7 +144,8 @@ class EmailTemplateDelete(GenDelete):
|
|
|
144
144
|
class EmailMessageList(GenList):
|
|
145
145
|
model = EmailMessage
|
|
146
146
|
show_details = True
|
|
147
|
-
|
|
147
|
+
search_filter_button = True
|
|
148
|
+
default_ordering = ["-updated"]
|
|
148
149
|
static_partial_row = "codenerix_email/partials/emailmessages_rows.html"
|
|
149
150
|
gentranslate = {
|
|
150
151
|
"sending": _("Sending"),
|
{django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.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.12
|
|
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>
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
codenerix_email/__init__.py,sha256=
|
|
1
|
+
codenerix_email/__init__.py,sha256=7EbI8PxpPMEHM1uvXlStjZZSYxcLFgwcqtW3UD6OadM,149
|
|
2
2
|
codenerix_email/admin.py,sha256=o3b-MaD7xsWeta0yfU9YNeKBmHQIcmHqXmP2t-hrrWc,1208
|
|
3
3
|
codenerix_email/apps.py,sha256=WXqu1XQibDDyCvvQYt2JbTK4GIpW8BNv5DCbRJS2mmk,149
|
|
4
|
-
codenerix_email/forms.py,sha256=
|
|
5
|
-
codenerix_email/models.py,sha256=
|
|
4
|
+
codenerix_email/forms.py,sha256=n-UrVPI7NMXNImEdJ4uB0HWHEvBNv71YKZ_RPLW4FSg,4075
|
|
5
|
+
codenerix_email/models.py,sha256=Y9Du0C9W3_h9Di9n0HphY15smzG1wFccMasHYjnbP0k,19063
|
|
6
6
|
codenerix_email/urls.py,sha256=QYrSJdhCp2PLrcfv_Y7X4OzxqQ0Q2-z7ObDPE-_WLdw,2595
|
|
7
7
|
codenerix_email/urls_frontend.py,sha256=DG5WS_fGQJC9sezNebPwG-E_WtUWy3Kwz4VUxh82xuc,873
|
|
8
|
-
codenerix_email/views.py,sha256=
|
|
8
|
+
codenerix_email/views.py,sha256=PyOWSAblRLzbYAzzTCZTR-VapKzf7-jpxnJ9TwX2t7Y,4790
|
|
9
9
|
codenerix_email/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
codenerix_email/management/__pycache__/__init__.cpython-310.pyc,sha256=7vUcYbg6ZpWdEhb0hq2FSiwMtpBWIjMTv8H8rcgtiOY,179
|
|
11
11
|
codenerix_email/management/__pycache__/__init__.cpython-311.pyc,sha256=OtPcxwWjtJcDfDWQCWutxYIIwHerLtkPPyMcEFOpdMM,195
|
|
12
12
|
codenerix_email/management/__pycache__/__init__.cpython-35.pyc,sha256=sBoEWs6zdI0al-7t1deW9SE_Ln2RNDl2LyIiOO9gfRA,160
|
|
13
13
|
codenerix_email/management/__pycache__/__init__.cpython-39.pyc,sha256=uPXklfliVd3b8pLOJQT9ZeKcqmJMrGychvt68BsPulY,168
|
|
14
14
|
codenerix_email/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
codenerix_email/management/commands/send_emails.py,sha256=
|
|
15
|
+
codenerix_email/management/commands/send_emails.py,sha256=hotzGEkuDILwKgZgHFOaSCeAZDIyoPcM7VDs3Z-5Vvg,7950
|
|
16
16
|
codenerix_email/management/commands/test_email.py,sha256=riV1zhSVY8t9EhzV2IXkIkTnhFyvAGGYpwGiwEKx7NM,1655
|
|
17
|
+
codenerix_email/management/commands/__pycache__/__init__.cpython-311.pyc,sha256=yiqmtIhQMYXWx7g7XT-mvQNgGu_X2ymasWvVxIqPsBE,211
|
|
18
|
+
codenerix_email/management/commands/__pycache__/send_emails.cpython-311.pyc,sha256=xh2ontFVtLWHZOoVf1YJrNbBNPtIyV2KQEk5Bf6mY_c,6884
|
|
17
19
|
codenerix_email/migrations/0001_initial.py,sha256=Rp6noz8vjtGi4f4qc2w_es_VV9PmO7WvVfv666mu9xE,3511
|
|
18
20
|
codenerix_email/migrations/0002_auto_20170502_1043.py,sha256=-zoc4RuZFXJA1Fw8ECCVqAg-PYfku3yxdtYNyXPI3LM,2369
|
|
19
21
|
codenerix_email/migrations/0003_auto_20170921_1206.py,sha256=ncVdyZJ616vQpllGdaPbFS0n9qKfDP-TuVA5HkbPf4I,656
|
|
@@ -23,6 +25,8 @@ codenerix_email/migrations/0006_emailmessage_error.py,sha256=qEZ-GQDufZDTdiH5w4c
|
|
|
23
25
|
codenerix_email/migrations/0007_emailmessage_next_retry.py,sha256=aSOTLqoYFo2V78r5dn-HmEs-loutCoqS-BF97_Vy_sU,602
|
|
24
26
|
codenerix_email/migrations/0008_auto_20171201_0928.py,sha256=cqlgf9IfNgU-IJey073K1FT-pp6i2gXDgLnU-xrFir0,731
|
|
25
27
|
codenerix_email/migrations/0009_emailmessage_opened_emailmessage_uuid.py,sha256=jt3AzNCHM-hTuV9FP1FTzYMJnsl_GNQ74jv5n9_adi4,1219
|
|
28
|
+
codenerix_email/migrations/0010_emailmessage_content_subtype_and_more.py,sha256=Zy9OwxcQCPoVdemJRlotfpGVyTLuzNkCNQYfoEVnvB0,1001
|
|
29
|
+
codenerix_email/migrations/0011_alter_emailmessage_content_subtype_and_more.py,sha256=Ki38KPngeeTIhrki57OXEwCB5P7JnI1Hv58dwRYLO7w,957
|
|
26
30
|
codenerix_email/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
31
|
codenerix_email/migrations/__pycache__/0001_initial.cpython-310.pyc,sha256=Sm54OKfGHUMvSVgbXiFAe9E2d85HqlLoMEaBEBP53mE,2005
|
|
28
32
|
codenerix_email/migrations/__pycache__/0001_initial.cpython-311.pyc,sha256=mOTNcz_iff63bExW7wNyR_HXU-e8ON-Au03YpCL_cCw,3682
|
|
@@ -58,14 +62,16 @@ codenerix_email/migrations/__pycache__/0008_auto_20171201_0928.cpython-35.pyc,sh
|
|
|
58
62
|
codenerix_email/migrations/__pycache__/0008_auto_20171201_0928.cpython-39.pyc,sha256=YQE0mlPzQ-MXjrPcJVK74O_rg6hsSnKJfTlqttYYlPU,794
|
|
59
63
|
codenerix_email/migrations/__pycache__/0009_delete_emailtemplatetexten.cpython-311.pyc,sha256=9BM5UKxi6pwZbBpULueb5BLCuMUIYGh6-r0PDvTE2LQ,733
|
|
60
64
|
codenerix_email/migrations/__pycache__/0009_emailmessage_opened_emailmessage_uuid.cpython-311.pyc,sha256=clSN_yEmdCuAm8QFGKCa8W35Emj7aADpHjHEqIOLdfo,1860
|
|
65
|
+
codenerix_email/migrations/__pycache__/0010_emailmessage_content_subtype_and_more.cpython-311.pyc,sha256=MTWH-MJZqJeT42E-nnRYs2H0vwnBN5kGCk1-TOF6YpE,1201
|
|
61
66
|
codenerix_email/migrations/__pycache__/0010_emailmessage_opened_emailmessage_uuid_and_more.cpython-311.pyc,sha256=ZoBI5fyc5vTdGSejQl5953krxh_BXl17tuft5ZmdH6U,3104
|
|
67
|
+
codenerix_email/migrations/__pycache__/0011_alter_emailmessage_content_subtype_and_more.cpython-311.pyc,sha256=rWr5NdYUVeF71WxMUPIzUN67Ua6fhWWjci5dAG7mpRg,1195
|
|
62
68
|
codenerix_email/migrations/__pycache__/__init__.cpython-310.pyc,sha256=PdaaABMdLCzCMnJmxaylT_jnLb8l7rr6n5C4CGDX37A,151
|
|
63
69
|
codenerix_email/migrations/__pycache__/__init__.cpython-311.pyc,sha256=RbbUUEhcJ_eAVNFAdNbkjv60hyF7BsnNOlti5xplQsY,195
|
|
64
70
|
codenerix_email/migrations/__pycache__/__init__.cpython-35.pyc,sha256=2g70xiMW6oJNkIpRM-0Dr5h7AUac-3xyCXPONxp9BBw,147
|
|
65
71
|
codenerix_email/migrations/__pycache__/__init__.cpython-39.pyc,sha256=qNj2NH0YvoWPnCKxkVZPsEFsbM05y7t1njMskNISdVQ,168
|
|
66
|
-
codenerix_email/static/codenerix_email/partials/emailmessages_rows.html,sha256=
|
|
67
|
-
django_codenerix_email-4.0.
|
|
68
|
-
django_codenerix_email-4.0.
|
|
69
|
-
django_codenerix_email-4.0.
|
|
70
|
-
django_codenerix_email-4.0.
|
|
71
|
-
django_codenerix_email-4.0.
|
|
72
|
+
codenerix_email/static/codenerix_email/partials/emailmessages_rows.html,sha256=2KjE_YZ91OAFZuHteQeQ2r2vOBeZmwmnJQ4L-bH4B50,1735
|
|
73
|
+
django_codenerix_email-4.0.12.dist-info/LICENSE,sha256=IXMIpi75XsrJt1Sznt4EftT9c_4X0C9eqK4tHhH8H48,11339
|
|
74
|
+
django_codenerix_email-4.0.12.dist-info/METADATA,sha256=vxgtpJQ43fdEoUhzSjk1kNjzn9OQo2K0X06JM64vv9Q,2640
|
|
75
|
+
django_codenerix_email-4.0.12.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110
|
|
76
|
+
django_codenerix_email-4.0.12.dist-info/top_level.txt,sha256=lljSA0iKE_UBEM5gIrGQwioC_i8Jjnp-aR1LFElENgw,16
|
|
77
|
+
django_codenerix_email-4.0.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{django_codenerix_email-4.0.10.dist-info → django_codenerix_email-4.0.12.dist-info}/top_level.txt
RENAMED
|
File without changes
|