4
4
import sys
5
5
import time
6
6
from datetime import datetime
7
+ from datetime import timedelta
7
8
8
9
from django .conf import settings
9
10
from django .contrib .sites .models import Site
10
11
from django .core import mail
11
12
from django .core .management .base import BaseCommand
13
+ from django .db .models import Q
12
14
from django .template .loader import render_to_string
15
+ from django .utils import timezone
13
16
from django .utils .translation import activate
14
17
from django .utils .translation import deactivate
15
18
@@ -75,7 +78,7 @@ def add_arguments(self, parser):
75
78
"--no-sys-exit" ,
76
79
action = "store_true" ,
77
80
dest = "no_sys_exit" ,
78
- help = "Skip sys-exit after forking daemon (for testing purposes)" ,
81
+ help = "Skip sys-exit after forking daemon (mainly for testing purposes)" ,
79
82
)
80
83
parser .add_argument (
81
84
"--daemon-sleep-interval" ,
@@ -84,6 +87,12 @@ def add_arguments(self, parser):
84
87
help = "Minimum sleep between each polling of the database." ,
85
88
default = SLEEP_TIME ,
86
89
)
90
+ parser .add_argument (
91
+ "--now" ,
92
+ action = "store" ,
93
+ dest = "now" ,
94
+ help = "Simulate when to start sending from (mainly for testing purposes)" ,
95
+ )
87
96
88
97
def _render_and_send (
89
98
self , template_name , template_subject_name , context , connection
@@ -168,7 +177,13 @@ def handle(self, *args, **options): # noqa: max-complexity=12
168
177
connection = mail .get_connection ()
169
178
170
179
if cron :
171
- self .send_mails (connection )
180
+ if self .options .get ("now" ):
181
+ now = self .options .get ("now" )
182
+ self .logger .info (f"using now: { now } " )
183
+ else :
184
+ now = timezone .now ()
185
+
186
+ self .send_mails (connection , now )
172
187
return
173
188
174
189
if not daemon :
@@ -183,34 +198,33 @@ def handle(self, *args, **options): # noqa: max-complexity=12
183
198
184
199
def send_loop (self , connection , sleep_time ):
185
200
186
- # This could be /improved by looking up the last notified person
187
201
last_sent = None
188
202
189
203
while True :
190
204
191
205
started_sending_at = datetime .now ()
192
206
self .logger .info ("Starting send loop at %s" % str (started_sending_at ))
207
+
208
+ # When we are looping, we don't want to iterate over user_settings that have
209
+ # an interval greater than our last_sent marker.
193
210
if last_sent :
194
211
user_settings = models .Settings .objects .filter (
195
212
interval__lte = ((started_sending_at - last_sent ).seconds // 60 ) // 60
196
213
).order_by ("user" )
214
+ now = self .options .get ("now" ) or timezone .now ()
197
215
else :
216
+ # TOD: This isn't perfect. If we are simulating a "now", we should also make a
217
+ # step-wise approach to incrementing it.
218
+ now = timezone .now ()
198
219
user_settings = None
199
220
200
221
self .send_mails (
201
- connection , last_sent = last_sent , user_settings = user_settings
222
+ connection , now , last_sent = last_sent , user_settings = user_settings
202
223
)
203
224
204
225
connection .close ()
205
226
last_sent = datetime .now ()
206
- elapsed_seconds = (last_sent - started_sending_at ).seconds
207
- time .sleep (
208
- max (
209
- (min (app_settings .NYT_INTERVALS )[0 ] - elapsed_seconds ) * 60 ,
210
- sleep_time ,
211
- 0 ,
212
- )
213
- )
227
+ time .sleep (sleep_time )
214
228
215
229
def _send_with_retry (
216
230
self , template_name , subject_template_name , context , connection , setting
@@ -239,6 +253,8 @@ def _send_with_retry(
239
253
for n in notifications :
240
254
n .is_emailed = True
241
255
n .save ()
256
+ n .subscription .last_sent = timezone .now ()
257
+ n .subscription .save ()
242
258
break
243
259
except smtplib .SMTPSenderRefused :
244
260
self .logger .error (
@@ -262,14 +278,16 @@ def _send_with_retry(
262
278
)
263
279
raise
264
280
265
- def send_mails (self , connection , last_sent = None , user_settings = None ):
281
+ def send_mails ( # noqa: max-complexity=12
282
+ self , connection , now , last_sent = None , user_settings = None
283
+ ):
266
284
"""
267
285
Does the lookups and sends out email digests to anyone who has them due.
268
286
Since the system may have different templates depending on which notification is being sent,
269
287
we will generate a call for each template.
270
288
"""
271
289
272
- self .logger .debug ("Entering send_mails()" )
290
+ self .logger .debug (f "Entering send_mails(now= { now } , last_sent= { last_sent } , ... )" )
273
291
274
292
connection .open ()
275
293
@@ -300,6 +318,8 @@ def send_mails(self, connection, last_sent=None, user_settings=None):
300
318
# this job is running in parallel with another unfinished process. Or a global lock.
301
319
for setting in user_settings :
302
320
321
+ threshold = now - timedelta (minutes = setting .interval )
322
+
303
323
context = {
304
324
"user" : None ,
305
325
"username" : None ,
@@ -319,9 +339,22 @@ def send_mails(self, connection, last_sent=None, user_settings=None):
319
339
320
340
emails_per_template = {}
321
341
342
+ filter_qs = Q ()
343
+
344
+ if setting .interval :
345
+
346
+ # How much time must have passed since either
347
+ # a) the subscription was created (in case nothing hast been sent)
348
+ # b) the subscription was last active (in case something has been sent)
349
+ filter_qs = Q (Q (created__lte = threshold ) & Q (last_sent = None )) | Q (
350
+ Q (last_sent__lte = threshold ) & Q (latest__is_emailed = False )
351
+ )
352
+
353
+ # The ordering by notification_type__key is because we want a predictable
354
+ # order currently just for testing purposes.
322
355
for subscription in setting .subscription_set .filter (
323
- send_emails = True , latest__is_emailed = False
324
- ):
356
+ filter_qs , send_emails = True
357
+ ). order_by ( "notification_type__key" ) :
325
358
try :
326
359
template_name = (
327
360
subscription .notification_type .get_email_template_name ()
@@ -338,15 +371,29 @@ def send_mails(self, connection, last_sent=None, user_settings=None):
338
371
emails_per_template .setdefault (
339
372
(template_name , subject_template_name ), []
340
373
)
341
- emails_per_template [(template_name , subject_template_name )].append (
342
- subscription .latest
374
+
375
+ # If there is an interval, we use the threshold.
376
+ if setting .interval :
377
+ filter_kwargs = {"created__lte" : threshold }
378
+ else :
379
+ filter_kwargs = {}
380
+ emails_per_template [(template_name , subject_template_name )] += list (
381
+ subscription .notification_set .filter (
382
+ is_emailed = False , ** filter_kwargs
383
+ ),
343
384
)
385
+ print ("found:" )
386
+ print (emails_per_template [(template_name , subject_template_name )])
344
387
345
388
# Send the prepared template names, subjects and context to the user
346
389
for (
347
390
template_name ,
348
391
subject_template_name ,
349
392
), notifications in emails_per_template .items ():
393
+
394
+ if not notifications :
395
+ continue
396
+
350
397
context ["notifications" ] = notifications
351
398
352
399
self ._send_with_retry (
0 commit comments