Skip to content

Commit fbb916c

Browse files
authored
Compatibility with RQ 2.0 (#675)
* rqworker command can now run * Make job_detail.html display better * Properly show currently running executions * Minor job detail cleanup * Fixed a few tests * Made stop job command work again * Fixed tests * Failed job registry should show jobs in reverse chronological order. * Make failed jobs sortable * Make finished jobs sortable * Require RQ >= 2 * Temporarily skip rq-scheduler tests * Comment out scheduler test * Comment out invalid test * Updated changelog
1 parent a953e6a commit fbb916c

File tree

13 files changed

+696
-144
lines changed

13 files changed

+696
-144
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
run: |
3535
python -m pip install --upgrade pip
3636
pip install django==${{ matrix.django-version }} \
37-
redis django-redis pyyaml rq sentry-sdk rq-scheduler
37+
redis django-redis pyyaml rq sentry-sdk
3838
3939
- name: Run Test
4040
run: |

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
### Version 3.0 (2024-10-28)
2+
* Added support for RQ 2.0. Thanks @selwin!
3+
* Many typing improvements. Thanks @SpecLad and @terencehonles!
4+
* Added management command to suspend and resume workers. Thanks @jackkinsella!
5+
* Better support for Redis Sentinel. Thanks @alaouimehdi1995!
6+
17
### Version 2.10.2 (2024-03-23)
28
* Added support for Django 5.0. Thanks @selwin!
39
* Fixed an error in Python 3.12. Thanks @selwin!

django_rq/management/commands/rqworker.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import sys
33

44
from redis.exceptions import ConnectionError
5-
from rq import Connection
65
from rq.logutils import setup_loghandlers
76

87
from django.core.management.base import BaseCommand
@@ -84,21 +83,18 @@ def handle(self, *args, **options):
8483
'queue_class': options['queue_class'],
8584
'job_class': options['job_class'],
8685
'name': options['name'],
87-
'default_worker_ttl': options['worker_ttl'],
86+
'worker_ttl': options['worker_ttl'],
8887
'serializer': options['serializer']
8988
}
9089
w = get_worker(*args, **worker_kwargs)
9190

92-
# Call Connection context manager to push the redis connection into LocalStack
93-
# without this, jobs using RQ's get_current_job() will fail
94-
with Connection(w.connection):
95-
# Close any opened DB connection before any fork
96-
reset_db_connections()
91+
# Close any opened DB connection before any fork
92+
reset_db_connections()
9793

98-
w.work(
99-
burst=options.get('burst', False), with_scheduler=options.get('with_scheduler', False),
100-
logging_level=level, max_jobs=options['max_jobs']
101-
)
94+
w.work(
95+
burst=options.get('burst', False), with_scheduler=options.get('with_scheduler', False),
96+
logging_level=level, max_jobs=options['max_jobs']
97+
)
10298
except ConnectionError as e:
10399
self.stderr.write(str(e))
104100
sys.exit(1)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
{% extends "admin/base_site.html" %}
2+
3+
{% load static jquery_path django_rq %}
4+
5+
{% block title %}Failed Jobs in {{ queue.name }} {{ block.super }}{% endblock %}
6+
7+
{% block extrastyle %}
8+
{{ block.super }}
9+
<link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}">
10+
{% endblock %}
11+
12+
{% block extrahead %}
13+
{{ block.super }}
14+
<script type="text/javascript" src="{% get_jquery_path as jquery_path %}{% static jquery_path %}"></script>
15+
<script type="text/javascript" src="{% static "admin/js/jquery.init.js" %}"></script>
16+
<script type="text/javascript" src="{% static "admin/js/actions.js" %}"></script>
17+
<script type="text/javascript">
18+
(function($) {
19+
$(document).ready(function($) {
20+
$("tr input.action-select").actions();
21+
});
22+
})(django.jQuery);
23+
</script>
24+
{% endblock %}
25+
26+
27+
{% block breadcrumbs %}
28+
<div class="breadcrumbs">
29+
<a href="{% url 'admin:index' %}">Home</a> &rsaquo;
30+
<a href="{% url 'rq_home' %}">Django RQ</a> &rsaquo;
31+
<a href="{% url 'rq_jobs' queue_index %}">{{ queue.name }}</a>
32+
</div>
33+
{% endblock %}
34+
35+
{% block content_title %}<h1>{{ job_status }} jobs in {{ queue.name }}</h1>{% endblock %}
36+
37+
{% block content %}
38+
39+
<div id="content-main">
40+
<ul class="object-tools">
41+
<li><a href="{% url 'rq_requeue_all' queue_index %}" class="requeuelink">Requeue All</a></li>
42+
<li><a href="{% url 'rq_delete_failed_jobs' queue_index %}" class="requeuelink">Delete All</a></li>
43+
</ul>
44+
<div class="module" id="changelist">
45+
<div class="changelist-form-container">
46+
<form id="changelist-form" action="{% url 'rq_confirm_action' queue_index %}" method="post">
47+
{% csrf_token %}
48+
<div class="actions">
49+
<label>Actions:
50+
<select name="action" required>
51+
<option value="" selected>---------</option>
52+
<option value="delete">Delete</option>
53+
<option value="requeue">Requeue</option>
54+
</select>
55+
</label>
56+
<button type="submit" class="button" title="Execute selected action" name="index" value="0">Go</button>
57+
</div>
58+
<div class="results">
59+
<table id="result_list">
60+
<thead>
61+
<tr>
62+
<th scope="col" class="action-checkbox-column">
63+
<div class="text">
64+
<span><input type="checkbox" id="action-toggle"></span>
65+
</div>
66+
<div class="clear"></div>
67+
</th>
68+
<th scope="col" class="sortable">
69+
<div class='text'><span>ID</span></div>
70+
<div class="clear"></div>
71+
</th>
72+
<th scope="col" class="sortable">
73+
<div class='text'><span>Created</span></div>
74+
<div class="clear"></div>
75+
</th>
76+
<th scope="col" class="sortable">
77+
<div class='text'><span>Enqueued</span></div>
78+
<div class="clear"></div>
79+
</th>
80+
<th scope="col" class="sortable sorted {{ sort_direction }}">
81+
<div class="sortoptions">
82+
{% if sort_direction == 'ascending' %}
83+
<a href="?desc=1" class="toggle ascending" title="Toggle sorting"></a>
84+
{% else %}
85+
<a href="?desc=0" class="toggle descending" title="Toggle sorting"></a>
86+
{% endif %}
87+
</div>
88+
<div class='text'>
89+
{% if sort_direction == 'ascending' %}
90+
<a href="?desc=1">
91+
{% else %}
92+
<a href="?desc=0">
93+
{% endif %}
94+
Ended
95+
</a>
96+
</div>
97+
<div class="clear"></div>
98+
</th>
99+
<th scope="col" class="sortable">
100+
<div class='text'><span>Status</span></div>
101+
<div class="clear"></div>
102+
</th>
103+
<th scope="col" class="sortable">
104+
<div class='text'><span>Callable</span></div>
105+
<div class="clear"></div>
106+
</th>
107+
{% block extra_columns %}
108+
{% endblock extra_columns %}
109+
</tr>
110+
</thead>
111+
<tbody>
112+
{% for job in jobs %}
113+
<tr>
114+
<td class="action-checkbox">
115+
<input class="action-select" name="_selected_action" type="checkbox" value="{{ job.id }}">
116+
</td>
117+
<th>
118+
<a href="{% url 'rq_job_detail' queue_index job.id %}">
119+
{{ job.id }}
120+
</a>
121+
</th>
122+
<td>
123+
{% if job.created_at %}
124+
{{ job.created_at|to_localtime|date:"Y-m-d, H:i:s" }}
125+
{% endif %}
126+
</td>
127+
{% if job_status == 'Scheduled' %}
128+
<td>
129+
{% if job.scheduled_at %}
130+
{{ job.scheduled_at|to_localtime|date:"Y-m-d, H:i:s" }}
131+
{% endif %}
132+
</td>
133+
{% endif %}
134+
<td>
135+
{% if job.enqueued_at %}
136+
{{ job.enqueued_at|to_localtime|date:"Y-m-d, H:i:s" }}
137+
{% endif %}
138+
</td>
139+
<td>
140+
{% if job.ended_at %}
141+
{{ job.ended_at|to_localtime|date:"Y-m-d, H:i:s" }}
142+
{% endif %}
143+
</td>
144+
<td>{{ job.get_status.value }}</td>
145+
<td>{{ job|show_func_name }}</td>
146+
{% block extra_columns_values %}
147+
{% endblock extra_columns_values %}
148+
</tr>
149+
{% endfor %}
150+
</tbody>
151+
</table>
152+
</div>
153+
<p class="paginator">
154+
{% for p in page_range %}
155+
{% if p == page %}
156+
<span class="this-page">{{ p }}</span>
157+
{% elif forloop.last %}
158+
<a href="?page={{ p }}" class="end">{{ p }}</a>
159+
{% else %}
160+
<a href="?page={{ p }}">{{ p }}</a>
161+
{% endif %}
162+
{% endfor %}
163+
{{ num_jobs }} jobs
164+
</p>
165+
</form>
166+
</div>
167+
</div>
168+
</div>
169+
170+
{% endblock %}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
{% extends "admin/base_site.html" %}
2+
3+
{% load static jquery_path django_rq %}
4+
5+
{% block title %}Finished Jobs in {{ queue.name }} {{ block.super }}{% endblock %}
6+
7+
{% block extrastyle %}
8+
{{ block.super }}
9+
<link rel="stylesheet" type="text/css" href="{% static "admin/css/changelists.css" %}">
10+
{% endblock %}
11+
12+
{% block extrahead %}
13+
{{ block.super }}
14+
<script type="text/javascript" src="{% get_jquery_path as jquery_path %}{% static jquery_path %}"></script>
15+
<script type="text/javascript" src="{% static "admin/js/jquery.init.js" %}"></script>
16+
<script type="text/javascript" src="{% static "admin/js/actions.js" %}"></script>
17+
<script type="text/javascript">
18+
(function($) {
19+
$(document).ready(function($) {
20+
$("tr input.action-select").actions();
21+
});
22+
})(django.jQuery);
23+
</script>
24+
{% endblock %}
25+
26+
27+
{% block breadcrumbs %}
28+
<div class="breadcrumbs">
29+
<a href="{% url 'admin:index' %}">Home</a> &rsaquo;
30+
<a href="{% url 'rq_home' %}">Django RQ</a> &rsaquo;
31+
<a href="{% url 'rq_jobs' queue_index %}">{{ queue.name }}</a>
32+
</div>
33+
{% endblock %}
34+
35+
{% block content_title %}<h1>{{ job_status }} jobs in {{ queue.name }}</h1>{% endblock %}
36+
37+
{% block content %}
38+
39+
<div id="content-main">
40+
<ul class="object-tools">
41+
<li><a href="{% url 'rq_requeue_all' queue_index %}" class="requeuelink">Requeue All</a></li>
42+
<li><a href="{% url 'rq_delete_failed_jobs' queue_index %}" class="requeuelink">Delete All</a></li>
43+
</ul>
44+
<div class="module" id="changelist">
45+
<div class="changelist-form-container">
46+
<form id="changelist-form" action="{% url 'rq_confirm_action' queue_index %}" method="post">
47+
{% csrf_token %}
48+
<div class="actions">
49+
<label>Actions:
50+
<select name="action" required>
51+
<option value="" selected>---------</option>
52+
<option value="delete">Delete</option>
53+
<option value="requeue">Requeue</option>
54+
</select>
55+
</label>
56+
<button type="submit" class="button" title="Execute selected action" name="index" value="0">Go</button>
57+
</div>
58+
<div class="results">
59+
<table id="result_list">
60+
<thead>
61+
<tr>
62+
<th scope="col" class="action-checkbox-column">
63+
<div class="text">
64+
<span><input type="checkbox" id="action-toggle"></span>
65+
</div>
66+
<div class="clear"></div>
67+
</th>
68+
<th scope="col" class="sortable">
69+
<div class='text'><span>ID</span></div>
70+
<div class="clear"></div>
71+
</th>
72+
<th scope="col" class="sortable">
73+
<div class='text'><span>Created</span></div>
74+
<div class="clear"></div>
75+
</th>
76+
<th scope="col" class="sortable">
77+
<div class='text'><span>Enqueued</span></div>
78+
<div class="clear"></div>
79+
</th>
80+
<th scope="col" class="sortable sorted {{ sort_direction }}">
81+
<div class="sortoptions">
82+
{% if sort_direction == 'ascending' %}
83+
<a href="?desc=1" class="toggle ascending" title="Toggle sorting"></a>
84+
{% else %}
85+
<a href="?desc=0" class="toggle descending" title="Toggle sorting"></a>
86+
{% endif %}
87+
</div>
88+
<div class='text'>
89+
{% if sort_direction == 'ascending' %}
90+
<a href="?desc=1">
91+
{% else %}
92+
<a href="?desc=0">
93+
{% endif %}
94+
Ended
95+
</a>
96+
</div>
97+
<div class="clear"></div>
98+
</th>
99+
<th scope="col" class="sortable">
100+
<div class='text'><span>Status</span></div>
101+
<div class="clear"></div>
102+
</th>
103+
<th scope="col" class="sortable">
104+
<div class='text'><span>Callable</span></div>
105+
<div class="clear"></div>
106+
</th>
107+
{% block extra_columns %}
108+
{% endblock extra_columns %}
109+
</tr>
110+
</thead>
111+
<tbody>
112+
{% for job in jobs %}
113+
<tr>
114+
<td class="action-checkbox">
115+
<input class="action-select" name="_selected_action" type="checkbox" value="{{ job.id }}">
116+
</td>
117+
<th>
118+
<a href="{% url 'rq_job_detail' queue_index job.id %}">
119+
{{ job.id }}
120+
</a>
121+
</th>
122+
<td>
123+
{% if job.created_at %}
124+
{{ job.created_at|to_localtime|date:"Y-m-d, H:i:s" }}
125+
{% endif %}
126+
</td>
127+
{% if job_status == 'Scheduled' %}
128+
<td>
129+
{% if job.scheduled_at %}
130+
{{ job.scheduled_at|to_localtime|date:"Y-m-d, H:i:s" }}
131+
{% endif %}
132+
</td>
133+
{% endif %}
134+
<td>
135+
{% if job.enqueued_at %}
136+
{{ job.enqueued_at|to_localtime|date:"Y-m-d, H:i:s" }}
137+
{% endif %}
138+
</td>
139+
<td>
140+
{% if job.ended_at %}
141+
{{ job.ended_at|to_localtime|date:"Y-m-d, H:i:s" }}
142+
{% endif %}
143+
</td>
144+
<td>{{ job.get_status.value }}</td>
145+
<td>{{ job|show_func_name }}</td>
146+
{% block extra_columns_values %}
147+
{% endblock extra_columns_values %}
148+
</tr>
149+
{% endfor %}
150+
</tbody>
151+
</table>
152+
</div>
153+
<p class="paginator">
154+
{% for p in page_range %}
155+
{% if p == page %}
156+
<span class="this-page">{{ p }}</span>
157+
{% elif forloop.last %}
158+
<a href="?page={{ p }}" class="end">{{ p }}</a>
159+
{% else %}
160+
<a href="?page={{ p }}">{{ p }}</a>
161+
{% endif %}
162+
{% endfor %}
163+
{{ num_jobs }} jobs
164+
</p>
165+
</form>
166+
</div>
167+
</div>
168+
</div>
169+
170+
{% endblock %}

0 commit comments

Comments
 (0)