mirror of
https://github.com/django-q2/django-q2.git
synced 2026-09-15 21:47:53 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65d2548e98 | ||
|
|
cbe51f995c | ||
|
|
0090a6f411 | ||
|
|
a154fd658a | ||
|
|
81f2531e3e | ||
|
|
c464751ae9 |
34
.github/workflows/test.yml
vendored
34
.github/workflows/test.yml
vendored
@@ -26,25 +26,31 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
|
||||
django: [ "4.2", "5.0", "5.1"]
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
django:
|
||||
- "4.2"
|
||||
- "5.0"
|
||||
- "5.1"
|
||||
- "5.2"
|
||||
exclude:
|
||||
# django 5.1 does not support 3.8 and 3.9
|
||||
- python-version: "3.8"
|
||||
django: "5.1"
|
||||
# django 5.2 does not support 3.9
|
||||
- python-version: "3.9"
|
||||
django: "5.2"
|
||||
# django 5.1 does not support 3.9
|
||||
- python-version: "3.9"
|
||||
django: "5.1"
|
||||
# django 5.0 does not support 3.8 and 3.9
|
||||
- python-version: "3.8"
|
||||
django: "5.0"
|
||||
# django 5.0 does not support 3.9
|
||||
- python-version: "3.9"
|
||||
django: "5.0"
|
||||
django: "5.0"
|
||||
# django 4.2 does not support 3.13
|
||||
- python-version: "3.13"
|
||||
django: "4.2"
|
||||
|
||||
services:
|
||||
disque:
|
||||
image: efrecon/disque:1.0-rc1
|
||||
ports:
|
||||
- '7711:7711/tcp'
|
||||
mongodb:
|
||||
image: mongo
|
||||
ports:
|
||||
@@ -74,7 +80,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install poetry==1.8.5
|
||||
poetry add "django==${{ matrix.django }}" --python=${{ matrix.python-version }}
|
||||
poetry add "django~=${{ matrix.django }}a1" --python=${{ matrix.python-version }}
|
||||
poetry install -E testing
|
||||
- name: Run Tests
|
||||
run: |
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [v1.8.0](https://github.com/django-q2/django-q2/tree/v1.7.6) (2025-04-25)
|
||||
|
||||
- Delete deprecated imports and unwrapping https://github.com/django-q2/django-q2/pull/261
|
||||
- Remove disque from CI https://github.com/django-q2/django-q2/pull/270
|
||||
- Avoid creating task run on Scheduler creation https://github.com/django-q2/django-q2/pull/268
|
||||
- Update tested versions, add python 3.13 support and django 5.2 support. Drop python 3.8 support https://github.com/django-q2/django-q2/pull/271
|
||||
- Move timeout function from ORM broker into class to allow easy customization https://github.com/django-q2/django-q2/pull/274
|
||||
|
||||
## [v1.7.6](https://github.com/django-q2/django-q2/tree/v1.7.6) (2025-01-12)
|
||||
|
||||
- Make poetry version fixed in CI https://github.com/django-q2/django-q2/pull/260
|
||||
|
||||
@@ -38,7 +38,10 @@ Requirements
|
||||
- `Django <https://www.djangoproject.com>`__ > = 4.2
|
||||
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__
|
||||
|
||||
Tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 4.2.X and 5.0.X
|
||||
Tested with:
|
||||
|
||||
* Python 3.9 to 3.13.
|
||||
* Django 4.2 to 5.2.
|
||||
|
||||
Brokers
|
||||
~~~~~~~
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
import django
|
||||
|
||||
VERSION = (1, 7, 6)
|
||||
|
||||
if django.VERSION < (3, 2):
|
||||
default_app_config = "django_q.apps.DjangoQConfig"
|
||||
VERSION = (1, 8, 0)
|
||||
|
||||
__all__ = ["conf", "cluster", "models", "tasks"]
|
||||
|
||||
@@ -138,7 +138,7 @@ class ScheduleAdmin(admin.ModelAdmin):
|
||||
url = reverse("admin:django_q_success_change", args=(obj.task_id,))
|
||||
else:
|
||||
url = reverse("admin:django_q_failure_change", args=(obj.task_id,))
|
||||
return format_html(f'<a href="{url}">[{obj.task_name}]</a>')
|
||||
return format_html('<a href="{}">[{}]</a>', url, obj.task_name)
|
||||
return None
|
||||
|
||||
get_last_run.allow_tags = True
|
||||
|
||||
@@ -10,10 +10,6 @@ from django_q.conf import Conf, logger
|
||||
from django_q.models import OrmQ
|
||||
|
||||
|
||||
def _timeout():
|
||||
return timezone.now() + timedelta(seconds=Conf.RETRY)
|
||||
|
||||
|
||||
class ORM(Broker):
|
||||
@staticmethod
|
||||
def get_connection(list_key: str = None):
|
||||
@@ -28,6 +24,9 @@ class ORM(Broker):
|
||||
logger.debug("Broker in an atomic transaction")
|
||||
return OrmQ.objects.using(Conf.ORM)
|
||||
|
||||
def timeout(self, task):
|
||||
return timezone.now() + timedelta(seconds=Conf.RETRY)
|
||||
|
||||
def queue_size(self) -> int:
|
||||
return (
|
||||
self.get_connection()
|
||||
@@ -75,7 +74,7 @@ class ORM(Broker):
|
||||
if (
|
||||
self.get_connection()
|
||||
.filter(id=task.id, lock=task.lock)
|
||||
.update(lock=_timeout())
|
||||
.update(lock=self.timeout(task))
|
||||
):
|
||||
task_list.append((task.pk, task.payload))
|
||||
# else don't process, as another cluster has been faster than us on
|
||||
|
||||
@@ -2,7 +2,6 @@ from datetime import datetime, timedelta
|
||||
from keyword import iskeyword
|
||||
|
||||
# Django
|
||||
from django import get_version
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
@@ -15,7 +14,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# External
|
||||
from picklefield import PickledObjectField
|
||||
from picklefield.fields import dbsafe_decode
|
||||
|
||||
# Local
|
||||
from django_q.conf import croniter
|
||||
@@ -59,7 +57,7 @@ class Task(models.Model):
|
||||
.exclude(success=False)
|
||||
.values_list("result", flat=True)
|
||||
)
|
||||
return decode_results(values)
|
||||
return values
|
||||
|
||||
def group_result(self, failures=False):
|
||||
if self.group:
|
||||
@@ -298,12 +296,18 @@ class Schedule(models.Model):
|
||||
url = reverse("admin:django_q_success_change", args=(task.id,))
|
||||
else:
|
||||
url = reverse("admin:django_q_failure_change", args=(task.id,))
|
||||
return format_html(f'<a href="{url}">[{task.name}]</a>')
|
||||
return format_html('<a href="{}">[{}]</a>', url, task.name)
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return self.func
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk is None and self.schedule_type == self.CRON:
|
||||
self.next_run = self.calculate_next_run()
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
success.boolean = True
|
||||
success.short_description = _("success")
|
||||
last_run.allow_tags = True
|
||||
@@ -356,11 +360,3 @@ class OrmQ(models.Model):
|
||||
app_label = "django_q"
|
||||
verbose_name = _("Queued task")
|
||||
verbose_name_plural = _("Queued tasks")
|
||||
|
||||
|
||||
# Backwards compatibility for Django 1.7
|
||||
def decode_results(values):
|
||||
if get_version().split(".")[1] == "7":
|
||||
# decode values in 1.7
|
||||
return [dbsafe_decode(v) for v in values]
|
||||
return values
|
||||
|
||||
@@ -473,6 +473,47 @@ def test_scheduler_atomic_must_specify_the_database_based_on_router_redirection(
|
||||
mocked_db.atomic.assert_called_with(using="default")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_schedule_save_sets_next_run_for_cron():
|
||||
"""Ensure Schedule.save() sets next_run correctly for CRON schedules."""
|
||||
cron_expression = "0 12 * * *" # Executes dialy at 12pm
|
||||
schedule = Schedule(
|
||||
func="math.sqrt",
|
||||
schedule_type=Schedule.CRON,
|
||||
cron=cron_expression,
|
||||
)
|
||||
|
||||
assert schedule.next_run is not None
|
||||
assert schedule.pk is None
|
||||
|
||||
initial_next_run = schedule.next_run
|
||||
schedule.save()
|
||||
|
||||
# After save, next_run must be recalculated based on the CRON expression
|
||||
assert schedule.next_run > initial_next_run
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_schedule_save_direct_db(broker):
|
||||
"""Ensure Schedule.save() updates next_run correctly when created directly in DB."""
|
||||
cron_expression = "0 12 * * *" # Executes dialy at 12pm
|
||||
|
||||
# Creating schedule directly in database
|
||||
schedule = Schedule.objects.create(
|
||||
func="math.sqrt",
|
||||
schedule_type=Schedule.CRON,
|
||||
cron=cron_expression,
|
||||
)
|
||||
|
||||
# Next run must be defined after execution time
|
||||
assert schedule.next_run is not None
|
||||
assert schedule.next_run > timezone.now()
|
||||
|
||||
scheduler(broker)
|
||||
|
||||
assert broker.queue_size() == 0
|
||||
|
||||
|
||||
def test_localtime():
|
||||
assert not is_naive(localtime())
|
||||
|
||||
|
||||
@@ -73,9 +73,9 @@ author = "Ilan Steemers, Stan Triepels"
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = "1.7"
|
||||
version = "1.8"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = "1.7.6"
|
||||
release = "1.8.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry]
|
||||
name = "django-q2"
|
||||
version = "1.7.6"
|
||||
version = "1.8.0"
|
||||
packages = [
|
||||
{ include = "django_q" },
|
||||
]
|
||||
@@ -30,11 +30,11 @@ classifiers = [
|
||||
'Operating System :: MacOS',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Programming Language :: Python :: 3.13',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Topic :: System :: Distributed Computing',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
@@ -52,7 +52,7 @@ include = [
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8,<4"
|
||||
python = ">=3.9,<4"
|
||||
django = ">=4.2, <6"
|
||||
django-picklefield = "^3.1"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user