django-admin-background-task 0.3.2__py3-none-any.whl → 0.4.1__py3-none-any.whl
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.
- bgtask/backends/thread_pool.py +2 -2
- bgtask/migrations/0001_initial.py +1 -1
- bgtask/migrations/0003_backgroundtask_queued_at.py +18 -0
- bgtask/model_admin.py +23 -7
- bgtask/models.py +67 -9
- bgtask/static/bgtask/js/bgtask.js +18 -3
- bgtask/templates/bgtask/admin/change_list.html +5 -2
- bgtask/tests/test_bgtask.py +37 -2
- {django_admin_background_task-0.3.2.dist-info → django_admin_background_task-0.4.1.dist-info}/METADATA +2 -1
- {django_admin_background_task-0.3.2.dist-info → django_admin_background_task-0.4.1.dist-info}/RECORD +12 -12
- bgtask/stack_contexts.py +0 -11
- {django_admin_background_task-0.3.2.dist-info → django_admin_background_task-0.4.1.dist-info}/LICENSE +0 -0
- {django_admin_background_task-0.3.2.dist-info → django_admin_background_task-0.4.1.dist-info}/WHEEL +0 -0
bgtask/backends/thread_pool.py
CHANGED
@@ -4,9 +4,9 @@ from concurrent.futures import ThreadPoolExecutor
|
|
4
4
|
SHARED_THREAD_POOL = None
|
5
5
|
|
6
6
|
|
7
|
-
def dispatch(func,
|
7
|
+
def dispatch(func, *args, **kwargs):
|
8
8
|
global SHARED_THREAD_POOL
|
9
9
|
if SHARED_THREAD_POOL is None:
|
10
10
|
SHARED_THREAD_POOL = ThreadPoolExecutor()
|
11
11
|
|
12
|
-
SHARED_THREAD_POOL.submit(func,
|
12
|
+
SHARED_THREAD_POOL.submit(func, *args, **kwargs)
|
@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
|
|
21
21
|
('updated', models.DateTimeField(auto_now=True)),
|
22
22
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
23
23
|
('name', models.CharField(max_length=1000)),
|
24
|
-
('state', models.CharField(choices=[('not_started', 'not_started'), ('running', 'running'), ('success', 'success'), ('partial_success', 'partial_success'), ('failed', 'failed')], default='not_started', max_length=16)),
|
24
|
+
('state', models.CharField(choices=[('not_started', 'not_started'), ('queued', 'queued'), ('running', 'running'), ('success', 'success'), ('partial_success', 'partial_success'), ('failed', 'failed')], default='not_started', max_length=16)),
|
25
25
|
('steps_to_complete', models.PositiveIntegerField(blank=True, help_text='The number of steps in the task for it to be completed.', null=True)),
|
26
26
|
('steps_completed', models.PositiveIntegerField(blank=True, help_text='The number of steps completed so far by this task', null=True)),
|
27
27
|
('started_at', models.DateTimeField(blank=True, null=True)),
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 4.2.11 on 2025-02-07 21:57
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
("bgtask", "0002_backgroundtask_namespace_alter_backgroundtask_name"),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name="backgroundtask",
|
15
|
+
name="queued_at",
|
16
|
+
field=models.DateTimeField(blank=True, null=True),
|
17
|
+
),
|
18
|
+
]
|
bgtask/model_admin.py
CHANGED
@@ -13,9 +13,14 @@ from .models import BackgroundTask
|
|
13
13
|
class BGTaskModelAdmin(admin.ModelAdmin):
|
14
14
|
# This is not overridden to avoid messing with the implicit logic for finding change list
|
15
15
|
# templates that ModelAdmin uses. So you either need to specify this yourself on your
|
16
|
-
# subclass or you need to extend from this in your custom template
|
16
|
+
# subclass or you need to extend from this in your custom template (which you can install at
|
17
|
+
# <your-app>/templates/admin/<your-app>/<your-model>/change_list.html)
|
17
18
|
#
|
18
19
|
# change_list_template = "bgtask/admin/change_list.html"
|
20
|
+
#
|
21
|
+
# Set this to tell the admin change list page which background tasks to show in the table.
|
22
|
+
#
|
23
|
+
# bgtask_names = ["task a", "task b"]
|
19
24
|
|
20
25
|
# ----------------------------------------------------------------------------------------------
|
21
26
|
# Class API
|
@@ -41,14 +46,15 @@ class BGTaskModelAdmin(admin.ModelAdmin):
|
|
41
46
|
# API for subclasses
|
42
47
|
# ----------------------------------------------------------------------------------------------
|
43
48
|
def start_bgtask(self, name, **kwargs):
|
44
|
-
bgtask =
|
45
|
-
name=name,
|
46
|
-
namespace=self._bgtask_namespace,
|
47
|
-
**kwargs,
|
48
|
-
)
|
49
|
+
bgtask = self._create_bgtask(name=name, **kwargs)
|
49
50
|
bgtask.start()
|
50
51
|
return bgtask
|
51
52
|
|
53
|
+
def queue_bgtask(self, name, **kwargs):
|
54
|
+
bgtask = self._create_bgtask(name=name, **kwargs)
|
55
|
+
bgtask.queue()
|
56
|
+
return bgtask
|
57
|
+
|
52
58
|
# ----------------------------------------------------------------------------------------------
|
53
59
|
# Superclass overrides
|
54
60
|
# ----------------------------------------------------------------------------------------------
|
@@ -78,6 +84,12 @@ class BGTaskModelAdmin(admin.ModelAdmin):
|
|
78
84
|
|
79
85
|
next_action = next_action.__wrapped__
|
80
86
|
|
87
|
+
def _create_bgtask(self, **kwargs):
|
88
|
+
return BackgroundTask.objects.create(
|
89
|
+
namespace=self._bgtask_namespace,
|
90
|
+
**kwargs,
|
91
|
+
)
|
92
|
+
|
81
93
|
def _admin_bg_tasks(self, request):
|
82
94
|
task_name_to_desc = {}
|
83
95
|
for action, action_name, action_description in self.get_actions(request).values():
|
@@ -100,12 +112,16 @@ class BGTaskModelAdmin(admin.ModelAdmin):
|
|
100
112
|
Q(state=BackgroundTask.STATES.running)
|
101
113
|
& Q(started_at__gt=timezone.now() - timedelta(days=1))
|
102
114
|
)
|
115
|
+
| (
|
116
|
+
Q(state=BackgroundTask.STATES.queued)
|
117
|
+
& Q(queued_at__gt=timezone.now() - timedelta(days=1))
|
118
|
+
)
|
103
119
|
| (
|
104
120
|
~Q(state=BackgroundTask.STATES.not_started)
|
105
121
|
& Q(completed_at__gt=timezone.now() - timedelta(hours=2))
|
106
122
|
)
|
107
123
|
)
|
108
|
-
.order_by("-started_at")
|
124
|
+
.order_by("-started_at", "-queued_at")
|
109
125
|
)
|
110
126
|
for bgt in bgts:
|
111
127
|
bgt.admin_description = task_name_to_desc[bgt.name]
|
bgtask/models.py
CHANGED
@@ -38,13 +38,16 @@ def locked(meth):
|
|
38
38
|
return _locked_meth
|
39
39
|
|
40
40
|
|
41
|
-
def only_if_state(state):
|
41
|
+
def only_if_state(state, no_op_states=frozenset()):
|
42
42
|
def only_if_state_decorator(meth):
|
43
|
-
def only_if_state_wrapper(self, *args, **kwargs):
|
44
|
-
if self.state
|
43
|
+
def only_if_state_wrapper(self, *args, no_op_if_already_in_state=False, **kwargs):
|
44
|
+
if self.state in no_op_states:
|
45
|
+
return
|
46
|
+
states = (state,) if isinstance(state, str) else tuple(state)
|
47
|
+
if self.state not in states:
|
45
48
|
raise RuntimeError(
|
46
|
-
"%s cannot execute %s as in state %s not %s"
|
47
|
-
% (self, meth.__name__, self.state,
|
49
|
+
"%s cannot execute %s as in state %s not one of %s"
|
50
|
+
% (self, meth.__name__, self.state, states)
|
48
51
|
)
|
49
52
|
return meth(self, *args, **kwargs)
|
50
53
|
|
@@ -72,7 +75,7 @@ class BackgroundTask(models.Model):
|
|
72
75
|
),
|
73
76
|
)
|
74
77
|
|
75
|
-
STATES = Choices("not_started", "running", "success", "partial_success", "failed")
|
78
|
+
STATES = Choices("not_started", "queued", "running", "success", "partial_success", "failed")
|
76
79
|
state = models.CharField(max_length=16, default=STATES.not_started, choices=STATES)
|
77
80
|
steps_to_complete = models.PositiveIntegerField(
|
78
81
|
null=True, blank=True, help_text="The number of steps in the task for it to be completed."
|
@@ -81,6 +84,7 @@ class BackgroundTask(models.Model):
|
|
81
84
|
null=True, blank=True, help_text="The number of steps completed so far by this task"
|
82
85
|
)
|
83
86
|
|
87
|
+
queued_at = models.DateTimeField(null=True, blank=True)
|
84
88
|
started_at = models.DateTimeField(null=True, blank=True)
|
85
89
|
completed_at = models.DateTimeField(null=True, blank=True)
|
86
90
|
result = models.JSONField(null=True, blank=True, help_text="The result(s) of the task, if any")
|
@@ -104,7 +108,12 @@ class BackgroundTask(models.Model):
|
|
104
108
|
@property
|
105
109
|
def task_dict(self):
|
106
110
|
task_dict = model_to_dict(self)
|
107
|
-
return {
|
111
|
+
return {
|
112
|
+
"id": str(self.id),
|
113
|
+
"updated": self.updated.isoformat(),
|
114
|
+
"position_in_queue": self.get_position_in_queue(),
|
115
|
+
**task_dict,
|
116
|
+
}
|
108
117
|
|
109
118
|
@property
|
110
119
|
def num_failed_steps(self):
|
@@ -114,6 +123,33 @@ class BackgroundTask(models.Model):
|
|
114
123
|
def incomplete(self):
|
115
124
|
return self.state in [self.STATES.not_started, self.STATES.running]
|
116
125
|
|
126
|
+
def get_position_in_queue(self):
|
127
|
+
if self.state != self.STATES.queued:
|
128
|
+
return None
|
129
|
+
|
130
|
+
if (
|
131
|
+
BackgroundTask.objects.filter(
|
132
|
+
queued_at__gt=self.queued_at,
|
133
|
+
)
|
134
|
+
.exclude(state=self.STATES.not_started)
|
135
|
+
.exclude(state=self.STATES.queued)
|
136
|
+
.exists()
|
137
|
+
):
|
138
|
+
# More recent tasks have started, assume we're next (either that or we're stuck)
|
139
|
+
return 0
|
140
|
+
|
141
|
+
# Otherwise approximate queue position as the number of tasks that were queued before us
|
142
|
+
# and are still in the queue. So if there is nothing in front of us we are 0 in the queue.
|
143
|
+
assert self.queued_at
|
144
|
+
return BackgroundTask.objects.filter(
|
145
|
+
queued_at__lt=self.queued_at, state=self.STATES.queued
|
146
|
+
).count()
|
147
|
+
|
148
|
+
def set_steps_to_complete(self, steps_to_complete):
|
149
|
+
self.steps_to_complete = steps_to_complete
|
150
|
+
self.steps_completed = 0
|
151
|
+
self.save()
|
152
|
+
|
117
153
|
@contextmanager
|
118
154
|
def runs_single_step(self):
|
119
155
|
try:
|
@@ -132,10 +168,32 @@ class BackgroundTask(models.Model):
|
|
132
168
|
else:
|
133
169
|
self.succeed()
|
134
170
|
|
171
|
+
@contextmanager
|
172
|
+
def fails_if_exception(self):
|
173
|
+
try:
|
174
|
+
yield
|
175
|
+
except Exception as exc:
|
176
|
+
self.fail(exc)
|
177
|
+
raise
|
178
|
+
|
135
179
|
@locked
|
136
180
|
@only_if_state(STATES.not_started)
|
181
|
+
def queue(self):
|
182
|
+
log.info("Background Task queueing: %s", self.id)
|
183
|
+
self.state = self.STATES.queued
|
184
|
+
self.queued_at = timezone.now()
|
185
|
+
self.save()
|
186
|
+
|
187
|
+
@locked
|
188
|
+
@only_if_state(
|
189
|
+
(STATES.not_started, STATES.queued),
|
190
|
+
# Allow start() to be called while running so that if a task's subtasks are queued and
|
191
|
+
# asynchronous each can call .start() independently so the first one that is executed will
|
192
|
+
# start the task.
|
193
|
+
no_op_states=(STATES.running,),
|
194
|
+
)
|
137
195
|
def start(self):
|
138
|
-
log.info("%s
|
196
|
+
log.info("Background Task starting: %s", self.id)
|
139
197
|
self.state = self.STATES.running
|
140
198
|
self.started_at = timezone.now()
|
141
199
|
self.save()
|
@@ -144,7 +202,7 @@ class BackgroundTask(models.Model):
|
|
144
202
|
@only_if_state(STATES.running)
|
145
203
|
def fail(self, exc):
|
146
204
|
"""Call to indicate a complete and final failure of the task"""
|
147
|
-
log.info("
|
205
|
+
log.info("Background Task failed: %s %s", self.id, exc)
|
148
206
|
self.state = self.STATES.failed
|
149
207
|
self.completed_at = timezone.now()
|
150
208
|
self.errors.append(
|
@@ -2,6 +2,7 @@ window.BG_TASK_ADDED = true;
|
|
2
2
|
|
3
3
|
const STATE_TO_EMOJI = {
|
4
4
|
"not_started": "⏱️",
|
5
|
+
"queued": "⋯",
|
5
6
|
"running": "🏃🏽♀️",
|
6
7
|
"success": "✅",
|
7
8
|
"partial_success": "⚠️",
|
@@ -201,10 +202,14 @@ class TaskProgressDiv {
|
|
201
202
|
this._showProgress();
|
202
203
|
this._hideState();
|
203
204
|
break;
|
205
|
+
case "queued":
|
206
|
+
this._hideProgress();
|
207
|
+
this._showState();
|
208
|
+
break;
|
204
209
|
}
|
205
210
|
|
206
211
|
const isOutOfDate = (
|
207
|
-
!["success", "partial_success", "not_started", "failed"].includes(task.state)
|
212
|
+
!["success", "queued", "partial_success", "not_started", "failed"].includes(task.state)
|
208
213
|
&& (new Date() - task.updated) > OUT_OF_DATE_PERIOD_S * 1000
|
209
214
|
);
|
210
215
|
|
@@ -212,11 +217,21 @@ class TaskProgressDiv {
|
|
212
217
|
this._addTitle("This task has not been updated for a while");
|
213
218
|
}
|
214
219
|
|
215
|
-
this.
|
220
|
+
this._setStateEmoji(task);
|
216
221
|
|
217
222
|
this.pgstate.update({ max: task.steps_to_complete, value: task.steps_completed, isOutOfDate });
|
218
223
|
}
|
219
224
|
|
225
|
+
_setStateEmoji(task) {
|
226
|
+
this.stateEle.innerHTML = `${STATE_TO_EMOJI[task.state] || "❓"}`;
|
227
|
+
|
228
|
+
switch (task.state) {
|
229
|
+
case "queued":
|
230
|
+
this.stateEle.innerHTML += ` (${task.position_in_queue})`;
|
231
|
+
break;
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
220
235
|
_addTitle(title) {
|
221
236
|
if (!this.div.title) {
|
222
237
|
this.div.title = title;
|
@@ -445,7 +460,7 @@ class BGTaskPoller {
|
|
445
460
|
for (const cbk of (this.taskCallbacks[task.id] || [])) {
|
446
461
|
cbk(task);
|
447
462
|
}
|
448
|
-
if (task.state
|
463
|
+
if (!['running', 'queued'].includes(task.state)) {
|
449
464
|
this.stopMonitoringTask(task.id);
|
450
465
|
}
|
451
466
|
}
|
@@ -19,8 +19,11 @@
|
|
19
19
|
<tbody>
|
20
20
|
{% for bgt in admin_bg_tasks %}
|
21
21
|
{% with bgtask=bgt.task_dict %}
|
22
|
-
<tr class="bgtask-row"
|
23
|
-
|
22
|
+
<tr class="bgtask-row">
|
23
|
+
<td><a href="{% url 'admin:bgtask_backgroundtask_change' bgtask.id %}">{{ bgt.admin_description }}</a></td>
|
24
|
+
<td>{{ bgt.started_at|timesince }} ago</td><td>{% include "bgtask/bg_completed_column.html" %}</td>
|
25
|
+
<td>{% include "bgtask/bg_changelist_status_column.html" %}</td>
|
26
|
+
</tr>
|
24
27
|
{% endwith %}
|
25
28
|
{% endfor %}
|
26
29
|
</tbody>
|
bgtask/tests/test_bgtask.py
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
|
+
from django.utils import timezone
|
4
|
+
|
3
5
|
from bgtask.models import BackgroundTask
|
4
6
|
|
7
|
+
pytestmark = pytest.mark.django_db
|
8
|
+
|
5
9
|
|
6
10
|
@pytest.fixture
|
7
11
|
def a_task():
|
@@ -30,6 +34,37 @@ def test_bgtask_immediate_failure(a_task):
|
|
30
34
|
assert a_task.state == BackgroundTask.STATES.failed
|
31
35
|
|
32
36
|
assert len(a_task.errors) == 1
|
33
|
-
assert a_task.errors[0]["datetime"] ==
|
37
|
+
assert a_task.errors[0]["datetime"] == a_task.completed_at.isoformat()
|
34
38
|
assert a_task.errors[0]["error_message"] == "Some global failure"
|
35
|
-
assert "test_bgtask_immediate_failure" in a_task.errors[0]["
|
39
|
+
assert "test_bgtask_immediate_failure" in a_task.errors[0]["traceback"]
|
40
|
+
|
41
|
+
|
42
|
+
def test_bgtask_start_again(a_task):
|
43
|
+
a_task.start()
|
44
|
+
import time
|
45
|
+
time.sleep(0.001)
|
46
|
+
|
47
|
+
curr_started_at = a_task.started_at
|
48
|
+
assert timezone.now() > curr_started_at
|
49
|
+
|
50
|
+
# Starting again is no-op
|
51
|
+
a_task.start()
|
52
|
+
assert a_task.started_at == curr_started_at
|
53
|
+
|
54
|
+
|
55
|
+
def test_fails_if_exception(a_task):
|
56
|
+
a_task.start()
|
57
|
+
|
58
|
+
with pytest.raises(Exception):
|
59
|
+
with a_task.fails_if_exception():
|
60
|
+
raise Exception("fail!")
|
61
|
+
|
62
|
+
assert a_task.state == BackgroundTask.STATES.failed
|
63
|
+
|
64
|
+
|
65
|
+
def test_set_steps(a_task):
|
66
|
+
a_task.steps_completed = 10
|
67
|
+
a_task.set_steps_to_complete(20)
|
68
|
+
a_task.refresh_from_db()
|
69
|
+
assert a_task.steps_to_complete == 20
|
70
|
+
assert a_task.steps_completed == 0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-admin-background-task
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.1
|
4
4
|
Summary: A set of tools for django apps to persist and monitor the status of background tasks
|
5
5
|
Home-page: https://github.com/daphtdazz/django-bgtask
|
6
6
|
License: Apache-2.0
|
@@ -58,4 +58,5 @@ urlpatterns = [
|
|
58
58
|
from bgtask.models import BackgroundTask
|
59
59
|
|
60
60
|
BackgroundTask.new(name)
|
61
|
+
```
|
61
62
|
|
{django_admin_background_task-0.3.2.dist-info → django_admin_background_task-0.4.1.dist-info}/RECORD
RENAMED
@@ -2,17 +2,17 @@ bgtask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
bgtask/admin.py,sha256=cOhbR__7WJ3rmjlbZjGKh_gJDpEz_c6Ll2zwHo7mpiM,965
|
3
3
|
bgtask/apps.py,sha256=zZHsNaW9nnJgK7jnpty0IjM0TTpsk33xhpsGTa-x3iE,144
|
4
4
|
bgtask/backends/__init__.py,sha256=zZlL7gHXL12heAhHD_7D7lEBihCE-LHYb07yIbk055s,57
|
5
|
-
bgtask/backends/thread_pool.py,sha256=
|
5
|
+
bgtask/backends/thread_pool.py,sha256=KD_RccLR1fSiPZGI_fbX4Rcic6mE21VUdrUkusuI9Zk,286
|
6
6
|
bgtask/decorators.py,sha256=UsiCBhqaWm1Yn4n4hmFbauGQvNezoeDm7Iqpjbvxbjk,1119
|
7
|
-
bgtask/migrations/0001_initial.py,sha256=
|
7
|
+
bgtask/migrations/0001_initial.py,sha256=TPuplGYJtqHJ7h4-A-nINMmvRD9HYvfhOY4bZyinz2c,2035
|
8
8
|
bgtask/migrations/0002_backgroundtask_namespace_alter_backgroundtask_name.py,sha256=XIG_gIvdA73tz0NRbNkGU_ieKde3ZtQltPeLdslD5Xo,968
|
9
|
+
bgtask/migrations/0003_backgroundtask_queued_at.py,sha256=Hp0WeQLKP0R5jNeEAgVlsx-7jgIzbnIkqkPUGsenK8o,439
|
9
10
|
bgtask/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
bgtask/model_admin.py,sha256=
|
11
|
-
bgtask/models.py,sha256=
|
12
|
-
bgtask/stack_contexts.py,sha256=2Hun6O5Mm949uAioC2E3i54Rg-_H8P7LsHdhJYc0w6E,231
|
11
|
+
bgtask/model_admin.py,sha256=loEh9uPUZbC19_daPoPfHNuLrI42oAA930J5IEH_1-w,5057
|
12
|
+
bgtask/models.py,sha256=HomYFivOW7176I2uq7Gk21wniJ29fIfv01Sz72DHjQI,11291
|
13
13
|
bgtask/static/bgtask/js/bgtask-once.js,sha256=DBerpxPLP21IH--HpBxVFk3kN52FtBiuyJsDCjg7mCA,746
|
14
|
-
bgtask/static/bgtask/js/bgtask.js,sha256=
|
15
|
-
bgtask/templates/bgtask/admin/change_list.html,sha256=
|
14
|
+
bgtask/static/bgtask/js/bgtask.js,sha256=6fVepM6AY_a3-I5ZABfaCaf7_g8IkClOFq9ZxG9GsX4,15366
|
15
|
+
bgtask/templates/bgtask/admin/change_list.html,sha256=pxajrUXZvsBfSM4Lh3HLjdjGNNO7lJPaTkUwZ_zbWEc,1080
|
16
16
|
bgtask/templates/bgtask/bg_changelist_status_column.html,sha256=SA7EAL50oBrcRPGstXROXkFJ541vYA8RlHidM3srTPU,844
|
17
17
|
bgtask/templates/bgtask/bg_completed_column.html,sha256=Q4l3EQaOXHYVOzv0jpx3ww3Wa2Bb33RyZp3RSWI1zog,788
|
18
18
|
bgtask/templates/bgtask/bgtask_templates.html,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
@@ -20,10 +20,10 @@ bgtask/templates/bgtask/bgtask_view.html,sha256=AeubqERFF14PHWjInBD39wGImnRx4taS
|
|
20
20
|
bgtask/templates/bgtask/progress.html,sha256=VVYbrunGasOshRlz8Nm8hFPxqcVtrpaW1e2QxzAMcMM,166
|
21
21
|
bgtask/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
bgtask/templatetags/bgtask.py,sha256=WUEWlQX5AarbXJBV6EGp-khT1zegVwGNigSKRF5mCTE,413
|
23
|
-
bgtask/tests/test_bgtask.py,sha256=
|
23
|
+
bgtask/tests/test_bgtask.py,sha256=pepgFBvRWNqmOdOFPkp4MC_ctWmYPYGpisb94OUUA9I,1882
|
24
24
|
bgtask/urls.py,sha256=DXXjgj18LTSCq2-xZf23-pkgO4RmZglv67mgrp-fDyk,161
|
25
25
|
bgtask/views.py,sha256=tPblidBq8mIv9cbyAfzJfNlM-I-Z11XVoobdA3DZBVM,1765
|
26
|
-
django_admin_background_task-0.
|
27
|
-
django_admin_background_task-0.
|
28
|
-
django_admin_background_task-0.
|
29
|
-
django_admin_background_task-0.
|
26
|
+
django_admin_background_task-0.4.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
27
|
+
django_admin_background_task-0.4.1.dist-info/METADATA,sha256=g-KSsxCt134tSJmO0wT_FwjPoVbHGo_rBHLR9fXdCbc,1379
|
28
|
+
django_admin_background_task-0.4.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
29
|
+
django_admin_background_task-0.4.1.dist-info/RECORD,,
|
bgtask/stack_contexts.py
DELETED
File without changes
|
{django_admin_background_task-0.3.2.dist-info → django_admin_background_task-0.4.1.dist-info}/WHEEL
RENAMED
File without changes
|