Skip to content

Commit e6daafe

Browse files
committed
Fix timezone support for run_after DATE_MAX
1 parent 945dfb3 commit e6daafe

File tree

5 files changed

+52
-10
lines changed

5 files changed

+52
-10
lines changed

django_tasks/backends/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any, TypeVar
55

66
from asgiref.sync import sync_to_async
7+
from django.conf import settings
78
from django.core.checks import messages
89
from django.db import connections
910
from django.utils import timezone
@@ -83,7 +84,11 @@ def validate_task(self, task: Task) -> None:
8384
if not self.supports_defer and task.run_after is not None:
8485
raise InvalidTaskError("Backend does not support run_after")
8586

86-
if task.run_after is not None and not timezone.is_aware(task.run_after):
87+
if (
88+
settings.USE_TZ
89+
and task.run_after is not None
90+
and not timezone.is_aware(task.run_after)
91+
):
8792
raise InvalidTaskError("run_after must be an aware datetime")
8893

8994
if self.queues and task.queue_name not in self.queues:

django_tasks/backends/database/migrations/0016_remove_dbtaskresult_django_task_new_ordering_idx_and_more.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ class Migration(migrations.Migration):
5454
model_name="dbtaskresult",
5555
name="run_after",
5656
field=models.DateTimeField(
57-
default=datetime.datetime(
58-
9999, 1, 1, 0, 0, tzinfo=datetime.timezone.utc
59-
),
57+
default=datetime.datetime(9999, 1, 1, 0, 0),
6058
verbose_name="run after",
6159
),
6260
preserve_default=False,

django_tasks/backends/database/models.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
55

66
import django
7+
from django.conf import settings
78
from django.core.exceptions import SuspiciousOperation
89
from django.db import models
910
from django.db.models import F, Q
@@ -48,7 +49,12 @@ def __class_getitem__(cls, _):
4849
return cls
4950

5051

51-
DATE_MAX = datetime.datetime(9999, 1, 1, tzinfo=datetime.timezone.utc)
52+
DATE_MAX = datetime.datetime(9999, 1, 1)
53+
DATE_MAXS = (DATE_MAX.astimezone(datetime.timezone.utc), DATE_MAX)
54+
55+
56+
def get_date_max() -> datetime.datetime:
57+
return DATE_MAXS[0] if settings.USE_TZ else DATE_MAXS[1]
5258

5359

5460
class DBTaskResultQuerySet(models.QuerySet):
@@ -58,7 +64,9 @@ def ready(self) -> "DBTaskResultQuerySet":
5864
"""
5965
return self.filter(
6066
status=ResultStatus.READY,
61-
).filter(models.Q(run_after=DATE_MAX) | models.Q(run_after__lte=timezone.now()))
67+
).filter(
68+
models.Q(run_after=get_date_max()) | models.Q(run_after__lte=timezone.now())
69+
)
6270

6371
def succeeded(self) -> "DBTaskResultQuerySet":
6472
return self.filter(status=ResultStatus.SUCCEEDED)
@@ -157,7 +165,7 @@ def task(self) -> Task[P, T]:
157165
return task.using(
158166
priority=self.priority,
159167
queue_name=self.queue_name,
160-
run_after=None if self.run_after == DATE_MAX else self.run_after,
168+
run_after=None if self.run_after in DATE_MAXS else self.run_after,
161169
backend=self.backend_name,
162170
)
163171

django_tasks/backends/database/signal_handlers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from django.db.models.signals import pre_save
44
from django.dispatch import receiver
55

6-
from .models import DATE_MAX, DBTaskResult
6+
from .models import DBTaskResult, get_date_max
77

88

99
@receiver(pre_save, sender=DBTaskResult)
1010
def set_run_after(sender: Any, instance: DBTaskResult, **kwargs: Any) -> None:
1111
if instance.run_after is None:
12-
instance.run_after = DATE_MAX
12+
instance.run_after = get_date_max()

tests/tests/test_database_backend.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import sys
88
import time
99
import uuid
10+
import warnings
1011
from collections import Counter
1112
from contextlib import redirect_stderr
12-
from datetime import timedelta
13+
from datetime import datetime, timedelta
1314
from functools import partial
1415
from io import StringIO
1516
from typing import Any, List, Optional, Sequence, Union, cast
@@ -436,6 +437,36 @@ def test_index_scan_for_ready(self) -> None:
436437
else:
437438
self.fail("Unknown database engine")
438439

440+
def test_run_after_tz(self) -> None:
441+
for use_tz in [True, False]:
442+
with self.subTest(use_tz=use_tz):
443+
with override_settings(USE_TZ=use_tz):
444+
result = test_tasks.noop_task.enqueue()
445+
self.assertIsNone(
446+
DBTaskResult.objects.get(id=result.id).task.run_after
447+
)
448+
449+
def test_run_after_null_0016_migration(self) -> None:
450+
for use_tz in [True, False]:
451+
with self.subTest(use_tz=use_tz):
452+
with override_settings(USE_TZ=use_tz):
453+
result = test_tasks.noop_task.enqueue()
454+
455+
db_result = DBTaskResult.objects.get(id=result.id)
456+
457+
# Literal taken from migration
458+
db_result.run_after = datetime(9999, 1, 1, 0, 0)
459+
460+
with warnings.catch_warnings():
461+
warnings.filterwarnings(
462+
"ignore", module="django.db", category=RuntimeWarning
463+
)
464+
db_result.save()
465+
466+
self.assertIsNone(
467+
DBTaskResult.objects.get(id=result.id).task.run_after
468+
)
469+
439470

440471
@override_settings(
441472
TASKS={

0 commit comments

Comments
 (0)