django-database-task 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_database_task-0.1.0/LICENSE +21 -0
- django_database_task-0.1.0/MANIFEST.in +3 -0
- django_database_task-0.1.0/PKG-INFO +506 -0
- django_database_task-0.1.0/README.md +473 -0
- django_database_task-0.1.0/django_database_task/__init__.py +27 -0
- django_database_task-0.1.0/django_database_task/admin.py +211 -0
- django_database_task-0.1.0/django_database_task/apps.py +7 -0
- django_database_task-0.1.0/django_database_task/backends.py +211 -0
- django_database_task-0.1.0/django_database_task/executor.py +181 -0
- django_database_task-0.1.0/django_database_task/locale/ja/LC_MESSAGES/django.mo +0 -0
- django_database_task-0.1.0/django_database_task/locale/ja/LC_MESSAGES/django.po +170 -0
- django_database_task-0.1.0/django_database_task/management/__init__.py +0 -0
- django_database_task-0.1.0/django_database_task/management/commands/__init__.py +0 -0
- django_database_task-0.1.0/django_database_task/management/commands/purge_completed_database_tasks.py +88 -0
- django_database_task-0.1.0/django_database_task/management/commands/run_database_tasks.py +98 -0
- django_database_task-0.1.0/django_database_task/migrations/0001_initial.py +190 -0
- django_database_task-0.1.0/django_database_task/migrations/__init__.py +0 -0
- django_database_task-0.1.0/django_database_task/models.py +120 -0
- django_database_task-0.1.0/django_database_task/urls.py +67 -0
- django_database_task-0.1.0/django_database_task/views.py +205 -0
- django_database_task-0.1.0/django_database_task.egg-info/PKG-INFO +506 -0
- django_database_task-0.1.0/django_database_task.egg-info/SOURCES.txt +31 -0
- django_database_task-0.1.0/django_database_task.egg-info/dependency_links.txt +1 -0
- django_database_task-0.1.0/django_database_task.egg-info/requires.txt +6 -0
- django_database_task-0.1.0/django_database_task.egg-info/top_level.txt +1 -0
- django_database_task-0.1.0/pyproject.toml +89 -0
- django_database_task-0.1.0/setup.cfg +4 -0
- django_database_task-0.1.0/tests/test_admin.py +233 -0
- django_database_task-0.1.0/tests/test_backend.py +247 -0
- django_database_task-0.1.0/tests/test_commands.py +211 -0
- django_database_task-0.1.0/tests/test_executor.py +367 -0
- django_database_task-0.1.0/tests/test_models.py +107 -0
- django_database_task-0.1.0/tests/test_views.py +261 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Shinya Okano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-database-task
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A database-backed task queue backend for Django 6.0's built-in task framework
|
|
5
|
+
Author-email: Shinya Okano <tokibito@gmail.com>
|
|
6
|
+
Maintainer-email: Shinya Okano <tokibito@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/tokibito/django-database-task
|
|
9
|
+
Project-URL: Repository, https://github.com/tokibito/django-database-task
|
|
10
|
+
Project-URL: Issues, https://github.com/tokibito/django-database-task/issues
|
|
11
|
+
Keywords: django,task,queue,background,database,async
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 6.0
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: Django>=6.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-django>=4.5; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# django-database-task
|
|
35
|
+
|
|
36
|
+
A database-backed task queue backend for Django 6.0's built-in task framework.
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **No external dependencies** - Uses your existing database, no Redis or message broker required
|
|
41
|
+
- **Priority support** - Tasks can have priorities from -100 to 100
|
|
42
|
+
- **Delayed execution** - Schedule tasks to run at a specific time with `run_after`
|
|
43
|
+
- **Exclusive locking** - Prevents duplicate task execution with `SELECT FOR UPDATE SKIP LOCKED`
|
|
44
|
+
- **Django Admin integration** - View and manage tasks from the admin interface
|
|
45
|
+
- **Async support** - Supports async task functions
|
|
46
|
+
|
|
47
|
+
## Architecture
|
|
48
|
+
|
|
49
|
+
```mermaid
|
|
50
|
+
sequenceDiagram
|
|
51
|
+
participant App as Application
|
|
52
|
+
participant Backend as DatabaseTaskBackend
|
|
53
|
+
participant DB as Database
|
|
54
|
+
participant Worker as Worker Process
|
|
55
|
+
|
|
56
|
+
Note over App,Worker: Task Enqueue
|
|
57
|
+
App->>Backend: task.enqueue(args, kwargs)
|
|
58
|
+
Backend->>Backend: Validate & serialize args
|
|
59
|
+
Backend->>DB: INSERT task (status=READY)
|
|
60
|
+
DB-->>Backend: Task ID
|
|
61
|
+
Backend-->>App: TaskResult (id, status=READY)
|
|
62
|
+
|
|
63
|
+
Note over App,Worker: Task Execution
|
|
64
|
+
Worker->>DB: SELECT FOR UPDATE SKIP LOCKED<br/>(status=READY, run_after <= now)
|
|
65
|
+
DB-->>Worker: Task record (with lock)
|
|
66
|
+
Worker->>DB: UPDATE status=RUNNING
|
|
67
|
+
Worker->>Worker: Execute task function
|
|
68
|
+
alt Success
|
|
69
|
+
Worker->>DB: UPDATE status=SUCCESSFUL,<br/>return_value, finished_at
|
|
70
|
+
else Failure
|
|
71
|
+
Worker->>DB: UPDATE status=FAILED,<br/>errors, finished_at
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Note over App,Worker: Result Retrieval (Optional)
|
|
75
|
+
App->>Backend: backend.get_result(task_id)
|
|
76
|
+
Backend->>DB: SELECT task
|
|
77
|
+
DB-->>Backend: Task record
|
|
78
|
+
Backend-->>App: TaskResult (status, return_value, errors)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Requirements
|
|
82
|
+
|
|
83
|
+
- Python 3.12+
|
|
84
|
+
- Django 6.0+
|
|
85
|
+
|
|
86
|
+
### Supported Databases
|
|
87
|
+
|
|
88
|
+
Django 6.0 officially supports the following database versions:
|
|
89
|
+
|
|
90
|
+
| Database | Minimum Version | Notes |
|
|
91
|
+
|----------|-----------------|-------|
|
|
92
|
+
| PostgreSQL | 14+ | Recommended for production. Full `SELECT FOR UPDATE SKIP LOCKED` support. |
|
|
93
|
+
| MySQL | 8.0.11+ | Full `SELECT FOR UPDATE SKIP LOCKED` support. |
|
|
94
|
+
| MariaDB | 10.6+ | Full `SELECT FOR UPDATE SKIP LOCKED` support. |
|
|
95
|
+
| SQLite | 3.31.0+ | Works for development/testing, but no row-level locking. |
|
|
96
|
+
| Oracle | 19c+ | Supported but not tested with this package. |
|
|
97
|
+
|
|
98
|
+
**Note**: `SELECT FOR UPDATE SKIP LOCKED` is used to prevent duplicate task execution in multi-worker environments. SQLite does not support row-level locking, so it is only recommended for development or single-worker deployments.
|
|
99
|
+
|
|
100
|
+
## Installation
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install django-database-task
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Quick Start
|
|
107
|
+
|
|
108
|
+
### 1. Add to INSTALLED_APPS
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
INSTALLED_APPS = [
|
|
112
|
+
# ...
|
|
113
|
+
'django_database_task',
|
|
114
|
+
]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 2. Configure the task backend
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
TASKS = {
|
|
121
|
+
'default': {
|
|
122
|
+
'BACKEND': 'django_database_task.backends.DatabaseTaskBackend',
|
|
123
|
+
'QUEUES': [], # Empty list means all queues
|
|
124
|
+
'OPTIONS': {},
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3. Run migrations
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python manage.py migrate django_database_task
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 4. Define a task
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from django.tasks import task
|
|
139
|
+
|
|
140
|
+
@task
|
|
141
|
+
def send_welcome_email(user_id):
|
|
142
|
+
user = User.objects.get(id=user_id)
|
|
143
|
+
# Send email...
|
|
144
|
+
return f"Email sent to {user.email}"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 5. Enqueue the task
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
result = send_welcome_email.enqueue(user_id=123)
|
|
151
|
+
print(f"Task ID: {result.id}")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 6. Run the worker
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Run once (exit when no tasks)
|
|
158
|
+
python manage.py run_database_tasks
|
|
159
|
+
|
|
160
|
+
# Run continuously (poll every 5 seconds)
|
|
161
|
+
python manage.py run_database_tasks --continuous --interval 5
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Usage
|
|
165
|
+
|
|
166
|
+
### Important: JSON-Serializable Parameters
|
|
167
|
+
|
|
168
|
+
Task arguments, keyword arguments, and return values **must be JSON-serializable**.
|
|
169
|
+
|
|
170
|
+
Supported types:
|
|
171
|
+
- `str`, `int`, `float`, `bool`, `None`
|
|
172
|
+
- `dict` (with JSON-serializable keys and values)
|
|
173
|
+
- `list`, `tuple` (with JSON-serializable elements)
|
|
174
|
+
- `bytes` (UTF-8 decodable only)
|
|
175
|
+
|
|
176
|
+
**Not supported** (will raise `TypeError`):
|
|
177
|
+
- `datetime`, `date`, `time` - convert to ISO string: `dt.isoformat()`
|
|
178
|
+
- `UUID` - convert to string: `str(uuid)`
|
|
179
|
+
- `Decimal` - convert to float or string
|
|
180
|
+
- Custom objects - serialize manually
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from django.tasks import task
|
|
184
|
+
|
|
185
|
+
# ❌ This will raise TypeError
|
|
186
|
+
@task
|
|
187
|
+
def bad_task(user_id, created_at):
|
|
188
|
+
pass
|
|
189
|
+
bad_task.enqueue(123, datetime.now()) # TypeError!
|
|
190
|
+
|
|
191
|
+
# ✅ Convert to JSON-serializable types
|
|
192
|
+
@task
|
|
193
|
+
def good_task(user_id, created_at_iso):
|
|
194
|
+
created_at = datetime.fromisoformat(created_at_iso)
|
|
195
|
+
# ...
|
|
196
|
+
good_task.enqueue(123, datetime.now().isoformat()) # OK
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Task with priority
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
@task(priority=10) # Higher priority, runs first
|
|
203
|
+
def urgent_task():
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
@task(priority=-10) # Lower priority
|
|
207
|
+
def background_task():
|
|
208
|
+
pass
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Delayed execution
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from datetime import timedelta
|
|
215
|
+
from django.utils import timezone
|
|
216
|
+
|
|
217
|
+
# Run 1 hour from now
|
|
218
|
+
delayed_task = my_task.using(run_after=timezone.now() + timedelta(hours=1))
|
|
219
|
+
result = delayed_task.enqueue()
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Task with context
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
@task(takes_context=True)
|
|
226
|
+
def task_with_context(context, message):
|
|
227
|
+
task_id = context.task_result.id
|
|
228
|
+
attempt = context.attempt
|
|
229
|
+
return f"Task {task_id} (attempt {attempt}): {message}"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Async tasks
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
@task
|
|
236
|
+
async def fetch_data(url):
|
|
237
|
+
async with aiohttp.ClientSession() as session:
|
|
238
|
+
async with session.get(url) as response:
|
|
239
|
+
return await response.text()
|
|
240
|
+
|
|
241
|
+
# Enqueue like normal tasks
|
|
242
|
+
result = fetch_data.enqueue("https://example.com/api")
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Queue-specific tasks
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
@task(queue_name="emails")
|
|
249
|
+
def send_newsletter():
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
# Run worker for specific queue
|
|
253
|
+
# python manage.py run_database_tasks --queue emails
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Management Commands
|
|
257
|
+
|
|
258
|
+
### run_database_tasks
|
|
259
|
+
|
|
260
|
+
Execute tasks queued in the database.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
python manage.py run_database_tasks [options]
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| Option | Description |
|
|
267
|
+
|--------|-------------|
|
|
268
|
+
| `--queue` | Queue name to process (all queues if not specified) |
|
|
269
|
+
| `--backend` | Backend name (default: "default") |
|
|
270
|
+
| `--continuous` | Keep polling even when no tasks |
|
|
271
|
+
| `--interval` | Polling interval in seconds (default: 5) |
|
|
272
|
+
| `--max-tasks` | Maximum number of tasks to process (0=unlimited) |
|
|
273
|
+
|
|
274
|
+
### purge_completed_database_tasks
|
|
275
|
+
|
|
276
|
+
Delete completed task records from the database.
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
python manage.py purge_completed_database_tasks [options]
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
| Option | Description |
|
|
283
|
+
|--------|-------------|
|
|
284
|
+
| `--days` | Delete tasks completed more than N days ago (0=all) |
|
|
285
|
+
| `--status` | Target statuses, comma-separated (default: "SUCCESSFUL,FAILED") |
|
|
286
|
+
| `--batch-size` | Number of tasks to delete at once (default: 1000) |
|
|
287
|
+
| `--dry-run` | Show count only without deleting |
|
|
288
|
+
|
|
289
|
+
## Programmatic API
|
|
290
|
+
|
|
291
|
+
You can also process tasks programmatically without management commands:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from django_database_task import process_one_task, process_tasks, get_pending_task_count
|
|
295
|
+
|
|
296
|
+
# Process a single task
|
|
297
|
+
result = process_one_task()
|
|
298
|
+
if result:
|
|
299
|
+
print(f"Processed: {result.id}, status: {result.status}")
|
|
300
|
+
|
|
301
|
+
# Process multiple tasks
|
|
302
|
+
results = process_tasks(max_tasks=10)
|
|
303
|
+
print(f"Processed {len(results)} tasks")
|
|
304
|
+
|
|
305
|
+
# Process tasks from a specific queue
|
|
306
|
+
results = process_tasks(queue_name="emails", max_tasks=5)
|
|
307
|
+
|
|
308
|
+
# Get pending task count
|
|
309
|
+
count = get_pending_task_count()
|
|
310
|
+
print(f"Pending tasks: {count}")
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## HTTP Endpoints (Optional)
|
|
314
|
+
|
|
315
|
+
For environments where cron or direct command execution is not available
|
|
316
|
+
(e.g., serverless, PaaS), you can use HTTP endpoints to trigger task processing.
|
|
317
|
+
|
|
318
|
+
### Setup
|
|
319
|
+
|
|
320
|
+
Include the URLs in your project:
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
# urls.py
|
|
324
|
+
from django.urls import path, include
|
|
325
|
+
|
|
326
|
+
urlpatterns = [
|
|
327
|
+
path("tasks/", include("django_database_task.urls")),
|
|
328
|
+
]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Available Endpoints
|
|
332
|
+
|
|
333
|
+
| Endpoint | Method | Description |
|
|
334
|
+
|----------|--------|-------------|
|
|
335
|
+
| `/tasks/run/` | POST | Process multiple pending tasks |
|
|
336
|
+
| `/tasks/run-one/` | POST | Process a single pending task |
|
|
337
|
+
| `/tasks/status/` | GET | Get pending task count |
|
|
338
|
+
|
|
339
|
+
### Request Parameters
|
|
340
|
+
|
|
341
|
+
#### POST `/tasks/run/`
|
|
342
|
+
|
|
343
|
+
| Parameter | Type | Default | Description |
|
|
344
|
+
|-----------|------|---------|-------------|
|
|
345
|
+
| `max_tasks` | int | 10 | Maximum tasks to process (1-100) |
|
|
346
|
+
| `queue_name` | string | null | Filter by queue name |
|
|
347
|
+
| `backend_name` | string | "default" | Task backend name |
|
|
348
|
+
|
|
349
|
+
Response:
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"processed": 3,
|
|
353
|
+
"results": [
|
|
354
|
+
{"id": "uuid", "status": "SUCCESSFUL", "task_path": "myapp.tasks.send_email"},
|
|
355
|
+
{"id": "uuid", "status": "FAILED", "task_path": "myapp.tasks.process_data"}
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### POST `/tasks/run-one/`
|
|
361
|
+
|
|
362
|
+
| Parameter | Type | Default | Description |
|
|
363
|
+
|-----------|------|---------|-------------|
|
|
364
|
+
| `queue_name` | string | null | Filter by queue name |
|
|
365
|
+
| `backend_name` | string | "default" | Task backend name |
|
|
366
|
+
|
|
367
|
+
Response:
|
|
368
|
+
```json
|
|
369
|
+
{"processed": true, "result": {"id": "uuid", "status": "SUCCESSFUL", "task_path": "..."}}
|
|
370
|
+
```
|
|
371
|
+
or
|
|
372
|
+
```json
|
|
373
|
+
{"processed": false, "result": null}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### GET `/tasks/status/`
|
|
377
|
+
|
|
378
|
+
| Parameter | Type | Default | Description |
|
|
379
|
+
|-----------|------|---------|-------------|
|
|
380
|
+
| `queue_name` | string | null | Filter by queue name |
|
|
381
|
+
| `backend_name` | string | "default" | Task backend name |
|
|
382
|
+
|
|
383
|
+
Response:
|
|
384
|
+
```json
|
|
385
|
+
{"pending_count": 5}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Example Usage
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# Process up to 10 tasks
|
|
392
|
+
curl -X POST http://localhost:8000/tasks/run/ \
|
|
393
|
+
-H "Content-Type: application/json" \
|
|
394
|
+
-d '{"max_tasks": 10}'
|
|
395
|
+
|
|
396
|
+
# Process tasks from a specific queue
|
|
397
|
+
curl -X POST http://localhost:8000/tasks/run/ \
|
|
398
|
+
-H "Content-Type: application/json" \
|
|
399
|
+
-d '{"queue_name": "emails", "max_tasks": 5}'
|
|
400
|
+
|
|
401
|
+
# Get pending task count
|
|
402
|
+
curl http://localhost:8000/tasks/status/
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Use Cases
|
|
406
|
+
|
|
407
|
+
#### Cloud Scheduler / Cron Job
|
|
408
|
+
|
|
409
|
+
Call the endpoint periodically to process tasks:
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# Every minute via cron or Cloud Scheduler
|
|
413
|
+
curl -X POST https://your-app.com/tasks/run/ \
|
|
414
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
415
|
+
-H "Content-Type: application/json" \
|
|
416
|
+
-d '{"max_tasks": 50}'
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### Webhook Trigger
|
|
420
|
+
|
|
421
|
+
Trigger task processing after an event:
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
# In your webhook handler
|
|
425
|
+
import requests
|
|
426
|
+
|
|
427
|
+
def handle_webhook(request):
|
|
428
|
+
# ... process webhook ...
|
|
429
|
+
|
|
430
|
+
# Trigger background task processing
|
|
431
|
+
requests.post(
|
|
432
|
+
"http://localhost:8000/tasks/run/",
|
|
433
|
+
json={"max_tasks": 10}
|
|
434
|
+
)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### Health Check with Task Status
|
|
438
|
+
|
|
439
|
+
Monitor pending task count:
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
# Alert if too many pending tasks
|
|
443
|
+
count=$(curl -s http://localhost:8000/tasks/status/ | jq '.pending_count')
|
|
444
|
+
if [ "$count" -gt 100 ]; then
|
|
445
|
+
echo "Warning: $count pending tasks"
|
|
446
|
+
fi
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Security
|
|
450
|
+
|
|
451
|
+
The endpoints are CSRF-exempt for API/webhook use. **Always add authentication in production:**
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
from django.contrib.admin.views.decorators import staff_member_required
|
|
455
|
+
from django_database_task.views import RunTasksView, RunOneTaskView, TaskStatusView
|
|
456
|
+
|
|
457
|
+
urlpatterns = [
|
|
458
|
+
path(
|
|
459
|
+
"tasks/run/",
|
|
460
|
+
staff_member_required(RunTasksView.as_view()),
|
|
461
|
+
name="run_tasks",
|
|
462
|
+
),
|
|
463
|
+
path(
|
|
464
|
+
"tasks/run-one/",
|
|
465
|
+
staff_member_required(RunOneTaskView.as_view()),
|
|
466
|
+
name="run_one_task",
|
|
467
|
+
),
|
|
468
|
+
path(
|
|
469
|
+
"tasks/status/",
|
|
470
|
+
staff_member_required(TaskStatusView.as_view()),
|
|
471
|
+
name="task_status",
|
|
472
|
+
),
|
|
473
|
+
]
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Or use token-based authentication:
|
|
477
|
+
|
|
478
|
+
```python
|
|
479
|
+
from django.http import HttpResponseForbidden
|
|
480
|
+
from django.conf import settings
|
|
481
|
+
|
|
482
|
+
def require_api_token(view_func):
|
|
483
|
+
def wrapper(request, *args, **kwargs):
|
|
484
|
+
token = request.headers.get("Authorization", "").replace("Bearer ", "")
|
|
485
|
+
if token != settings.TASK_API_TOKEN:
|
|
486
|
+
return HttpResponseForbidden("Invalid token")
|
|
487
|
+
return view_func(request, *args, **kwargs)
|
|
488
|
+
return wrapper
|
|
489
|
+
|
|
490
|
+
urlpatterns = [
|
|
491
|
+
path("tasks/run/", require_api_token(RunTasksView.as_view())),
|
|
492
|
+
]
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Django Admin
|
|
496
|
+
|
|
497
|
+
The package includes a Django Admin integration to view task status:
|
|
498
|
+
|
|
499
|
+
- Task list with status badges
|
|
500
|
+
- Filter by status, queue, backend
|
|
501
|
+
- Search by task ID or path
|
|
502
|
+
- View task arguments and results
|
|
503
|
+
|
|
504
|
+
## License
|
|
505
|
+
|
|
506
|
+
MIT License - see [LICENSE](LICENSE) for details.
|