Skip to content

Commit f342ed4

Browse files
committed
Add cancellation and migrations
1 parent 5de2ed9 commit f342ed4

File tree

6 files changed

+165
-2
lines changed

6 files changed

+165
-2
lines changed

galahad/admin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ def rerun(modeladmin, request, queryset):
2727
rerun.allowed_permissions = ('rerun',)
2828

2929

30+
def cancel(modeladmin, request, queryset):
31+
succeeded = queryset.succeeded().count()
32+
if succeeded:
33+
messages.warning(request, "Only failed tasks can be retried. %s tasks have been skipped" % succeeded)
34+
queryset.not_succeeded().cancel(request.user)
35+
messages.success(request, "Tasks have been successfully queued")
36+
37+
38+
cancel.short_description = t('Cancel selected tasks')
39+
cancel.allowed_permissions = ('cancel',)
40+
41+
3042
@admin.register(models.Task)
3143
class TaskAdmin(VersionAdmin):
3244

galahad/migrations/0001_initial.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Generated by Django 2.1.3 on 2018-11-29 15:10
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
import galahad.models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
initial = True
12+
13+
dependencies = [
14+
('contenttypes', '0002_remove_content_type_name'),
15+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16+
]
17+
18+
operations = [
19+
migrations.CreateModel(
20+
name='Process',
21+
fields=[
22+
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
23+
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
24+
('modified', models.DateTimeField(auto_now=True, db_index=True)),
25+
],
26+
),
27+
migrations.CreateModel(
28+
name='Task',
29+
fields=[
30+
('id', models.BigAutoField(editable=False, primary_key=True, serialize=False)),
31+
('node_name', models.TextField(db_index=True, editable=False)),
32+
('node_type', models.TextField(choices=[('human', 'human'), ('machine', 'machine')], db_index=True, editable=False)),
33+
('status', models.TextField(choices=[('failed', 'failed'), ('succeeded', 'succeeded'), ('scheduled', 'scheduled'), ('canceled', 'canceled')], db_index=True, default='scheduled', editable=False)),
34+
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
35+
('modified', models.DateTimeField(auto_now=True, db_index=True)),
36+
('completed', models.DateTimeField(blank=True, db_index=True, editable=False, null=True)),
37+
('exception', models.TextField(blank=True)),
38+
('stacktrace', models.TextField(blank=True)),
39+
('_process', models.ForeignKey(db_column='process_id', editable=False, on_delete=django.db.models.deletion.CASCADE, to='galahad.Process')),
40+
('assignees', models.ManyToManyField(related_name='galahad_assignee_task_set', to=settings.AUTH_USER_MODEL, verbose_name='assignees')),
41+
('completed_by_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='galahad_completed_by_task_set', to=settings.AUTH_USER_MODEL, verbose_name='completed by')),
42+
('content_type', models.ForeignKey(editable=False, limit_choices_to=galahad.models.process_subclasses, on_delete=django.db.models.deletion.CASCADE, related_name='galahad_task_set', to='contenttypes.ContentType')),
43+
('parent_task_set', models.ManyToManyField(editable=False, related_name='child_task_set', to='galahad.Task')),
44+
],
45+
options={
46+
'get_latest_by': ('created',),
47+
'permissions': (('rerun', 'Can rerun failed tasks.'), ('cancel', 'Can cancel failed tasks.'), ('override', 'Can override a process.')),
48+
'ordering': ('-completed', '-created'),
49+
'default_manager_name': 'objects',
50+
},
51+
),
52+
]

galahad/migrations/__init__.py

Whitespace-only changes.

galahad/models.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import graphviz as gv
66
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
77
from django.db import models, transaction
8+
from django.db.models.functions import Now
89
from django.urls import path, reverse, NoReverseMatch
910
from django.utils import timezone
1011
from django.utils.safestring import SafeString
@@ -89,6 +90,7 @@ class Process(models.Model, metaclass=BaseProcess):
8990
"""
9091

9192
manual_override = views.ManualOverrideView
93+
detail_view = views.ProcessDetailView
9294

9395
@classmethod
9496
def _wrap_view_instance(cls, name, view_instance):
@@ -134,9 +136,9 @@ def urls(cls):
134136
route = '{name}/<pk>/'.format(name=name)
135137
urls.append(path(route, cls._wrap_view_instance(name, node), name=name))
136138
urls.extend((
137-
path('<pk>/', views.ProcessDetailView.as_view(model=cls),
139+
path('<pk>/', cls.detail_view.as_view(model=cls),
138140
name='detail'),
139-
path('<pk>/override', views.ManualOverrideView.as_view(model=cls), name='override'),
141+
path('<pk>/override', cls.manual_override.as_view(model=cls), name='override'),
140142
))
141143
return urls, cls.get_url_namespace()
142144

@@ -275,6 +277,9 @@ def save(self, **kwargs):
275277
update_fields.append('modified')
276278
super().save(**kwargs)
277279

280+
def cancel(self, user=None):
281+
self.task_set.cancel(user)
282+
278283

279284
def process_subclasses():
280285
from django.apps import apps
@@ -301,6 +306,18 @@ def not_succeeded(self):
301306
def failed(self):
302307
return self.filter(status=self.model.FAILED)
303308

309+
def canceled(self):
310+
return self.filter(status=self.model.CANCELED)
311+
312+
def cancel(self, user=None):
313+
if user and not user.is_authenticated:
314+
user = None
315+
return self.update(
316+
status=self.model.CANCELED,
317+
completed_by_user=user,
318+
completed=Now(),
319+
)
320+
304321

305322
class Task(models.Model):
306323
id = models.BigAutoField(primary_key=True, editable=False)
@@ -343,10 +360,12 @@ class Task(models.Model):
343360
FAILED = 'failed'
344361
SUCCEEDED = 'succeeded'
345362
SCHEDULED = 'scheduled'
363+
CANCELED = 'canceled'
346364
_status_choices = (
347365
(FAILED, t(FAILED)),
348366
(SUCCEEDED, t(SUCCEEDED)),
349367
(SCHEDULED, t(SCHEDULED)),
368+
(CANCELED, t(CANCELED)),
350369
)
351370
status = models.TextField(
352371
choices=_status_choices,
@@ -384,6 +403,7 @@ class Meta:
384403
get_latest_by = ('created',)
385404
permissions = (
386405
('rerun', t('Can rerun failed tasks.')),
406+
('cancel', t('Can cancel failed tasks.')),
387407
('override', t('Can override a process.')),
388408
)
389409
default_manager_name = 'objects'
@@ -431,6 +451,18 @@ def finish(self, user=None):
431451
else:
432452
self.save()
433453

454+
def cancel(self, user=None):
455+
self.completed = timezone.now()
456+
self.status = self.CANCELED
457+
if user and not user.is_authenticated:
458+
user = None
459+
self.completed_by_user = user
460+
self.save(update_fields=[
461+
'status',
462+
'completed',
463+
'completed_by_user'
464+
])
465+
434466
def fail(self):
435467
self.completed = timezone.now()
436468
self.status = self.FAILED
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Generated by Django 2.1.3 on 2018-11-29 15:10
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14+
('galahad', '0001_initial'),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name='GatewayProcess',
20+
fields=[
21+
('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='galahad.Process')),
22+
],
23+
bases=('galahad.process',),
24+
),
25+
migrations.CreateModel(
26+
name='LoopProcess',
27+
fields=[
28+
('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='galahad.Process')),
29+
('counter', models.PositiveIntegerField(default=0)),
30+
],
31+
bases=('galahad.process',),
32+
),
33+
migrations.CreateModel(
34+
name='SimpleProcess',
35+
fields=[
36+
('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='galahad.Process')),
37+
],
38+
bases=('galahad.process',),
39+
),
40+
migrations.CreateModel(
41+
name='SplitJoinProcess',
42+
fields=[
43+
('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='galahad.Process')),
44+
('parallel_task_value', models.PositiveIntegerField(default=0)),
45+
],
46+
bases=('galahad.process',),
47+
),
48+
migrations.CreateModel(
49+
name='WaitProcess',
50+
fields=[
51+
('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='galahad.Process')),
52+
('parallel_task_value', models.PositiveIntegerField(default=0)),
53+
],
54+
bases=('galahad.process',),
55+
),
56+
migrations.CreateModel(
57+
name='WelcomeProcess',
58+
fields=[
59+
('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='galahad.Process')),
60+
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
61+
],
62+
options={
63+
'abstract': False,
64+
},
65+
bases=('galahad.process', models.Model),
66+
),
67+
]

tests/testapp/migrations/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)