diff --git a/django_tasks/backends/database/apps.py b/django_tasks/backends/database/apps.py index d5bec65..d9de15a 100644 --- a/django_tasks/backends/database/apps.py +++ b/django_tasks/backends/database/apps.py @@ -5,6 +5,3 @@ class TasksAppConfig(AppConfig): name = "django_tasks.backends.database" label = "django_tasks_database" verbose_name = "Tasks Database Backend" - - def ready(self) -> None: - from . import signal_handlers # noqa diff --git a/django_tasks/backends/database/migrations/0017_alter_dbtaskresult_run_after.py b/django_tasks/backends/database/migrations/0017_alter_dbtaskresult_run_after.py new file mode 100644 index 0000000..aa460c8 --- /dev/null +++ b/django_tasks/backends/database/migrations/0017_alter_dbtaskresult_run_after.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.4 on 2025-05-07 19:10 + +from django.db import migrations + +import django_tasks.backends.database.models + + +class Migration(migrations.Migration): + dependencies = [ + ("django_tasks_database", "0016_alter_dbtaskresult_options_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="dbtaskresult", + name="run_after", + field=django_tasks.backends.database.models.NullIsMaxDateTimeField( + verbose_name="run after" + ), + ), + ] diff --git a/django_tasks/backends/database/models.py b/django_tasks/backends/database/models.py index 59de8eb..0fe6a93 100644 --- a/django_tasks/backends/database/models.py +++ b/django_tasks/backends/database/models.py @@ -50,6 +50,24 @@ def __class_getitem__(cls, _): DATE_MAX = datetime.datetime(9999, 1, 1, tzinfo=datetime.timezone.utc) +class NullIsMaxDateTimeField(models.DateTimeField): + """ + MySQL can only use indexes on simple queries, so instead of using null, + and nulls_last, or models.Case, set null/None as MAX_DATE, and vice-versa + """ + + def get_prep_value(self, value): # type: ignore + value = super().get_prep_value(value) + if value is None: + value = DATE_MAX + return value + + def from_db_value(self, value, expression, connection): # type: ignore + if value == DATE_MAX: + return None + return value + + class DBTaskResultQuerySet(models.QuerySet): def ready(self) -> "DBTaskResultQuerySet": """ @@ -104,7 +122,7 @@ class DBTaskResult(GenericBase[P, T], models.Model): ) backend_name = models.CharField(_("backend name"), max_length=32) - run_after = models.DateTimeField(_("run after")) + run_after = NullIsMaxDateTimeField(_("run after")) return_value = models.JSONField(_("return value"), default=None, null=True) @@ -155,7 +173,7 @@ def task(self) -> Task[P, T]: return task.using( priority=self.priority, queue_name=self.queue_name, - run_after=None if self.run_after == DATE_MAX else self.run_after, + run_after=self.run_after, backend=self.backend_name, ) diff --git a/django_tasks/backends/database/signal_handlers.py b/django_tasks/backends/database/signal_handlers.py deleted file mode 100644 index 09c0d3b..0000000 --- a/django_tasks/backends/database/signal_handlers.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Any - -from django.db.models.signals import pre_save -from django.dispatch import receiver - -from .models import DATE_MAX, DBTaskResult - - -@receiver(pre_save, sender=DBTaskResult) -def set_run_after(sender: Any, instance: DBTaskResult, **kwargs: Any) -> None: - if instance.run_after is None: - instance.run_after = DATE_MAX