django-admin-background-task 0.2.0__tar.gz → 0.3.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_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/PKG-INFO +1 -1
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/model_admin.py +45 -4
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/static/bgtask/js/bgtask.js +66 -3
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templates/bgtask/admin/change_list.html +2 -2
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templates/bgtask/bg_changelist_status_column.html +0 -4
- django_admin_background_task-0.3.0/bgtask/templates/bgtask/bg_completed_column.html +24 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/pyproject.toml +1 -1
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/LICENSE +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/README.md +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/__init__.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/admin.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/apps.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/backends/__init__.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/backends/thread_pool.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/decorators.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/migrations/0001_initial.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/migrations/0002_backgroundtask_namespace_alter_backgroundtask_name.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/migrations/__init__.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/models.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/stack_contexts.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/static/bgtask/js/bgtask-once.js +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templates/bgtask/bgtask_templates.html +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templates/bgtask/bgtask_view.html +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templates/bgtask/progress.html +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templatetags/__init__.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/templatetags/bgtask.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/tests/test_bgtask.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/urls.py +0 -0
- {django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/views.py +0 -0
{django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/model_admin.py
RENAMED
@@ -1,7 +1,9 @@
|
|
1
1
|
from datetime import timedelta
|
2
|
+
from functools import wraps
|
2
3
|
|
3
4
|
from django.contrib import admin
|
4
5
|
from django.contrib.admin.utils import label_for_field
|
6
|
+
from django.contrib.messages import INFO
|
5
7
|
from django.db.models import Q
|
6
8
|
from django.utils import timezone
|
7
9
|
|
@@ -12,8 +14,29 @@ class BGTaskModelAdmin(admin.ModelAdmin):
|
|
12
14
|
# This is not overridden to avoid messing with the implicit logic for finding change list
|
13
15
|
# templates that ModelAdmin uses. So you either need to specify this yourself on your
|
14
16
|
# subclass or you need to extend from this in your custom template.
|
17
|
+
#
|
15
18
|
# change_list_template = "bgtask/admin/change_list.html"
|
16
19
|
|
20
|
+
# ----------------------------------------------------------------------------------------------
|
21
|
+
# Class API
|
22
|
+
# ----------------------------------------------------------------------------------------------
|
23
|
+
@classmethod
|
24
|
+
def starts_task(cls, name, **task_kwargs):
|
25
|
+
|
26
|
+
def starts_task_decorator(func):
|
27
|
+
|
28
|
+
@wraps(func)
|
29
|
+
def starts_task_wrapper(self, request, *args, **kwargs):
|
30
|
+
bgtask = self.start_bgtask(name, **task_kwargs)
|
31
|
+
result = func(self, request, *args, bgtask=bgtask, **kwargs)
|
32
|
+
self.message_user(request, f"Dispatched task {name}", INFO)
|
33
|
+
|
34
|
+
func.bgtask_name = name
|
35
|
+
|
36
|
+
return starts_task_wrapper
|
37
|
+
|
38
|
+
return starts_task_decorator
|
39
|
+
|
17
40
|
# ----------------------------------------------------------------------------------------------
|
18
41
|
# API for subclasses
|
19
42
|
# ----------------------------------------------------------------------------------------------
|
@@ -41,11 +64,26 @@ class BGTaskModelAdmin(admin.ModelAdmin):
|
|
41
64
|
def _bgtask_namespace(self):
|
42
65
|
return type(self).__module__ + "." + type(self).__name__
|
43
66
|
|
67
|
+
@staticmethod
|
68
|
+
def _extract_bgtask_name_from_admin_action(action):
|
69
|
+
# recurse through the potentially wrapped action until we find one that declares
|
70
|
+
# the bgtask_name
|
71
|
+
next_action = action
|
72
|
+
while True:
|
73
|
+
if hasattr(next_action, "bgtask_name"):
|
74
|
+
return next_action.bgtask_name
|
75
|
+
|
76
|
+
if not hasattr(next_action, "__wrapped__"):
|
77
|
+
return None
|
78
|
+
|
79
|
+
next_action = next_action.__wrapped__
|
80
|
+
|
44
81
|
def _admin_bg_tasks(self, request):
|
45
82
|
task_name_to_desc = {}
|
46
83
|
for action, action_name, action_description in self.get_actions(request).values():
|
47
|
-
|
48
|
-
|
84
|
+
bgtask_name = self._extract_bgtask_name_from_admin_action(action)
|
85
|
+
if bgtask_name is not None:
|
86
|
+
task_name_to_desc[bgtask_name] = action_description
|
49
87
|
|
50
88
|
for name in getattr(self, "bgtask_names", []):
|
51
89
|
task_name_to_desc[name] = name
|
@@ -58,10 +96,13 @@ class BGTaskModelAdmin(admin.ModelAdmin):
|
|
58
96
|
name__in=task_name_to_desc, namespace=self._bgtask_namespace
|
59
97
|
)
|
60
98
|
.filter(
|
61
|
-
|
99
|
+
(
|
100
|
+
Q(state=BackgroundTask.STATES.running)
|
101
|
+
& Q(started_at__gt=timezone.now() - timedelta(days=1))
|
102
|
+
)
|
62
103
|
| (
|
63
104
|
~Q(state=BackgroundTask.STATES.not_started)
|
64
|
-
& Q(completed_at__gt=timezone.now() - timedelta(
|
105
|
+
& Q(completed_at__gt=timezone.now() - timedelta(hours=2))
|
65
106
|
)
|
66
107
|
)
|
67
108
|
.order_by("-started_at")
|
@@ -14,6 +14,33 @@ const TIME_TO_REDUCED_REFRESH_PERIOD_S = 300;
|
|
14
14
|
const REDUCED_REFRESH_PERIOD_S = 60;
|
15
15
|
const PROGRESS_REFRESH_MS = 100;
|
16
16
|
|
17
|
+
function millisecondsToTimeAgoString(ms) {
|
18
|
+
const seconds = Math.floor(ms / 1000);
|
19
|
+
const minutes = Math.floor(seconds / 60);
|
20
|
+
const hours = Math.floor(minutes / 60);
|
21
|
+
const days = Math.floor(hours / 24);
|
22
|
+
|
23
|
+
if (days > 0) {
|
24
|
+
if (days === 1) {
|
25
|
+
return `${days} day, ${hours % 24} hours ago`;
|
26
|
+
}
|
27
|
+
return `${days} days ago`;
|
28
|
+
}
|
29
|
+
if (hours > 0) {
|
30
|
+
if (hours === 1) {
|
31
|
+
return `${hours} hour, ${minutes % 60} minutes ago`;
|
32
|
+
}
|
33
|
+
return `${hours} hours ago`;
|
34
|
+
}
|
35
|
+
if (minutes > 0) {
|
36
|
+
if (minutes === 1) {
|
37
|
+
return `${minutes} minute, ${seconds % 60} seconds ago`;
|
38
|
+
}
|
39
|
+
return `${minutes} minutes ago`;
|
40
|
+
}
|
41
|
+
return `${seconds} seconds ago`;
|
42
|
+
}
|
43
|
+
|
17
44
|
// -------------------------------------------------------------------------------------------------
|
18
45
|
// Generic live-looking progress bar manager.
|
19
46
|
// -------------------------------------------------------------------------------------------------
|
@@ -140,7 +167,6 @@ class TaskProgressDiv {
|
|
140
167
|
}
|
141
168
|
|
142
169
|
updateFromTask(task) {
|
143
|
-
// console.log(`TaskProgressDiv.updateFromTask`, task);
|
144
170
|
this.div.title = "";
|
145
171
|
|
146
172
|
switch (task.state) {
|
@@ -152,7 +178,14 @@ class TaskProgressDiv {
|
|
152
178
|
case "failed":
|
153
179
|
this._hideProgress();
|
154
180
|
this._showState();
|
155
|
-
|
181
|
+
let title = "Task failed";
|
182
|
+
for (const error of task.errors) {
|
183
|
+
if (error.traceback) {
|
184
|
+
title = `${title}\n${error.traceback}\n\n${error.error_message}`;
|
185
|
+
break;
|
186
|
+
}
|
187
|
+
}
|
188
|
+
this._addTitle(title);
|
156
189
|
break;
|
157
190
|
case "success":
|
158
191
|
this._hideProgress();
|
@@ -205,6 +238,26 @@ class TaskProgressDiv {
|
|
205
238
|
}
|
206
239
|
}
|
207
240
|
|
241
|
+
// -------------------------------------------------------------------------------------------------
|
242
|
+
// Manage an element which just shows one field from the task
|
243
|
+
// -------------------------------------------------------------------------------------------------
|
244
|
+
class TaskFieldElement {
|
245
|
+
constructor (element, fieldName, task) {
|
246
|
+
this.taskId = task.id;
|
247
|
+
this.element = element;
|
248
|
+
this.fieldName = fieldName;
|
249
|
+
this.updateFromTask(task);
|
250
|
+
}
|
251
|
+
|
252
|
+
attachToPoller(poller) {
|
253
|
+
poller.monitorTask(this.taskId, task => this.updateFromTask(task));
|
254
|
+
}
|
255
|
+
|
256
|
+
updateFromTask(task) {
|
257
|
+
this.element.innerHTML = task[this.fieldName];
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
208
261
|
// -------------------------------------------------------------------------------------------------
|
209
262
|
// Manage a task detail div
|
210
263
|
// -------------------------------------------------------------------------------------------------
|
@@ -299,6 +352,14 @@ class BGTaskPoller {
|
|
299
352
|
|
300
353
|
static normalizeTask(task) {
|
301
354
|
task.updated = new Date(task.updated);
|
355
|
+
|
356
|
+
if (task.completed_at !== null) {
|
357
|
+
task.completed_at = new Date(task.completed_at);
|
358
|
+
const completedTimeAgoMS = Date.now() - task.completed_at.getTime();
|
359
|
+
task.completed_at_time_ago = millisecondsToTimeAgoString(completedTimeAgoMS);
|
360
|
+
} else {
|
361
|
+
task.completed_at_time_ago = "–";
|
362
|
+
}
|
302
363
|
}
|
303
364
|
|
304
365
|
static sharedInstance(baseURL) {
|
@@ -357,7 +418,9 @@ class BGTaskPoller {
|
|
357
418
|
const req = new XMLHttpRequest();
|
358
419
|
const self = this;
|
359
420
|
req.addEventListener("load", function () { self._receivePoll(this); });
|
360
|
-
const
|
421
|
+
const taskIds = Object.keys(this.taskCallbacks).join(",");
|
422
|
+
const url = `${this.baseURL}?tasks=${taskIds}`;
|
423
|
+
console.log(`Poll for tasks ${taskIds}`);
|
361
424
|
req.open("GET", url);
|
362
425
|
req.setRequestHeader('Accept', 'application/json');
|
363
426
|
req.send();
|
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
{% block object-tools %}
|
10
10
|
{{ block.super }}
|
11
|
-
<div>
|
11
|
+
<div style="margin-bottom: 10px;">
|
12
12
|
<h3>Action background tasks</h3>
|
13
13
|
{% if not admin_bg_tasks|length %}
|
14
14
|
<p class="help" style="font-style: italic;">No recent background tasks</p>
|
@@ -20,7 +20,7 @@
|
|
20
20
|
<tbody>
|
21
21
|
{% for bgt in admin_bg_tasks %}
|
22
22
|
{% with bgtask=bgt.task_dict %}
|
23
|
-
<tr class="bgtask-row"><td><a href="{% url 'admin:bgtask_backgroundtask_change' bgtask.id %}">{{ bgt.admin_description }}</a></td><td>{{ bgt.started_at|timesince }} ago</td><td>{%
|
23
|
+
<tr class="bgtask-row"><td><a href="{% url 'admin:bgtask_backgroundtask_change' bgtask.id %}">{{ bgt.admin_description }}</a></td><td>{{ bgt.started_at|timesince }} ago</td><td>{% include "bgtask/bg_completed_column.html" %}</td><td>
|
24
24
|
{% include "bgtask/bg_changelist_status_column.html" %}</td></tr>
|
25
25
|
{% endwith %}
|
26
26
|
{% endfor %}
|
@@ -6,11 +6,7 @@
|
|
6
6
|
{{ bgtask|json_script:initialTasksJsonId }}
|
7
7
|
{% endwith %}
|
8
8
|
<script src="{% static 'bgtask/js/bgtask-once.js' %}"></script>
|
9
|
-
{% if bgtask.acted_on_object_id %}
|
10
|
-
<a href="{% url 'bgtask:tasks' %}?object_id={{bgtask.acted_on_object_id}}">
|
11
|
-
{% else %}
|
12
9
|
<a href="{% url 'admin:bgtask_backgroundtask_change' bgtask.id %}">
|
13
|
-
{% endif %}
|
14
10
|
<div id="bgtask-column-{{ bgtask.id }}">
|
15
11
|
{% include 'bgtask/progress.html' %}
|
16
12
|
</div>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{% load static %}
|
2
|
+
{% if not bgtask %}
|
3
|
+
–
|
4
|
+
{% else %}
|
5
|
+
{% with initialTasksJsonId="initialTasksJson-completedcolumn-"|add:bgtask.id %}
|
6
|
+
{{ bgtask|json_script:initialTasksJsonId }}
|
7
|
+
{% endwith %}
|
8
|
+
<script src="{% static 'bgtask/js/bgtask-once.js' %}"></script>
|
9
|
+
<span></span>
|
10
|
+
<script>
|
11
|
+
(() => {
|
12
|
+
const completedSpan = document.currentScript.previousElementSibling;
|
13
|
+
|
14
|
+
const originalTask = JSON.parse(
|
15
|
+
document.getElementById("initialTasksJson-completedcolumn-{{ bgtask.id }}").textContent
|
16
|
+
);
|
17
|
+
BGTaskPoller.normalizeTask(originalTask);
|
18
|
+
|
19
|
+
const statusCol = new TaskFieldElement(completedSpan, "completed_at_time_ago", originalTask);
|
20
|
+
const poller = BGTaskPoller.sharedInstance("{% url 'bgtask:tasks' %}");
|
21
|
+
statusCol.attachToPoller(poller);
|
22
|
+
})();
|
23
|
+
</script>
|
24
|
+
{% endif %}
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
4
4
|
|
5
5
|
[tool.poetry]
|
6
6
|
name = "django-admin-background-task"
|
7
|
-
version = "0.
|
7
|
+
version = "0.3.0"
|
8
8
|
description = "A set of tools for django apps to persist and monitor the status of background tasks"
|
9
9
|
authors = [
|
10
10
|
"David Park <david@greenparksoftware.co.uk>",
|
File without changes
|
File without changes
|
{django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/decorators.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{django_admin_background_task-0.2.0 → django_admin_background_task-0.3.0}/bgtask/stack_contexts.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|