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.
@@ -1,4 +1,4 @@
1
- __version__ = "4.0.10"
1
+ __version__ = "4.0.12"
2
2
 
3
3
  __authors__ = [
4
4
  "Juan Miguel Taboada Godoy <juanmi@juanmitaboada.com>",
codenerix_email/forms.py CHANGED
@@ -36,9 +36,18 @@ class EmailTemplateForm(GenModelForm):
36
36
  (
37
37
  _("Details"),
38
38
  12,
39
- ["cid", 3],
40
- ["efrom", 9],
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
- ["priority", 3],
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
- ["opened", 3],
107
+ ["priority", 3],
93
108
  ["log", 3],
94
109
  ),
95
110
  (
96
111
  _("Body"),
97
- 6,
112
+ 12,
98
113
  ["body", 3],
99
114
  ),
100
115
  ]
@@ -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
- # Get user configuration
81
- daemon = bool(options["daemon"] or options["d"])
82
- clear = bool(options["clear"] or options["c"])
83
- BUCKET_SIZE = getattr(settings, "CLIENT_EMAIL_BUCKETS", 10)
84
- if daemon:
85
- self.debug(
86
- "Starting command in DAEMON mode with a queue of {} emails".format(
87
- BUCKET_SIZE
88
- ),
89
- color="cyan",
90
- )
91
- else:
92
- self.debug(
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
- next_retry__lte=timezone.now(),
114
- ).order_by("priority", "next_retry")[0 : BUCKET_SIZE + 1]
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
- self.debug(
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
- " - Connecting",
139
- color="yellow",
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 email.sent:
158
- self.debug(" -> SENT", color="green", head=False)
159
- else:
160
- self.debug(
161
- " -> ERROR", color="red", head=False, tail=False
162
- )
163
- self.debug(
164
- " ({} retries left)".format(
165
- getattr(settings, "CLIENT_EMAIL_RETRIES", 10)
166
- - email.retries
167
- ),
168
- color="cyan",
169
- head=False,
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
- else:
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/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 # type: ignore
36
+ from codenerix.models import CodenerixModel
34
37
  from codenerix_lib.debugger import Debugger
35
- from codenerix.lib.genmail import ( # type: ignore # noqa: N817
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 # type: ignore
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, 100))
81
- fields.append(("error", None, 100))
82
- fields.append(("sent", _("Send"), 100))
83
- fields.append(("priority", _("Priority"), 100))
84
- fields.append(("uuid", _("UUID"), 100))
85
- fields.append(("created", _("Created"), 100))
86
- fields.append(("opened", _("Opened"), 100))
87
- fields.append(("efrom", _("From"), 100))
88
- fields.append(("eto", _("To"), 100))
89
- fields.append(("subject", _("Subject"), 100))
90
- fields.append(("retries", _("Retries"), 100))
91
- fields.append(("next_retry", _("Next retry"), 100))
92
- fields.append(("pk", _("ID"), 100))
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
- def connect(self, legacy=False):
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 with connection.close()
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
- self.__connect_info = {"host": host, "port": port, "use_tls": use_tls}
240
+ connect_info = {
241
+ "host": host,
242
+ "port": port,
243
+ "use_tls": use_tls,
244
+ "legacy": legacy,
245
+ }
123
246
  # Get connection
124
- return get_connection(
125
- host=host,
126
- port=port,
127
- username=username,
128
- password=password,
129
- use_tls=use_tls,
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="plain",
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 += error
179
- # We will not retry anymore
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".format(e)
366
+ error = f"SSLError: {e}\n"
223
367
  self.warning(error)
224
- self.log += error
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".format(e)
227
- self.warning(error)
228
- self.log += error
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".format(e)
391
+ error = f"SMTPException: {e}\n"
231
392
  self.warning(error)
232
- self.log += error
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 falta el eto
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 no spaces"
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".format(model, lang_code)
382
- query += " {} = models.OneToOneField({}, on_delete=models.CASCADE, blank=False, null=False, related_name='{}')\n".format(
383
- field, model, lang_code.lower()
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
- <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>
3
- <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>
4
- <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>
5
- <span ng-if='row.error' alt='{{data.meta.gentranslate.notsent}}' title='{{data.meta.gentranslate.notsent}}' codenerix-html-compile="false|codenerix"></span>
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.uuid|codenerix}}</td>
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
- default_ordering = ["-created"]
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"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-codenerix-email
3
- Version: 4.0.10
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=2zJS-yDMitL9qpjQIh8y7gnRJtFviLBEH65RPfd2Fzc,149
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=6qEOBoNIVidS5w-CdCKIHLU5kkFerG02BqrkCWRu7Hs,3706
5
- codenerix_email/models.py,sha256=UVfMzN1yGztdS7y-_W1CUL6Xu5P9e1D72OYGl6Lr9OM,13286
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=T6qAFeD_PTlX-6eZuSFP68s80vrFne0ZkZjTbHhbyV8,4758
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=eVRWGQ67E6XT3Dqq_q4WAs50i-kKeXRtqY4oOV_hy7Y,6290
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=AvSL9B5r6obY792JExRbFn8NM7i0h-FCGGUhFgemMwk,1170
67
- django_codenerix_email-4.0.10.dist-info/LICENSE,sha256=IXMIpi75XsrJt1Sznt4EftT9c_4X0C9eqK4tHhH8H48,11339
68
- django_codenerix_email-4.0.10.dist-info/METADATA,sha256=umpHyIFZbxRNmTS-WxOYpi8tH1vXzs7M8J7fVV8YyyE,2640
69
- django_codenerix_email-4.0.10.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110
70
- django_codenerix_email-4.0.10.dist-info/top_level.txt,sha256=lljSA0iKE_UBEM5gIrGQwioC_i8Jjnp-aR1LFElENgw,16
71
- django_codenerix_email-4.0.10.dist-info/RECORD,,
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,,