Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-django-rq for
openSUSE:Factory checked in at 2023-12-07 19:10:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-rq (Old)
and /work/SRC/openSUSE:Factory/.python-django-rq.new.25432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-rq"
Thu Dec 7 19:10:43 2023 rev:6 rq:1131506 version:2.9.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-django-rq/python-django-rq.changes
2023-06-11 19:58:53.440466033 +0200
+++
/work/SRC/openSUSE:Factory/.python-django-rq.new.25432/python-django-rq.changes
2023-12-07 19:12:28.111825292 +0100
@@ -1,0 +2,11 @@
+Wed Dec 6 22:49:46 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 2.9.0:
+ * Added an option to delete all failed jobs.
+ * You can now specify `SERIALIZER` option while declaring
+ queues in `settings.py`
+ * Updated templates to match newer versions of Django admin's
+ styling.
+ * Don't show `Empty Queue` button on registry pages.
+
+-------------------------------------------------------------------
Old:
----
django-rq-2.8.1.tar.gz
New:
----
django-rq-2.9.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django-rq.spec ++++++
--- /var/tmp/diff_new_pack.GOgxtz/_old 2023-12-07 19:12:28.639844771 +0100
+++ /var/tmp/diff_new_pack.GOgxtz/_new 2023-12-07 19:12:28.639844771 +0100
@@ -16,10 +16,9 @@
#
-%define skip_python2 1
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%{?sle15_python_module_pythons}
Name: python-django-rq
-Version: 2.8.1
+Version: 2.9.0
Release: 0
Summary: Simple app that provides django integration for RQ (Redis
Queue)
License: MIT
@@ -38,6 +37,7 @@
BuildRequires: %{python_module django-redis >= 3.0}
BuildRequires: %{python_module pytest-django}
BuildRequires: %{python_module rq >= 1.14}
+BuildRequires: %{python_module rq-scheduler}
BuildRequires: redis
# /SECTION
%python_subpackages
++++++ django-rq-2.8.1.tar.gz -> django-rq-2.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/.github/workflows/test.yml
new/django-rq-2.9.0/.github/workflows/test.yml
--- old/django-rq-2.8.1/.github/workflows/test.yml 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/.github/workflows/test.yml 2023-11-26
12:34:09.000000000 +0100
@@ -16,7 +16,7 @@
name: Python${{ matrix.python-version }}/Django${{ matrix.django-version }}
strategy:
matrix:
- python-version: ["3.8", "3.9", "3.10"]
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
django-version: ["3.2.16", "4.0.8", "4.1.3", "4.2"]
steps:
@@ -34,7 +34,7 @@
run: |
python -m pip install --upgrade pip
pip install django==${{ matrix.django-version }}
- pip install redis django-redis rq sentry-sdk
+ pip install redis django-redis rq sentry-sdk rq-scheduler
- name: Run Test
run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/CHANGELOG.md
new/django-rq-2.9.0/CHANGELOG.md
--- old/django-rq-2.8.1/CHANGELOG.md 2023-05-14 03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/CHANGELOG.md 2023-11-26 12:34:09.000000000 +0100
@@ -1,27 +1,33 @@
-# Version 2.8.1 (2023-05-14)
+### Version 2.9.0 (2023-11-26)
+* Added an option to delete all failed jobs. Thanks @chromium7!
+* You can now specify `SERIALIZER` option while declaring queues in
`settings.py` Thanks @sophcass!
+* Updated templates to match newer versions of Django admin's styling. Thanks
@nikhilweee!
+* Don't show `Empty Queue` button on registry pages. Thanks @selwin!
+
+### Version 2.8.1 (2023-05-14)
* Added a button to stop currently running jobs. Thanks @gabriels1234!
* Added a failed jobs column to rqstats command. Thanks @dangquangdon!
* Explicitly requires RQ >= 1.14 in `setup.py`. Thanks @selwin!
-# Version 2.8.0 (2023-05-02)
+### Version 2.8.0 (2023-05-02)
* Support for RQ 1.14. Thanks @Cerebro92 and @selwin!
* Show scheduler PID information in admin interface. Thanks @gabriels1234!
* Added `serializer` argument to `rqworker` command. Thanks @gabriels1234!
* Added `USERNAME` and `SENTINEL_KWARGS` support. Thanks @joachimBurket!
-# Version 2.7.0 (2023-02-07)
+### Version 2.7.0 (2023-02-07)
* Able to show multiple execution results for each job (requires RQ v1.12).
Thanks @selwin!
* Various admin interface improvements. Thanks @selwin!
-# Version 2.6.0 (2022-11-05)
+### Version 2.6.0 (2022-11-05)
* Added `--max-jobs` argument to `rqworker` management command. Thanks
@arpit-goel!
* Remove job from `ScheduledJobRegistry` if a scheduled job is enqueued from
admin. Thanks @robertaistleitner!
* Minor code cleanup. Thanks @reybog90!
-# Version 2.5.1 (2021-11-22)
+### Version 2.5.1 (2021-11-22)
* `Redis.from_url` does not accept `ssl_cert_reqs` argument for non SSL Redis
URL. Thanks @barash-asenov!
-# Version 2.5.0 (2021-11-17)
+### Version 2.5.0 (2021-11-17)
* Better integration with Django admin, along with a new `Access admin page`
permission that you can selectively grant to users. Thanks @haakenlid!
* Worker count is now updated everytime you view workers for that specific
queue. Thanks @cgl!
* Add the capability to pass arbitrary Redis client kwargs. Thanks
@juanjgarcia!
@@ -29,21 +35,21 @@
* Add `@never_cache` decorator to all Django-RQ views. Thanks @Cybernisk!
* `SSL_CERT_REQS` argument should also be passed to Redis client even when
Redis URL is used. Thanks @paltman!
-# Version 2.4.1 (2021-03-31)
+### Version 2.4.1 (2021-03-31)
* Added `ssl_cert_reqs` and `username` to queue config. Thanks @jeyang!
-# Version 2.4.0 (2020-11-08)
+### Version 2.4.0 (2020-11-08)
* Various admin interface improvements. Thanks @selwin and @atten!
* Improved Sentry integration. Thanks @hugorodgerbrown and @kichawa!
-# Version 2.3.2 (2020-05-13)
+### Version 2.3.2 (2020-05-13)
* Compatibility with RQ >= 1.4.0 which implements customizable serialization
method. Thanks @selwin!
-# Version 2.3.1 (2020-04-10)
+### Version 2.3.1 (2020-04-10)
* Added `--with-scheduler` argument to `rqworker` management command. Thanks
@stlk!
* Fixed a bug where opening job detail would crash if job.dependency no longer
exists. Thanks @selwin!
-# Version 2.3.0 (2020-02-09)
+### Version 2.3.0 (2020-02-09)
* Support for RQ's new `ScheduledJobRegistry`. Thanks @Yolley!
* Improve performance when displaying pages showing a large number of jobs by
using `Job.fetch_many()`. Thanks @selwin!
* `django-rq` will now automatically cleanup orphaned worker keys in job
registries. Thanks @selwin!
@@ -51,17 +57,17 @@
* `NoSuchJobError`s are now handled properly when requeuing all jobs. Thanks
@thomasmatecki!
* Support for displaying jobs with names containing `$`. Thanks @gowthamk63!
-# Version 2.2.0 (2019-12-08)
+### Version 2.2.0 (2019-12-08)
- Support for Django 3.0. This release also drops support for Django 1.X.
Thanks @hugorodgerbrown!
- `rqworker` management command now properly passes in `--verbosity` to
`Worker`. Thanks @stlk!
- The admin interface can now view jobs with `:` on their IDs. Thanks
@carboncoop!
- Job detail page now shows `job.dependency`. Thanks @selwin!
-# Version 2.1.0 (2019-06-14)
+### Version 2.1.0 (2019-06-14)
- Fixed `Requeue All`
- Django-RQ now automatically runs maintenance tasks when `rq_home` is opened
-# Version 2.0 (2019-04-06)
+### Version 2.0 (2019-04-06)
- Compatibility with RQ 1.0 (Thanks @selwin). Backward incompatible changes
include:
* `FailedQueue` is now replaced by `FailedJobRegistry`
* RQ now uses `sentry-sdk` to send job failures to Sentry.
@@ -69,39 +75,39 @@
- Minor improvements and bug fixes. Thanks @selwin!
-# Version 1.3.1 (2019-03-15)
+### Version 1.3.1 (2019-03-15)
- Run `rqworker` with `--sentry_dsn=""` to disable Sentry integration. Thanks
@Bolayniuss!
- Support for `SSL` Redis kwarg. Thanks @ajknv!
- `rqworker`and `rqscheduler` management commands now uses RQ's built in
`setup_loghandlers` function. Thanks @Paulius-Maruska!
- Remove the use of deprecated `admin_static` template tag. Thanks
@lorenzomorandini!
-# Version 1.3.0 (2018-12-18)
+### Version 1.3.0 (2018-12-18)
- Added support `redis-py` >= 3 and `RQ` >= 0.13. Thanks @selwin!
- Use `Worker.count(queue=queue)` to speed up the process of getting the
number of active workers. Thanks @selwin!
- Added an option to requeue job from the admin interface. Thanks @seiryuz!
- Improve Sentinel support. Thanks @pnuckowski!
-# Version 1.2.0 (2018-07-26)
+### Version 1.2.0 (2018-07-26)
- Supports Python 3.7 by renaming `async` to `is_async`. Thanks @Flimm!
- `UnpickleError` is now handled properly. Thanks @selwin!
- Redis Sentinel support. Thanks @SpeedyCoder!
-# Version 1.1.0
+### Version 1.1.0
- Fixed some admin related bugs. Thanks @seiryuz!
- More Django 2.0 compatibility fixes. Thanks @selwin and @koddr!
- Custom `Job` and `Worker` classes are now supported. Thanks @skirsdeda!
- `SENTRY_DSN` value in `settings.py` will now be used by default. Thanks
@inetss!
-# 1.0.1
+### 1.0.1
- Django 2.0 compatibility fixes.
- Minor bug fixes
-# 1.0.0
+### 1.0.0
- You can now view worker information
- Detailed worker statistics such as failed/completed job count are
@@ -116,13 +122,13 @@
- Improved performance when requeueing all jobs. Thanks
@therefromhere!
-# 0.9.6
+### 0.9.6
- More Django 1.10 compatibility fixes. Thanks @dmwyatt!
- Improves performance when dealing with a large number of workers.
Thanks @lucastamoios!
-# 0.9.5
+### 0.9.5
- Fixed view paging for registry-based job lists. Thanks @smaccona!
- Fixed an issue where multiple failed queues may appear for the same
@@ -132,7 +138,7 @@
- Fixed an argument parsing bug `rqworker` management command. Thanks
@hendi!
-# 0.9.3
+### 0.9.3
- Added a `--pid` option to `rqscheduler` management command. Thanks
@vindemasi!
@@ -147,13 +153,13 @@
@randomguy91!
- Other minor fixes by @jeromer and @sbussetti.
-# 0.9.2
+### 0.9.2
- Support for Django 1.10. Thanks @jtburchfield!
- Added `--queue-class` option to `rqworker` management command.
Thanks @Krukov!
-# 0.9.1
+### 0.9.1
- Added `-i` and `--queue` options to rqscheduler management command.
Thanks @mbodock and @sbussetti!
@@ -165,7 +171,7 @@
`RQ_EXCEPTION_HANDLERS` in `settings.py`. Thanks @sbussetti!
- Queues in django-admin are now sorted by name. Thanks @pnuckowski!
-# 0.9.0
+### 0.9.0
- Support for Django 1.9. Thanks @aaugustin and @viaregio!
- `rqworker` management command now accepts `--worker-ttl` argument.
@@ -174,7 +180,7 @@
`settings.py`. Thanks @xuhcc!
- `django-rq` now requires RQ >= 0.5.5
-# 0.8.0
+### 0.8.0
- You can now view deferred, finished and currently active jobs from
admin interface.
@@ -182,7 +188,7 @@
- Requires RQ >= 0.5.
- You can now use StrictRedis with Django-RQ. Thanks @wastrachan!
-# 0.7.0
+### 0.7.0
- Added `rqenqueue` management command for easy scheduling of tasks
(e.g via cron
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/README.rst
new/django-rq-2.9.0/README.rst
--- old/django-rq-2.8.1/README.rst 2023-05-14 03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/README.rst 2023-11-26 12:34:09.000000000 +0100
@@ -67,7 +67,7 @@
'SOCKET_TIMEOUT': 0.3,
'CONNECTION_KWARGS': { # Eventual additional Redis connection
arguments
'ssl': True
- }
+ },
'SENTINEL_KWARGS': { # Eventual Sentinel connection arguments
# If Sentinel also has auth, username/password can be passed
here
'username': 'sentinel-user',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/__init__.py
new/django-rq-2.9.0/django_rq/__init__.py
--- old/django-rq-2.8.1/django_rq/__init__.py 2023-05-14 03:31:42.000000000
+0200
+++ new/django-rq-2.9.0/django_rq/__init__.py 2023-11-26 12:34:09.000000000
+0100
@@ -1,4 +1,4 @@
-VERSION = (2, 8, 1)
+VERSION = (2, 9, 0)
from .decorators import job
from .queues import enqueue, get_connection, get_queue, get_scheduler
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/queues.py
new/django-rq-2.9.0/django_rq/queues.py
--- old/django-rq-2.8.1/django_rq/queues.py 2023-05-14 03:31:42.000000000
+0200
+++ new/django-rq-2.9.0/django_rq/queues.py 2023-11-26 12:34:09.000000000
+0100
@@ -155,6 +155,7 @@
connection=None,
queue_class=None,
job_class=None,
+ serializer=None,
**kwargs
):
"""
@@ -176,6 +177,8 @@
default_timeout = QUEUES[name].get('DEFAULT_TIMEOUT')
if connection is None:
connection = get_connection(name)
+ if serializer is None:
+ serializer = QUEUES[name].get('SERIALIZER')
queue_class = get_queue_class(QUEUES[name], queue_class)
return queue_class(
name,
@@ -184,6 +187,7 @@
is_async=is_async,
job_class=job_class,
autocommit=autocommit,
+ serializer=serializer,
**kwargs
)
@@ -196,9 +200,21 @@
config = QUEUES_LIST[int(index)]
return get_queue_class(config)(
- config['name'],
connection=get_redis_connection(config['connection_config']),
is_async=config.get('ASYNC', True)
+ config['name'],
+ connection=get_redis_connection(config['connection_config']),
+ is_async=config.get('ASYNC', True),
+ serializer=config['connection_config'].get('SERIALIZER')
)
+def get_scheduler_by_index(index):
+ """
+ Returns an rq-scheduler Scheduler using parameters defined in
``QUEUES_LIST``
+ """
+ from .settings import QUEUES_LIST
+
+ config = QUEUES_LIST[int(index)]
+ return get_scheduler(config['name'])
+
def filter_connection_params(queue_params):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/settings.py
new/django-rq-2.9.0/django_rq/settings.py
--- old/django-rq-2.8.1/django_rq/settings.py 2023-05-14 03:31:42.000000000
+0200
+++ new/django-rq-2.9.0/django_rq/settings.py 2023-11-26 12:34:09.000000000
+0100
@@ -15,8 +15,10 @@
# All queues in list format so we can get them by index, includes failed queues
QUEUES_LIST = []
+QUEUES_MAP = {}
for key, value in sorted(QUEUES.items(), key=itemgetter(0)):
QUEUES_LIST.append({'name': key, 'connection_config': value})
+ QUEUES_MAP[key] = len(QUEUES_LIST) - 1
# Get exception handlers
EXCEPTION_HANDLERS = getattr(settings, 'RQ_EXCEPTION_HANDLERS', [])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-rq-2.8.1/django_rq/templates/django_rq/clear_failed_queue.html
new/django-rq-2.9.0/django_rq/templates/django_rq/clear_failed_queue.html
--- old/django-rq-2.8.1/django_rq/templates/django_rq/clear_failed_queue.html
1970-01-01 01:00:00.000000000 +0100
+++ new/django-rq-2.9.0/django_rq/templates/django_rq/clear_failed_queue.html
2023-11-26 12:34:09.000000000 +0100
@@ -0,0 +1,43 @@
+{% extends "admin/base_site.html" %}
+
+{% load static %}
+
+{% block extrastyle %}
+ {{ block.super }}
+ <style>
+ .data {
+ display: inline-block;
+ float: left;
+ width: 80%;
+ }
+ </style>
+ <link href="{% static 'admin/css/forms.css' %}" type="text/css"
rel="stylesheet">
+{% endblock %}
+
+{% block breadcrumbs %}
+ <div class="breadcrumbs">
+ <a href="{% url 'admin:index' %}">Home</a> ›
+ <a href="{% url 'rq_home' %}">Django RQ</a> ›
+ <a href = "{% url 'rq_jobs' queue_index %}">{{ queue.name }}</a>
›
+ Delete All
+ </div>
+{% endblock %}
+
+{% block content_title %}<h1>Are you sure?</h1>{% endblock %}
+
+{% block content %}
+
+<div id="content-main">
+ <p>
+ Are you sure you want to delete {{ total_jobs }} failed job{{
total_jobs|pluralize }} in the <a href = "{% url 'rq_jobs' queue_index %}">{{
queue.name }}</a> queue?
+ This action can not be undone.
+ </p>
+ <form action="" method="post">
+ {% csrf_token %}
+ <div>
+ <input type="submit" value="Yes, I'm sure" />
+ </div>
+ </form>
+</div>
+
+{% endblock %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-rq-2.8.1/django_rq/templates/django_rq/job_detail.html
new/django-rq-2.9.0/django_rq/templates/django_rq/job_detail.html
--- old/django-rq-2.8.1/django_rq/templates/django_rq/job_detail.html
2023-05-14 03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/templates/django_rq/job_detail.html
2023-11-26 12:34:09.000000000 +0100
@@ -139,7 +139,7 @@
{% if data_is_valid %}
{% if job.kwargs %}
<ul>
- {% for key, value in job.kwargs.items %}
+ {% for key, value in job.kwargs|items %}
<li>{{ key }}: {{ value|force_escape
}}</li>
{% endfor %}
</ul>
@@ -171,7 +171,7 @@
</div>
</div>
{% endif %}
-
+
{% if job.legacy_result %}
<div class="form-row">
<div>
@@ -182,10 +182,10 @@
{% endif %}
</fieldset>
-
+
<div class="submit-row">
- <p class="deletelink-box"><a href="delete/"
class="deletelink">Delete</a></p>
+ <div class="deletelink-box"><a href="delete/"
class="deletelink">Delete</a></div>
{% if job.is_started %}
<form method = 'POST' action = "{% url 'rq_stop_job' queue_index
job.id %}">
{% csrf_token %}
@@ -212,37 +212,37 @@
{% for result in job.results %}
<h2>Result {{ result.id }}</h2>
<div class="inline-related">
-
- <fieldset class="module aligned ">
- <div class="form-row field-choice_text">
+
+ <fieldset class="module aligned ">
+ <div class="form-row field-choice_text">
<div>
<label>Type:</label>
<div class="readonly">{{ result.type.name }}</div>
- </div>
+ </div>
</div>
-
+
<div class="form-row field-votes">
<div>
<label>Created at: {{ result.Type }}</label>
<div class="readonly">{{
result.created_at|to_localtime|date:"Y-m-d, H:i:s" }}</div>
- </div>
+ </div>
</div>
{% if result.type.value == 1 %}
<div class="form-row field-votes">
<div>
<label>Return value:</label>
<div><pre>{{ result.return_value }}</pre></div>
- </div>
+ </div>
</div>
{% elif result.type.value == 2 %}
<div class="form-row field-votes">
<div>
<label>Exception:</label>
<div><pre>{{ result.exc_string }}</pre></div>
- </div>
+ </div>
</div>
{% endif %}
-
+
</fieldset>
</div>
{% endfor %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-rq-2.8.1/django_rq/templates/django_rq/jobs.html
new/django-rq-2.9.0/django_rq/templates/django_rq/jobs.html
--- old/django-rq-2.8.1/django_rq/templates/django_rq/jobs.html 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/templates/django_rq/jobs.html 2023-11-26
12:34:09.000000000 +0100
@@ -28,7 +28,7 @@
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">Home</a> ›
<a href="{% url 'rq_home' %}">Django RQ</a> ›
- <a href = "{% url 'rq_jobs' queue_index %}">{{ queue.name }}</a>
+ <a href="{% url 'rq_jobs' queue_index %}">{{ queue.name }}</a>
</div>
{% endblock %}
@@ -40,105 +40,130 @@
<ul class="object-tools">
{% if job_status == 'Failed' %}
<li><a href="{% url 'rq_requeue_all' queue_index %}"
class="requeuelink">Requeue All</a></li>
+ <li><a href="{% url 'rq_delete_failed_jobs' queue_index %}"
class="requeuelink">Delete All</a></li>
+ {% elif job_status == 'Queued' %}
+ <li><a href="{% url 'rq_clear' queue_index %}"
class="deletelink">Empty Queue</a></li>
{% endif %}
- <li><a href="{% url 'rq_clear' queue_index %}"
class="deletelink">Empty Queue</a></li>
</ul>
<div class="module" id="changelist">
- <form id="changelist-form" action="{% url 'rq_confirm_action'
queue_index %}" method="post">
- {% csrf_token %}
- <div class="actions">
- <label>Actions:
- <select name="action">
- <option value="" selected="selected">---------</option>
- <option value="delete">Delete</option>
- {% if job_status == 'Failed' %}
- <option value="requeue">Requeue</option>
- {% endif %}
- {% if job_status == 'Started' %}
- <option value="stop">Stop</option>
- {% endif %}
- </select>
- </label>
- <button type="submit" class="button" title="Execute selected
action" name="index" value="0">Go</button>
- </div>
- <div class="results">
- <table id="result_list">
- <thead>
- <tr>
- <th scope="col" class="action-checkbox-column">
- <div class="text">
- <span><input type="checkbox"
id="action-toggle" style="display: inline-block;"></span>
- </div>
- <div class="clear"></div>
- </th>
- <th><div class = 'text'><span>ID</span></div></th>
- <th><div class =
'text'><span>Created</span></div></th>
- {% if job_status == 'Scheduled' %}
- <th><div class =
'text'><span>Scheduled</span></div></th>
+ <div class="changelist-form-container">
+ <form id="changelist-form" action="{% url 'rq_confirm_action'
queue_index %}" method="post">
+ {% csrf_token %}
+ <div class="actions">
+ <label>Actions:
+ <select name="action" required>
+ <option value="" selected>---------</option>
+ <option value="delete">Delete</option>
+ {% if job_status == 'Failed' %}
+ <option value="requeue">Requeue</option>
{% endif %}
- <th><div class =
'text'><span>Enqueued</span></div></th>
- <th><div class =
'text'><span>Ended</span></div></th>
- <th><div class =
'text'><span>Status</span></div></th>
- <th><div class =
'text'><span>Callable</span></div></th>
- {% block extra_columns %}
- {% endblock extra_columns %}
- </tr>
- </thead>
- <tbody>
- {% for job in jobs %}
- <tr class = "{% cycle 'row1' 'row2' %}">
- <td class="action-checkbox">
- <input class="action-select"
name="_selected_action" type="checkbox" value="{{ job.id }}">
- </td>
- <th>
- <a href = "{% url 'rq_job_detail'
queue_index job.id %}">
- {{ job.id }}
- </a>
- </th>
- <td>
- {% if job.created_at %}
- {{
job.created_at|to_localtime|date:"Y-m-d, H:i:s" }}
- {% endif %}
- </td>
+ {% if job_status == 'Started' %}
+ <option value="stop">Stop</option>
+ {% endif %}
+ </select>
+ </label>
+ <button type="submit" class="button" title="Execute
selected action" name="index" value="0">Go</button>
+ </div>
+ <div class="results">
+ <table id="result_list">
+ <thead>
+ <tr>
+ <th scope="col" class="action-checkbox-column">
+ <div class="text">
+ <span><input type="checkbox"
id="action-toggle"></span>
+ </div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class='text'><span>ID</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div
class='text'><span>Created</span></div>
+ <div class="clear"></div>
+ </th>
{% if job_status == 'Scheduled' %}
+ <th scope="col" class="sortable">
+ <div
class='text'><span>Scheduled</span></div>
+ <div class="clear"></div>
+ </th>
+ {% endif %}
+ <th scope="col" class="sortable">
+ <div
class='text'><span>Enqueued</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class='text'><span>Ended</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class='text'><span>Status</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div
class='text'><span>Callable</span></div>
+ <div class="clear"></div>
+ </th>
+ {% block extra_columns %}
+ {% endblock extra_columns %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for job in jobs %}
+ <tr>
+ <td class="action-checkbox">
+ <input class="action-select"
name="_selected_action" type="checkbox" value="{{ job.id }}">
+ </td>
+ <th>
+ <a href="{% url 'rq_job_detail'
queue_index job.id %}">
+ {{ job.id }}
+ </a>
+ </th>
+ <td>
+ {% if job.created_at %}
+ {{
job.created_at|to_localtime|date:"Y-m-d, H:i:s" }}
+ {% endif %}
+ </td>
+ {% if job_status == 'Scheduled' %}
<td>
{% if job.scheduled_at %}
{{
job.scheduled_at|to_localtime|date:"Y-m-d, H:i:s" }}
{% endif %}
</td>
- {% endif %}
- <td>
- {% if job.enqueued_at %}
- {{
job.enqueued_at|to_localtime|date:"Y-m-d, H:i:s" }}
{% endif %}
- </td>
- <td>
- {% if job.ended_at %}
- {{
job.ended_at|to_localtime|date:"Y-m-d, H:i:s" }}
- {% endif %}
- </td>
- <td>{{ job.get_status }}</td>
- <td>{{ job|show_func_name }}</td>
- {% block extra_columns_values %}
- {% endblock extra_columns_values %}
- </tr>
- {% endfor %}
- </tbody>
- </table>
- </div>
- <p class="paginator">
- {% for p in page_range %}
- {% if p == page %}
- <span class="this-page">{{ p }}</span>
- {% elif forloop.last %}
- <a href="?page={{ p }}" class="end">{{ p }}</a>
- {% else %}
- <a href="?page={{ p }}">{{ p }}</a>
- {% endif %}
- {% endfor %}
- {{ num_jobs }} jobs
- </p>
- </form>
+ <td>
+ {% if job.enqueued_at %}
+ {{
job.enqueued_at|to_localtime|date:"Y-m-d, H:i:s" }}
+ {% endif %}
+ </td>
+ <td>
+ {% if job.ended_at %}
+ {{
job.ended_at|to_localtime|date:"Y-m-d, H:i:s" }}
+ {% endif %}
+ </td>
+ <td>{{ job.get_status }}</td>
+ <td>{{ job|show_func_name }}</td>
+ {% block extra_columns_values %}
+ {% endblock extra_columns_values %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ <p class="paginator">
+ {% for p in page_range %}
+ {% if p == page %}
+ <span class="this-page">{{ p }}</span>
+ {% elif forloop.last %}
+ <a href="?page={{ p }}" class="end">{{ p }}</a>
+ {% else %}
+ <a href="?page={{ p }}">{{ p }}</a>
+ {% endif %}
+ {% endfor %}
+ {{ num_jobs }} jobs
+ </p>
+ </form>
+ </div>
</div>
</div>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-rq-2.8.1/django_rq/templates/django_rq/scheduler.html
new/django-rq-2.9.0/django_rq/templates/django_rq/scheduler.html
--- old/django-rq-2.8.1/django_rq/templates/django_rq/scheduler.html
1970-01-01 01:00:00.000000000 +0100
+++ new/django-rq-2.9.0/django_rq/templates/django_rq/scheduler.html
2023-11-26 12:34:09.000000000 +0100
@@ -0,0 +1,119 @@
+{% extends "admin/base_site.html" %}
+
+{% load static jquery_path django_rq %}
+
+{% block title %}Scheduler Jobs in {{ scheduler.name }} {{ block.super }}{%
endblock %}
+
+{% block extrastyle %}
+ {{ block.super }}
+ <link rel="stylesheet" type="text/css" href="{% static
"admin/css/changelists.css" %}">
+{% endblock %}
+
+{% block extrahead %}
+ {{ block.super }}
+ <script type="text/javascript" src="{% get_jquery_path as jquery_path %}{%
static jquery_path %}"></script>
+ <script type="text/javascript" src="{% static "admin/js/jquery.init.js"
%}"></script>
+ <script type="text/javascript" src="{% static "admin/js/actions.js"
%}"></script>
+ <script type="text/javascript">
+ (function($) {
+ $(document).ready(function($) {
+ $("tr input.action-select").actions();
+ });
+ })(django.jQuery);
+ </script>
+{% endblock %}
+
+
+{% block breadcrumbs %}
+ <div class="breadcrumbs">
+ <a href="{% url 'admin:index' %}">Home</a> ›
+ <a href="{% url 'rq_home' %}">Django RQ</a> ›
+ </div>
+{% endblock %}
+
+{% block content_title %}<h1>Scheduler Managed Jobs</h1>{% endblock %}
+
+{% block content %}
+
+<div id="content-main">
+ <ul class="object-tools">
+ </ul>
+ <div class="module" id="changelist">
+ <form id="changelist-form" action="" method="post">
+ {% csrf_token %}
+ <div class="actions">
+ <label>Actions:
+ <select name="action">
+ <option value="" selected="selected">---------</option>
+ <option value="delete">Delete</option>
+ {% if job_status == 'Failed' %}
+ <option value="requeue">Requeue</option>
+ {% endif %}
+ </select>
+ </label>
+ <button type="submit" class="button" title="Execute selected
action" name="index" value="0">Go</button>
+ </div>
+ <div class="results">
+ <table id="result_list">
+ <thead>
+ <tr>
+ <th scope="col" class="action-checkbox-column">
+ <div class="text">
+ <span><input type="checkbox"
id="action-toggle" style="display: inline-block;"></span>
+ </div>
+ <div class="clear"></div>
+ </th>
+ <th><div class = 'text'><span>ID</span></div></th>
+ <th><div class =
'text'><span>Schedule</span></div></th>
+ <th><div class = 'text'><span>Next
Run</span></div></th>
+ <th><div class = 'text'><span>Last
Ended</span></div></th>
+ <th><div class = 'text'><span>Last
Status</span></div></th>
+ <th><div class =
'text'><span>Callable</span></div></th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for job in jobs %}
+ <tr class = "{% cycle 'row1' 'row2' %}">
+ <td class="action-checkbox">
+ <input class="action-select"
name="_selected_action" type="checkbox" value="{{ job.id }}">
+ </td>
+ <td>
+ <a href = "{% url 'rq_job_detail'
job.queue_index job.id %}">
+ {{ job.id }}
+ </a>
+ </td>
+ <td>{{ job.schedule }}</td>
+ <td>
+ {% if job.next_run %}
+ {{
job.next_run|to_localtime|date:"Y-m-d, H:i:s" }}
+ {% endif %}
+ </td>
+ <td>
+ {% if job.ended_at %}
+ {{
job.ended_at|to_localtime|date:"Y-m-d, H:i:s" }}
+ {% endif %}
+ </td>
+ <td>{{ job.get_status }}</td>
+ <td>{{ job|show_func_name }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ <p class="paginator">
+ {% for p in page_range %}
+ {% if p == page %}
+ <span class="this-page">{{ p }}</span>
+ {% elif forloop.last %}
+ <a href="?page={{ p }}" class="end">{{ p }}</a>
+ {% else %}
+ <a href="?page={{ p }}">{{ p }}</a>
+ {% endif %}
+ {% endfor %}
+ {{ num_jobs }} jobs
+ </p>
+ </form>
+ </div>
+</div>
+
+{% endblock %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-rq-2.8.1/django_rq/templates/django_rq/stats.html
new/django-rq-2.9.0/django_rq/templates/django_rq/stats.html
--- old/django-rq-2.8.1/django_rq/templates/django_rq/stats.html
2023-05-14 03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/templates/django_rq/stats.html
2023-11-26 12:34:09.000000000 +0100
@@ -1,12 +1,18 @@
{% extends "admin/base_site.html" %}
-
-{% block title %}Queues {{ block.super }}{% endblock %}
+{% load static %}
{% block extrastyle %}
{{ block.super }}
- <style>table {width: 100%;}</style>
+ <link rel="stylesheet" type="text/css" href="{% static
"admin/css/changelists.css" %}">
+ <style>
+ #changelist table thead th:first-child {
+ width: inherit
+ }
+ </style>
{% endblock %}
+{% block title %}Queues{% endblock %}
+
{% block content_title %}<h1>Queues</h1>{% endblock %}
{% block breadcrumbs %}
@@ -19,84 +25,148 @@
{% block content %}
<div id="content-main">
-
- <div class="module">
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Queued Jobs</th>
- <th>Oldest Queued Job</th>
- <th>Active Jobs</th>
- <th>Deferred Jobs</th>
- <th>Finished Jobs</th>
- <th>Failed Jobs</th>
- <th>Scheduled Jobs</th>
- <th>Workers</th>
- <th>Host</th>
- <th>Port</th>
- <th>DB</th>
- {% if queue.scheduler_pid is not False %}
- <th>Scheduler PID</th>
- {% endif%}
- </tr>
- </thead>
- <tbody>
- {% for queue in queues %}
- <tr class = "{% cycle 'row1' 'row2' %}">
- <th>
- <a href = "{% url 'rq_jobs' queue.index %}">
- {{ queue.name }}
- </a>
- </th>
- <td>
- <a href = "{% url 'rq_jobs' queue.index %}">
- {{ queue.jobs }}
- </a>
- </td>
- <td>{{ queue.oldest_job_timestamp }}</td>
- <th>
- <a href = "{% url 'rq_started_jobs' queue.index
%}">
- {{ queue.started_jobs }}
- </a>
- </th>
- <th>
- <a href = "{% url 'rq_deferred_jobs' queue.index
%}">
- {{ queue.deferred_jobs }}
- </a>
- </th>
- <th>
- <a href = "{% url 'rq_finished_jobs' queue.index
%}">
- {{ queue.finished_jobs }}
- </a>
- </th>
- <th>
- <a href = "{% url 'rq_failed_jobs' queue.index
%}">
- {{ queue.failed_jobs }}
- </a>
- </th>
- <th>
- <a href = "{% url 'rq_scheduled_jobs' queue.index
%}">
- {{ queue.scheduled_jobs }}
- </a>
- </th>
- <th><a href = "{% url 'rq_workers' queue.index %}">
- {{ queue.workers }}
- </a>
- </th>
- <td>{{ queue.connection_kwargs.host }}</td>
- <td>{{ queue.connection_kwargs.port }}</td>
- <td>{{ queue.connection_kwargs.db }}</td>
- {% if queue.scheduler_pid is not False %}
- <td>{{ queue.scheduler_pid|default_if_none:"Inactive"
}}</td>
- {% endif %}
- </tr>
- {% endfor %}
- </tbody>
- </table>
- <br />
- <a href="{% url 'rq_home_json' %}">View as JSON</a>
+ <div class="module" id="changelist">
+ <div class="changelist-form-container">
+ <form id="changelist-form" method="post" {% if cl.formset and
cl.formset.is_multipart %}
+ enctype="multipart/form-data" {% endif %} novalidate>{%
csrf_token %}
+ <div class="results">
+ <table id="results_list">
+ <thead>
+ <tr>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Name</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Queued
Jobs</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Oldest Queued
Job</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Active
Jobs</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Deferred
Jobs</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Finished
Jobs</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Failed
Jobs</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Scheduled
Jobs</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div
class="text"><span>Workers</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Host</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>Port</span></div>
+ <div class="clear"></div>
+ </th>
+ <th scope="col" class="sortable">
+ <div class="text"><span>DB</span></div>
+ <div class="clear"></div>
+ </th>
+ {% if queue.scheduler_pid is not False %}
+ <th scope="col" class="sortable">
+ <div class="text"><span>Scheduler
PID</span></div>
+ <div class="clear"></div>
+ </th>
+ {% endif%}
+ </tr>
+ </thead>
+ <tbody>
+ {% for queue in queues %}
+ <tr class="{% cycle 'row1' 'row2' %}">
+ <th>
+ <a href="{% url 'rq_jobs' queue.index %}">
+ {{ queue.name }}
+ </a>
+ </th>
+ <th>
+ <a href="{% url 'rq_jobs' queue.index %}">
+ {{ queue.jobs }}
+ </a>
+ </th>
+ <td>{{ queue.oldest_job_timestamp }}</td>
+ <th>
+ <a href="{% url 'rq_started_jobs'
queue.index %}">
+ {{ queue.started_jobs }}
+ </a>
+ </th>
+ <th>
+ <a href="{% url 'rq_deferred_jobs'
queue.index %}">
+ {{ queue.deferred_jobs }}
+ </a>
+ </th>
+ <th>
+ <a href="{% url 'rq_finished_jobs'
queue.index %}">
+ {{ queue.finished_jobs }}
+ </a>
+ </th>
+ <th>
+ <a href="{% url 'rq_failed_jobs'
queue.index %}">
+ {{ queue.failed_jobs }}
+ </a>
+ </th>
+ <th>
+ <a href="{% url 'rq_scheduled_jobs'
queue.index %}">
+ {{ queue.scheduled_jobs }}
+ </a>
+ </th>
+ <th><a href="{% url 'rq_workers' queue.index
%}">
+ {{ queue.workers }}
+ </a>
+ </th>
+ <td>{{ queue.connection_kwargs.host }}</td>
+ <td>{{ queue.connection_kwargs.port }}</td>
+ <td>{{ queue.connection_kwargs.db }}</td>
+ {% if queue.scheduler_pid is not False %}
+ <td>{{
queue.scheduler_pid|default_if_none:"Inactive" }}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ <p class="paginator">
+ <a href="{% url 'rq_home_json' %}" class="showall">View as
JSON</a>
+ </p>
+ </form>
+ </div>
</div>
+
+ {% if schedulers %}
+ <h2>RQ Scheduler</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Redis Connection</th>
+ <th>Recurring Jobs</th>
+ </tr>
+ </thead>
+ {% for connection, scheduler in schedulers.items %}
+ <tr class="{% cycle 'row1' 'row2' %}">
+ <td><a href="{% url 'rq_scheduler_jobs' scheduler.index %}">{{
connection }}</a></td>
+ <td><a href="{% url 'rq_scheduler_jobs' scheduler.index %}">{{
scheduler.count }}</a></td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% endif %}
</div>
{% endblock %}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/templatetags/django_rq.py
new/django-rq-2.9.0/django_rq/templatetags/django_rq.py
--- old/django-rq-2.8.1/django_rq/templatetags/django_rq.py 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/templatetags/django_rq.py 2023-11-26
12:34:09.000000000 +0100
@@ -8,7 +8,7 @@
@register.filter
def to_localtime(time):
- '''Converts naive datetime to localtime based on settings'''
+ """Converts naive datetime to localtime based on settings"""
utc_time = time.replace(tzinfo=timezone.utc)
to_zone = timezone.get_default_timezone()
@@ -17,7 +17,7 @@
@register.filter
def show_func_name(job):
- '''Shows job.func_name and handles errors during deserialization'''
+ """Shows job.func_name and handles errors during deserialization"""
try:
return job.func_name
except Exception as e:
@@ -27,3 +27,12 @@
@register.filter
def force_escape(text):
return escape(text)
+
+
[email protected]
+def items(dictionary):
+ """
+ Explicitly calls `dictionary.items` function
+ to avoid django from accessing the key `items` if any.
+ """
+ return dictionary.items()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/tests/settings.py
new/django-rq-2.9.0/django_rq/tests/settings.py
--- old/django-rq-2.8.1/django_rq/tests/settings.py 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/tests/settings.py 2023-11-26
12:34:09.000000000 +0100
@@ -187,6 +187,12 @@
'DB': 0,
'DEFAULT_TIMEOUT': 400,
},
+ 'test_serializer': {
+ 'HOST': REDIS_HOST,
+ 'PORT': 6379,
+ 'DB': 0,
+ 'SERIALIZER': 'rq.serializers.JSONSerializer',
+ },
}
RQ = {
'AUTOCOMMIT': False,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/tests/test_views.py
new/django-rq-2.9.0/django_rq/tests/test_views.py
--- old/django-rq-2.8.1/django_rq/tests/test_views.py 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/tests/test_views.py 2023-11-26
12:34:09.000000000 +0100
@@ -1,5 +1,5 @@
import uuid
-from datetime import datetime
+from datetime import datetime, timedelta, timezone
from unittest.mock import PropertyMock, patch
@@ -17,6 +17,7 @@
)
from django_rq import get_queue
+from django_rq.queues import get_scheduler
from django_rq.workers import get_worker
from .fixtures import access_self, failing_job
@@ -395,3 +396,55 @@
for job_id in job_ids:
self.assertIn(job_id, canceled_job_registry)
+
+ def test_scheduler_jobs(self):
+ # Override testing RQ_QUEUES
+ queues = [
+ {
+ "connection_config": {
+ "DB": 0,
+ "HOST": "localhost",
+ "PORT": 6379,
+ },
+ "name": "default",
+ }
+ ]
+ with patch(
+ "django_rq.utils.QUEUES_LIST",
+ new_callable=PropertyMock(return_value=queues),
+ ):
+ scheduler = get_scheduler("default")
+ scheduler_index = get_queue_index("default")
+
+ # Enqueue some jobs
+ cron_job = scheduler.cron("10 9 * * *", func=access_self,
id="cron-job")
+ forever_job = scheduler.schedule(
+ scheduled_time=datetime.now() + timedelta(minutes=10),
+ interval=600,
+ func=access_self,
+ id="forever-repeat",
+ )
+ repeat_job = scheduler.schedule(
+ scheduled_time=datetime.now() + timedelta(minutes=30),
+ repeat=30,
+ func=access_self,
+ interval=600,
+ id="thirty-repeat",
+ )
+
+ response = self.client.get(
+ reverse("rq_scheduler_jobs", args=[scheduler_index])
+ )
+ self.assertEqual(response.context["num_jobs"], 3)
+ context_jobs = {job.id: job for job in response.context["jobs"]}
+ self.assertEqual(context_jobs["cron-job"].schedule, "cron: '10 9 *
* *'")
+ self.assertEqual(context_jobs["forever-repeat"].schedule,
"interval: 600")
+ self.assertEqual(
+ context_jobs["thirty-repeat"].schedule, "interval: 600 repeat:
30"
+ )
+
+ index_response = self.client.get(reverse("rq_home"))
+ self.assertEqual(
+ index_response.context["schedulers"],
+ {"localhost:6379/1": {"count": 3, "index": 0}},
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/tests/tests.py
new/django-rq-2.9.0/django_rq/tests/tests.py
--- old/django-rq-2.8.1/django_rq/tests/tests.py 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/django_rq/tests/tests.py 2023-11-26
12:34:09.000000000 +0100
@@ -13,6 +13,7 @@
from redis.exceptions import ConnectionError
from rq import get_current_job, Queue
+import rq
from rq.exceptions import NoSuchJobError
from rq.job import Job
from rq.registry import FinishedJobRegistry, ScheduledJobRegistry
@@ -457,6 +458,14 @@
queue = get_queue('test1')
self.assertEqual(queue._default_timeout, 400)
+ def test_get_queue_serializer(self):
+ """
+ Test that the correct serializer is set on the queue.
+ """
+ queue = get_queue('test_serializer')
+ self.assertEqual(queue.name, 'test_serializer')
+ self.assertEqual(queue.serializer, rq.serializers.JSONSerializer)
+
@override_settings(RQ={'AUTOCOMMIT': True})
class DecoratorTest(TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/urls.py
new/django-rq-2.9.0/django_rq/urls.py
--- old/django-rq-2.8.1/django_rq/urls.py 2023-05-14 03:31:42.000000000
+0200
+++ new/django-rq-2.9.0/django_rq/urls.py 2023-11-26 12:34:09.000000000
+0100
@@ -10,6 +10,7 @@
re_path(r'^workers/(?P<queue_index>[\d]+)/(?P<key>[-\w\.\:\$]+)/$',
views.worker_details, name='rq_worker_details'),
re_path(r'^queues/(?P<queue_index>[\d]+)/finished/$', views.finished_jobs,
name='rq_finished_jobs'),
re_path(r'^queues/(?P<queue_index>[\d]+)/failed/$', views.failed_jobs,
name='rq_failed_jobs'),
+ re_path(r'^queues/(?P<queue_index>[\d]+)/failed/clear/$',
views.delete_failed_jobs, name='rq_delete_failed_jobs'),
re_path(r'^queues/(?P<queue_index>[\d]+)/scheduled/$',
views.scheduled_jobs, name='rq_scheduled_jobs'),
re_path(r'^queues/(?P<queue_index>[\d]+)/started/$', views.started_jobs,
name='rq_started_jobs'),
re_path(r'^queues/(?P<queue_index>[\d]+)/deferred/$', views.deferred_jobs,
name='rq_deferred_jobs'),
@@ -32,4 +33,5 @@
re_path(
r'^queues/(?P<queue_index>[\d]+)/(?P<job_id>[^/]+)/stop/$',
views.stop_job, name='rq_stop_job'
),
+ re_path(r'^schedulers/(?P<scheduler_index>[\d]+)/$', views.scheduler_jobs,
name='rq_scheduler_jobs'),
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/utils.py
new/django-rq-2.9.0/django_rq/utils.py
--- old/django-rq-2.8.1/django_rq/utils.py 2023-05-14 03:31:42.000000000
+0200
+++ new/django-rq-2.9.0/django_rq/utils.py 2023-11-26 12:34:09.000000000
+0100
@@ -1,3 +1,5 @@
+from django.core.exceptions import ImproperlyConfigured
+from rq.command import send_stop_job_command
from rq.job import Job
from rq.registry import (
DeferredJobRegistry,
@@ -7,15 +9,13 @@
StartedJobRegistry,
clean_registries,
)
-from rq.command import send_stop_job_command
from rq.worker import Worker
from rq.worker_registration import clean_worker_registry
-
from .queues import get_connection, get_queue_by_index, get_scheduler
from .settings import QUEUES_LIST
from .templatetags.django_rq import to_localtime
-from django.core.exceptions import ImproperlyConfigured
+
def get_scheduler_pid(queue):
'''Checks whether there's a scheduler-lock on a particular queue, and
returns the PID.
@@ -30,6 +30,7 @@
return False # Not possible to give useful information without
creating a performance issue (redis.keys())
except ImproperlyConfigured:
from rq.scheduler import RQScheduler
+
# When a scheduler acquires a lock it adds an expiring key: (e.g:
rq:scheduler-lock:<queue.name>)
#TODO: (RQ>= 1.13) return queue.scheduler_pid
pid = queue.connection.get(RQScheduler.get_locking_key(queue.name))
@@ -89,15 +90,37 @@
queue_data['scheduled_jobs'] = len(scheduled_job_registry)
queues.append(queue_data)
+
return {'queues': queues}
+def get_scheduler_statistics():
+ schedulers = {}
+ for index, config in enumerate(QUEUES_LIST):
+ # there is only one scheduler per redis connection, so we use the
connection as key
+ # to handle the possibility of a configuration with multiple redis
connections and scheduled
+ # jobs in more than one of them
+ queue = get_queue_by_index(index)
+ connection_kwargs = queue.connection.connection_pool.connection_kwargs
+ conn_key =
f"{connection_kwargs['host']}:{connection_kwargs['port']}/{connection_kwargs['db']}"
+ if conn_key not in schedulers:
+ try:
+ scheduler = get_scheduler(config['name'])
+ schedulers[conn_key] ={
+ 'count': scheduler.count(),
+ 'index': index,
+ }
+ except ImproperlyConfigured:
+ pass
+ return {'schedulers': schedulers}
+
+
def get_jobs(queue, job_ids, registry=None):
"""Fetch jobs in bulk from Redis.
1. If job data is not present in Redis, discard the result
2. If `registry` argument is supplied, delete empty jobs from registry
"""
- jobs = Job.fetch_many(job_ids, connection=queue.connection)
+ jobs = Job.fetch_many(job_ids, connection=queue.connection,
serializer=queue.serializer)
valid_jobs = []
for i, job in enumerate(jobs):
if job is None:
@@ -108,6 +131,7 @@
return valid_jobs
+
def stop_jobs(queue, job_ids):
job_ids = job_ids if isinstance(job_ids, (list, tuple)) else [job_ids]
stopped_job_ids = []
@@ -119,4 +143,4 @@
failed_to_stop_job_ids.append(job_id)
continue
stopped_job_ids.append(job_id)
- return stopped_job_ids, failed_to_stop_job_ids
\ No newline at end of file
+ return stopped_job_ids, failed_to_stop_job_ids
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/django_rq/views.py
new/django-rq-2.9.0/django_rq/views.py
--- old/django-rq-2.8.1/django_rq/views.py 2023-05-14 03:31:42.000000000
+0200
+++ new/django-rq-2.9.0/django_rq/views.py 2023-11-26 12:34:09.000000000
+0100
@@ -9,7 +9,6 @@
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_POST
-
from redis.exceptions import ResponseError
from rq import requeue_job
from rq.exceptions import NoSuchJobError
@@ -23,17 +22,20 @@
)
from rq.worker import Worker
from rq.worker_registration import clean_worker_registry
-from rq.command import send_stop_job_command
-from .queues import get_queue_by_index
-from .settings import API_TOKEN
-from .utils import get_statistics, get_jobs, stop_jobs
+from .queues import get_queue_by_index, get_scheduler_by_index
+from .settings import API_TOKEN, QUEUES_MAP
+from .utils import get_jobs, get_scheduler_statistics, get_statistics,
stop_jobs
@never_cache
@staff_member_required
def stats(request):
- context_data = {**admin.site.each_context(request),
**get_statistics(run_maintenance_tasks=True)}
+ context_data = {
+ **admin.site.each_context(request),
+ **get_statistics(run_maintenance_tasks=True),
+ **get_scheduler_statistics(),
+ }
return render(request, 'django_rq/stats.html', context_data)
@@ -287,7 +289,7 @@
for job_id in job_ids:
try:
- jobs.append(Job.fetch(job_id, connection=queue.connection))
+ jobs.append(Job.fetch(job_id, connection=queue.connection,
serializer=queue.serializer))
except NoSuchJobError:
pass
@@ -314,7 +316,7 @@
queue = get_queue_by_index(queue_index)
try:
- job = Job.fetch(job_id, connection=queue.connection)
+ job = Job.fetch(job_id, connection=queue.connection,
serializer=queue.serializer)
except NoSuchJobError:
raise Http404("Couldn't find job with this ID: %s" % job_id)
@@ -351,7 +353,7 @@
def delete_job(request, queue_index, job_id):
queue_index = int(queue_index)
queue = get_queue_by_index(queue_index)
- job = Job.fetch(job_id, connection=queue.connection)
+ job = Job.fetch(job_id, connection=queue.connection,
serializer=queue.serializer)
if request.method == 'POST':
# Remove job id from queue and delete the actual job
@@ -374,10 +376,10 @@
def requeue_job_view(request, queue_index, job_id):
queue_index = int(queue_index)
queue = get_queue_by_index(queue_index)
- job = Job.fetch(job_id, connection=queue.connection)
+ job = Job.fetch(job_id, connection=queue.connection,
serializer=queue.serializer)
if request.method == 'POST':
- requeue_job(job_id, connection=queue.connection)
+ requeue_job(job_id, connection=queue.connection,
serializer=queue.serializer)
messages.info(request, 'You have successfully requeued %s' % job.id)
return redirect('rq_job_detail', queue_index, job_id)
@@ -431,7 +433,7 @@
# Confirmation received
for job_id in job_ids:
try:
- requeue_job(job_id, connection=queue.connection)
+ requeue_job(job_id, connection=queue.connection,
serializer=queue.serializer)
count += 1
except NoSuchJobError:
pass
@@ -451,6 +453,35 @@
@never_cache
@staff_member_required
+def delete_failed_jobs(request, queue_index):
+ queue_index = int(queue_index)
+ queue = get_queue_by_index(queue_index)
+ registry = FailedJobRegistry(queue=queue)
+
+ if request.method == 'POST':
+ job_ids = registry.get_job_ids()
+ jobs = Job.fetch_many(job_ids, connection=queue.connection)
+ count = 0
+ for job in jobs:
+ if job:
+ job.delete()
+ count += 1
+
+ messages.info(request, 'You have successfully deleted %d jobs!' %
count)
+ return redirect('rq_home')
+
+ context_data = {
+ **admin.site.each_context(request),
+ 'queue_index': queue_index,
+ 'queue': queue,
+ 'total_jobs': len(registry),
+ }
+
+ return render(request, 'django_rq/clear_failed_queue.html', context_data)
+
+
+@never_cache
+@staff_member_required
def confirm_action(request, queue_index):
queue_index = int(queue_index)
queue = get_queue_by_index(queue_index)
@@ -486,14 +517,14 @@
if request.POST['action'] == 'delete':
for job_id in job_ids:
- job = Job.fetch(job_id, connection=queue.connection)
+ job = Job.fetch(job_id, connection=queue.connection,
serializer=queue.serializer)
# Remove job id from queue and delete the actual job
queue.connection.lrem(queue.key, 0, job.id)
job.delete()
messages.info(request, 'You have successfully deleted %s
jobs!' % len(job_ids))
elif request.POST['action'] == 'requeue':
for job_id in job_ids:
- requeue_job(job_id, connection=queue.connection)
+ requeue_job(job_id, connection=queue.connection,
serializer=queue.serializer)
messages.info(request, 'You have successfully requeued %d
jobs!' % len(job_ids))
elif request.POST['action'] == 'stop':
stopped, failed_to_stop = stop_jobs(queue, job_ids)
@@ -511,7 +542,7 @@
"""Enqueue deferred jobs"""
queue_index = int(queue_index)
queue = get_queue_by_index(queue_index)
- job = Job.fetch(job_id, connection=queue.connection)
+ job = Job.fetch(job_id, connection=queue.connection,
serializer=queue.serializer)
if request.method == 'POST':
try:
@@ -557,4 +588,45 @@
return redirect('rq_job_detail', queue_index, job_id)
else:
messages.error(request, 'Failed to stop %s' % job_id)
- return redirect('rq_job_detail', queue_index, job_id)
\ No newline at end of file
+ return redirect('rq_job_detail', queue_index, job_id)
+
+
+@never_cache
+@staff_member_required
+def scheduler_jobs(request, scheduler_index):
+ scheduler = get_scheduler_by_index(scheduler_index)
+
+ items_per_page = 100
+ num_jobs = scheduler.count()
+ page = int(request.GET.get('page', 1))
+ jobs = []
+
+ if num_jobs > 0:
+ last_page = int(ceil(num_jobs / items_per_page))
+ page_range = range(1, last_page + 1)
+ offset = items_per_page * (page - 1)
+ jobs_times = scheduler.get_jobs(with_times=True, offset=offset,
length=items_per_page)
+ for job, time in jobs_times:
+ job.next_run = time
+ job.queue_index = QUEUES_MAP.get(job.origin, 0)
+ if 'cron_string' in job.meta:
+ job.schedule = f"cron: '{job.meta['cron_string']}'"
+ elif 'interval' in job.meta:
+ job.schedule = f"interval: {job.meta['interval']}"
+ if 'repeat' in job.meta:
+ job.schedule += f" repeat: {job.meta['repeat']}"
+ else:
+ job.schedule = 'unknown'
+ jobs.append(job)
+ else:
+ page_range = []
+
+ context_data = {
+ **admin.site.each_context(request),
+ 'scheduler': scheduler,
+ 'jobs': jobs,
+ 'num_jobs': num_jobs,
+ 'page': page,
+ 'page_range': page_range,
+ }
+ return render(request, 'django_rq/scheduler.html', context_data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/django-rq-2.8.1/integration_test/integration_app/views.py
new/django-rq-2.9.0/integration_test/integration_app/views.py
--- old/django-rq-2.8.1/integration_test/integration_app/views.py
2023-05-14 03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/integration_test/integration_app/views.py
2023-11-26 12:34:09.000000000 +0100
@@ -13,4 +13,3 @@
return HttpResponse("Enqueued")
names = [m.name for m in MyModel.objects.order_by("name")]
return HttpResponse("Entries: {}".format(",".join(names)))
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/integration_test/requirements.txt
new/django-rq-2.9.0/integration_test/requirements.txt
--- old/django-rq-2.8.1/integration_test/requirements.txt 2023-05-14
03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/integration_test/requirements.txt 2023-11-26
12:34:09.000000000 +0100
@@ -1,5 +1,5 @@
-e ..
-Django==3.2.19
+Django==3.2.20
gunicorn==20.1.0
-psycopg2==2.9.6
-requests==2.30.0
+psycopg2==2.9.7
+requests==2.31.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/django-rq-2.8.1/setup.py new/django-rq-2.9.0/setup.py
--- old/django-rq-2.8.1/setup.py 2023-05-14 03:31:42.000000000 +0200
+++ new/django-rq-2.9.0/setup.py 2023-11-26 12:34:09.000000000 +0100
@@ -3,7 +3,7 @@
setup(
name='django-rq',
- version='2.8.1',
+ version='2.9.0',
author='Selwin Ong',
author_email='[email protected]',
packages=['django_rq'],
@@ -14,7 +14,7 @@
zip_safe=False,
include_package_data=True,
package_data={'': ['README.rst']},
- install_requires=['django>=2.0', 'rq>=1.14', 'redis>=3'],
+ install_requires=['django>=3.2', 'rq>=1.14', 'redis>=3'],
extras_require={
'Sentry': ['raven>=6.1.0'],
'testing': ['mock>=2.0.0'],
@@ -28,12 +28,11 @@
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
'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',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development :: Libraries :: Python Modules',
],