processlib 0.9.0__tar.gz → 0.10.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.
- {processlib-0.9.0/processlib.egg-info → processlib-0.10.0}/PKG-INFO +3 -2
- {processlib-0.9.0 → processlib-0.10.0}/processlib/__init__.py +1 -4
- {processlib-0.9.0 → processlib-0.10.0}/processlib/activity.py +4 -45
- {processlib-0.9.0 → processlib-0.10.0}/processlib/flow.py +4 -7
- {processlib-0.9.0 → processlib-0.10.0}/processlib/models.py +6 -11
- {processlib-0.9.0 → processlib-0.10.0}/processlib/services.py +22 -8
- {processlib-0.9.0 → processlib-0.10.0}/processlib/signals.py +22 -13
- {processlib-0.9.0 → processlib-0.10.0}/processlib/tests.py +68 -18
- {processlib-0.9.0 → processlib-0.10.0}/processlib/urls.py +10 -2
- {processlib-0.9.0 → processlib-0.10.0}/processlib/views.py +10 -5
- {processlib-0.9.0 → processlib-0.10.0/processlib.egg-info}/PKG-INFO +3 -2
- processlib-0.10.0/setup.py +16 -0
- processlib-0.9.0/setup.py +0 -14
- {processlib-0.9.0 → processlib-0.10.0}/LICENSE +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/MANIFEST.in +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/README.md +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/apps.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/assignment.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/forms.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/migrations/0001_initial.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/migrations/0002_auto_20220525_1136.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/migrations/__init__.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/serializers.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/tasks.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/layout.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_cancel.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_detail.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_details_partial.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_list.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_list_item.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_to_do_partial.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/view_activity.html +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templatetags/__init__.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/templatetags/processlib_tags.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/test_settings.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib/test_urls.py +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib.egg-info/SOURCES.txt +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib.egg-info/dependency_links.txt +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib.egg-info/requires.txt +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/processlib.egg-info/top_level.txt +0 -0
- {processlib-0.9.0 → processlib-0.10.0}/setup.cfg +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: processlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: A workflow library for python
|
|
5
5
|
Home-page: https://github.com/RaphaelKimmig/processlib
|
|
6
|
-
Download-URL: https://github.com/RaphaelKimmig/processlib/archive/0.
|
|
6
|
+
Download-URL: https://github.com/RaphaelKimmig/processlib/archive/0.10.0.tar.gz
|
|
7
7
|
Author: Raphael Kimmig
|
|
8
8
|
Author-email: raphael@ampad.de
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: django>=3.2
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import six
|
|
3
|
-
from django.http import HttpResponseRedirect
|
|
4
|
-
|
|
5
|
-
if django.VERSION[0] < 2:
|
|
6
|
-
from django.core.urlresolvers import reverse
|
|
7
|
-
else:
|
|
8
|
-
from django.urls import reverse
|
|
1
|
+
import logging
|
|
9
2
|
|
|
10
3
|
from django.db import transaction
|
|
4
|
+
from django.http import HttpResponseRedirect
|
|
5
|
+
from django.urls import reverse
|
|
11
6
|
from django.utils import timezone
|
|
12
7
|
|
|
13
|
-
import logging
|
|
14
8
|
from processlib.assignment import inherit
|
|
15
9
|
from processlib.tasks import run_async_activity
|
|
16
10
|
|
|
@@ -18,7 +12,6 @@ from processlib.tasks import run_async_activity
|
|
|
18
12
|
logger = logging.getLogger(__name__)
|
|
19
13
|
|
|
20
14
|
|
|
21
|
-
@six.python_2_unicode_compatible
|
|
22
15
|
class Activity(object):
|
|
23
16
|
def __init__(
|
|
24
17
|
self,
|
|
@@ -61,7 +54,7 @@ class Activity(object):
|
|
|
61
54
|
return False
|
|
62
55
|
|
|
63
56
|
def __str__(self):
|
|
64
|
-
return
|
|
57
|
+
return str(self.verbose_name or self.name)
|
|
65
58
|
|
|
66
59
|
def __repr__(self):
|
|
67
60
|
return '{}(name="{}")'.format(self.__class__.__name__, self.name)
|
|
@@ -346,40 +339,6 @@ class EndActivity(Activity):
|
|
|
346
339
|
self.process.save(update_fields=update_fields)
|
|
347
340
|
|
|
348
341
|
|
|
349
|
-
class EndRedirectActivity(EndActivity):
|
|
350
|
-
def __init__(self, redirect_url_callback=None, **kwargs):
|
|
351
|
-
self.redirect_url_callback = redirect_url_callback
|
|
352
|
-
super(EndActivity, self).__init__(**kwargs)
|
|
353
|
-
|
|
354
|
-
def instantiate(self, **kwargs):
|
|
355
|
-
# HACK: we skip the EndActivity implementation
|
|
356
|
-
# because it would finish the activity right away
|
|
357
|
-
super(EndActivity, self).instantiate(**kwargs)
|
|
358
|
-
|
|
359
|
-
def has_view(self):
|
|
360
|
-
return True
|
|
361
|
-
|
|
362
|
-
def get_absolute_url(self):
|
|
363
|
-
return reverse(
|
|
364
|
-
"processlib:process-activity",
|
|
365
|
-
kwargs={"flow_label": self.flow.label, "activity_id": self.instance.pk},
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
def dispatch(self, request, *args, **kwargs):
|
|
369
|
-
self.start()
|
|
370
|
-
url = reverse(
|
|
371
|
-
"processlib:process-detail", kwargs={"pk": self.instance.process.pk}
|
|
372
|
-
)
|
|
373
|
-
try:
|
|
374
|
-
if self.redirect_url_callback:
|
|
375
|
-
url = self.redirect_url_callback(self)
|
|
376
|
-
self.finish()
|
|
377
|
-
except Exception as e:
|
|
378
|
-
logger.exception(e)
|
|
379
|
-
self.error(exception=e)
|
|
380
|
-
return HttpResponseRedirect(url)
|
|
381
|
-
|
|
382
|
-
|
|
383
342
|
class FormActivity(Activity):
|
|
384
343
|
def __init__(self, form_class=None, **kwargs):
|
|
385
344
|
self.form_class = form_class
|
|
@@ -3,9 +3,7 @@ from __future__ import unicode_literals
|
|
|
3
3
|
from collections import OrderedDict, defaultdict
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
import six
|
|
7
6
|
from django.utils import timezone
|
|
8
|
-
from six import python_2_unicode_compatible
|
|
9
7
|
|
|
10
8
|
from .models import Process, ActivityInstance
|
|
11
9
|
|
|
@@ -34,7 +32,6 @@ def register_flow(flow):
|
|
|
34
32
|
_FLOWS[flow.label] = flow
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
@python_2_unicode_compatible
|
|
38
35
|
class Flow(object):
|
|
39
36
|
def __init__(
|
|
40
37
|
self,
|
|
@@ -68,7 +65,7 @@ class Flow(object):
|
|
|
68
65
|
)
|
|
69
66
|
|
|
70
67
|
def __str__(self):
|
|
71
|
-
return
|
|
68
|
+
return str(self.verbose_name or self.name)
|
|
72
69
|
|
|
73
70
|
def start_with(self, activity_name, activity, **activity_kwargs):
|
|
74
71
|
if self._activities:
|
|
@@ -98,7 +95,7 @@ class Flow(object):
|
|
|
98
95
|
predecessors = [after] if after else []
|
|
99
96
|
|
|
100
97
|
if wait_for:
|
|
101
|
-
if isinstance(wait_for,
|
|
98
|
+
if isinstance(wait_for, str):
|
|
102
99
|
raise TypeError("wait_for should be a list or tuple")
|
|
103
100
|
|
|
104
101
|
activity_kwargs["wait_for"] = wait_for
|
|
@@ -125,7 +122,7 @@ class Flow(object):
|
|
|
125
122
|
process=process,
|
|
126
123
|
instance=None,
|
|
127
124
|
name=activity_name,
|
|
128
|
-
**self._activity_kwargs[activity_name]
|
|
125
|
+
**self._activity_kwargs[activity_name],
|
|
129
126
|
)
|
|
130
127
|
|
|
131
128
|
def get_activity_by_instance(self, instance):
|
|
@@ -143,7 +140,7 @@ class Flow(object):
|
|
|
143
140
|
flow_label=self.label,
|
|
144
141
|
started_at=timezone.now(),
|
|
145
142
|
status=self.process_model.STATUS_STARTED,
|
|
146
|
-
**(process_kwargs or {})
|
|
143
|
+
**(process_kwargs or {}),
|
|
147
144
|
)
|
|
148
145
|
activity = self._get_activity_by_name(process, list(self._activities)[0])
|
|
149
146
|
activity.instantiate(instance_kwargs=activity_instance_kwargs, request=request)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
import string
|
|
3
3
|
|
|
4
|
-
import six
|
|
5
4
|
from django.conf import settings
|
|
6
5
|
from django.contrib.auth.models import Group
|
|
7
6
|
from django.core.exceptions import ValidationError
|
|
@@ -9,9 +8,6 @@ from django.db import models
|
|
|
9
8
|
from django.utils.translation import gettext_lazy as _
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
from six import python_2_unicode_compatible
|
|
13
|
-
|
|
14
|
-
|
|
15
11
|
def validate_flow_label(value):
|
|
16
12
|
from .flow import _FLOWS
|
|
17
13
|
|
|
@@ -24,14 +20,13 @@ def validate_flow_label(value):
|
|
|
24
20
|
|
|
25
21
|
def is_format_string(value):
|
|
26
22
|
try:
|
|
27
|
-
parsed = next(string.Formatter().parse(
|
|
23
|
+
parsed = next(string.Formatter().parse(str(value)))
|
|
28
24
|
except ValueError:
|
|
29
25
|
return False
|
|
30
26
|
|
|
31
27
|
return parsed[1] is not None
|
|
32
28
|
|
|
33
29
|
|
|
34
|
-
@python_2_unicode_compatible
|
|
35
30
|
class Process(models.Model):
|
|
36
31
|
STATUS_STARTED = "started"
|
|
37
32
|
STATUS_CANCELED = "canceled"
|
|
@@ -61,9 +56,9 @@ class Process(models.Model):
|
|
|
61
56
|
|
|
62
57
|
def __str__(self):
|
|
63
58
|
if not self.flow:
|
|
64
|
-
return
|
|
59
|
+
return str(self.id)
|
|
65
60
|
if self.flow.verbose_name:
|
|
66
|
-
return
|
|
61
|
+
return str(self.flow.verbose_name)
|
|
67
62
|
return self.flow.name
|
|
68
63
|
|
|
69
64
|
@property
|
|
@@ -73,16 +68,16 @@ class Process(models.Model):
|
|
|
73
68
|
def can_cancel(self, user=None):
|
|
74
69
|
return (
|
|
75
70
|
self.status not in (self.STATUS_DONE, self.STATUS_CANCELED)
|
|
76
|
-
and
|
|
71
|
+
and
|
|
77
72
|
# FIXME allow cancelation of scheduled instances
|
|
78
|
-
self._activity_instances.filter(
|
|
73
|
+
not self._activity_instances.filter(
|
|
79
74
|
status=ActivityInstance.STATUS_SCHEDULED
|
|
80
75
|
).exists()
|
|
81
76
|
)
|
|
82
77
|
|
|
83
78
|
@property
|
|
84
79
|
def description(self):
|
|
85
|
-
flow_description =
|
|
80
|
+
flow_description = str(self.flow.description or self.flow)
|
|
86
81
|
if not is_format_string(flow_description):
|
|
87
82
|
return flow_description
|
|
88
83
|
try:
|
|
@@ -87,6 +87,9 @@ def cancel_process(process, user):
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
def get_user_processes(user, include_unassigned=True):
|
|
90
|
+
if not user.is_authenticated:
|
|
91
|
+
return Process.objects.none()
|
|
92
|
+
|
|
90
93
|
q = Q(_activity_instances__assigned_group__in=user.groups.all()) & ~Q(
|
|
91
94
|
_activity_instances__status=ActivityInstance.STATUS_CANCELED
|
|
92
95
|
) | Q(_activity_instances__assigned_user=user) & ~Q(
|
|
@@ -104,6 +107,9 @@ def get_user_processes(user, include_unassigned=True):
|
|
|
104
107
|
|
|
105
108
|
|
|
106
109
|
def get_user_current_processes(user, include_unassigned=True):
|
|
110
|
+
if not user.is_authenticated:
|
|
111
|
+
return Process.objects.none()
|
|
112
|
+
|
|
107
113
|
q = Q(
|
|
108
114
|
_activity_instances__assigned_group__in=user.groups.all(),
|
|
109
115
|
_activity_instances__status__in=(
|
|
@@ -129,17 +135,23 @@ def get_user_current_processes(user, include_unassigned=True):
|
|
|
129
135
|
)
|
|
130
136
|
|
|
131
137
|
q &= get_permission_filter(user)
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
return (
|
|
139
|
+
Process.objects.filter(
|
|
140
|
+
status=Process.STATUS_STARTED,
|
|
141
|
+
)
|
|
142
|
+
.filter(q)
|
|
143
|
+
.distinct()
|
|
144
|
+
)
|
|
135
145
|
|
|
136
146
|
|
|
137
147
|
def get_permission_filter(user):
|
|
138
148
|
flows = get_flows()
|
|
139
|
-
flows_label_list = [
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
flows_label_list = [
|
|
150
|
+
label
|
|
151
|
+
for (label, flow) in flows
|
|
152
|
+
if not flow.permission or user.has_perm(flow.permission)
|
|
153
|
+
]
|
|
154
|
+
return Q(_activity_instances__process__flow_label__in=flows_label_list)
|
|
143
155
|
|
|
144
156
|
|
|
145
157
|
def user_has_any_process_perm(user, process):
|
|
@@ -162,7 +174,9 @@ def user_has_activity_perm(user, activity):
|
|
|
162
174
|
# if there are no required permissions we grant access
|
|
163
175
|
return True
|
|
164
176
|
elif activity.permission and activity.flow.permission:
|
|
165
|
-
return user.has_perm(activity.permission) and user.has_perm(
|
|
177
|
+
return user.has_perm(activity.permission) and user.has_perm(
|
|
178
|
+
activity.flow.permission
|
|
179
|
+
)
|
|
166
180
|
elif activity.permission:
|
|
167
181
|
return user.has_perm(activity.permission)
|
|
168
182
|
elif activity.flow.permission:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from logging import getLogger
|
|
2
2
|
|
|
3
|
-
import six
|
|
4
3
|
from django.contrib.auth.models import Permission
|
|
5
4
|
from django.contrib.contenttypes.models import ContentType
|
|
6
5
|
from django.db.models.signals import pre_migrate
|
|
@@ -28,15 +27,21 @@ def create_flow_permissions(app_config, **kwargs):
|
|
|
28
27
|
flow.permission, app_label, process_content_type.app_label
|
|
29
28
|
)
|
|
30
29
|
)
|
|
31
|
-
if
|
|
30
|
+
if (
|
|
31
|
+
Permission.objects.filter(codename=codename)
|
|
32
|
+
.exclude(content_type=process_content_type)
|
|
33
|
+
.exists()
|
|
34
|
+
):
|
|
32
35
|
# app_label.codename should match process_content_type, otherwise that doesn't really make sense
|
|
33
|
-
raise ValueError(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"A permission for {}.{} with a different content type already"
|
|
38
|
+
" exists. Set auto_create_permission to False or remove the other"
|
|
39
|
+
" permission"
|
|
40
|
+
)
|
|
36
41
|
Permission.objects.update_or_create(
|
|
37
42
|
content_type=process_content_type,
|
|
38
43
|
codename=codename,
|
|
39
|
-
defaults={"name":
|
|
44
|
+
defaults={"name": str(flow)},
|
|
40
45
|
)
|
|
41
46
|
activity_content_type = ContentType.objects.get_for_model(flow.process_model)
|
|
42
47
|
for activity_name in flow._activities:
|
|
@@ -47,11 +52,17 @@ def create_flow_permissions(app_config, **kwargs):
|
|
|
47
52
|
# split like django
|
|
48
53
|
app_label, codename = activity.permission.split(".", 1)
|
|
49
54
|
|
|
50
|
-
if
|
|
55
|
+
if (
|
|
56
|
+
Permission.objects.filter(codename=codename)
|
|
57
|
+
.exclude(content_type=activity_content_type)
|
|
58
|
+
.exists()
|
|
59
|
+
):
|
|
51
60
|
# app_label.codename should match activity_content_type, otherwise that doesn't really make sense
|
|
52
|
-
raise ValueError(
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
raise ValueError(
|
|
62
|
+
"A permission for {}.{} with a different content type already"
|
|
63
|
+
" exists. Set auto_create_permission to False or remove the other"
|
|
64
|
+
" permission"
|
|
65
|
+
)
|
|
55
66
|
|
|
56
67
|
if app_label != activity_content_type.app_label:
|
|
57
68
|
raise ValueError(
|
|
@@ -65,9 +76,7 @@ def create_flow_permissions(app_config, **kwargs):
|
|
|
65
76
|
Permission.objects.update_or_create(
|
|
66
77
|
content_type=activity_content_type,
|
|
67
78
|
codename=codename,
|
|
68
|
-
defaults={
|
|
69
|
-
"name": "{} - {}".format(six.text_type(flow), six.text_type(name))
|
|
70
|
-
},
|
|
79
|
+
defaults={"name": "{} - {}".format(str(flow), str(name))},
|
|
71
80
|
)
|
|
72
81
|
|
|
73
82
|
|
|
@@ -9,7 +9,13 @@ from django.urls import reverse
|
|
|
9
9
|
|
|
10
10
|
from processlib.activity import FunctionActivity, AsyncActivity
|
|
11
11
|
from processlib.forms import ProcessCancelForm
|
|
12
|
-
from .activity import
|
|
12
|
+
from .activity import (
|
|
13
|
+
StartActivity,
|
|
14
|
+
EndActivity,
|
|
15
|
+
ViewActivity,
|
|
16
|
+
Wait,
|
|
17
|
+
StartViewActivity,
|
|
18
|
+
)
|
|
13
19
|
from .assignment import inherit, nobody, request_user
|
|
14
20
|
from .flow import Flow
|
|
15
21
|
from .models import ActivityInstance
|
|
@@ -393,9 +399,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
393
399
|
self.post_with_permissions = RequestFactory().post("/")
|
|
394
400
|
self.post_with_permissions.user = self.user_with_perms
|
|
395
401
|
|
|
396
|
-
def test_process_list_view_respects_flow_permission(
|
|
397
|
-
self
|
|
398
|
-
):
|
|
402
|
+
def test_process_list_view_respects_flow_permission(self):
|
|
399
403
|
no_perm_response = ProcessListView.as_view()(
|
|
400
404
|
self.get_no_permissions,
|
|
401
405
|
)
|
|
@@ -406,9 +410,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
406
410
|
)
|
|
407
411
|
self.assertContains(perm_response, self.process.pk)
|
|
408
412
|
|
|
409
|
-
def test_user_process_list_view_respects_flow_permission(
|
|
410
|
-
self
|
|
411
|
-
):
|
|
413
|
+
def test_user_process_list_view_respects_flow_permission(self):
|
|
412
414
|
no_perm_response = UserProcessListView.as_view()(
|
|
413
415
|
self.get_no_permissions,
|
|
414
416
|
)
|
|
@@ -419,9 +421,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
419
421
|
)
|
|
420
422
|
self.assertContains(perm_response, self.process.pk)
|
|
421
423
|
|
|
422
|
-
def test_user_current_process_list_view_respects_flow_permission(
|
|
423
|
-
self
|
|
424
|
-
):
|
|
424
|
+
def test_user_current_process_list_view_respects_flow_permission(self):
|
|
425
425
|
no_perm_response = UserCurrentProcessListView.as_view()(
|
|
426
426
|
self.get_no_permissions,
|
|
427
427
|
)
|
|
@@ -433,7 +433,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
433
433
|
self.assertContains(perm_response, self.process.pk)
|
|
434
434
|
|
|
435
435
|
def test_process_detail_view_raises_permission_denied_with_missing_permissions(
|
|
436
|
-
self
|
|
436
|
+
self,
|
|
437
437
|
):
|
|
438
438
|
with self.assertRaises(PermissionDenied):
|
|
439
439
|
ProcessDetailView.as_view()(self.get_no_permissions, pk=self.process.id)
|
|
@@ -443,7 +443,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
443
443
|
self.assertEqual(response.status_code, 200)
|
|
444
444
|
|
|
445
445
|
def test_process_cancel_view_raises_permission_denied_with_missing_permissions(
|
|
446
|
-
self
|
|
446
|
+
self,
|
|
447
447
|
):
|
|
448
448
|
with self.assertRaises(PermissionDenied):
|
|
449
449
|
ProcessCancelView.as_view()(self.get_no_permissions, pk=self.process.id)
|
|
@@ -474,7 +474,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
474
474
|
self.assertEqual(response.status_code, 302)
|
|
475
475
|
|
|
476
476
|
def test_process_activity_view_raises_permission_denied_with_missing_permissions(
|
|
477
|
-
self
|
|
477
|
+
self,
|
|
478
478
|
):
|
|
479
479
|
next_activity = next(get_current_activities_in_process(self.process))
|
|
480
480
|
|
|
@@ -506,7 +506,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
506
506
|
self.assertEqual(response.status_code, 302)
|
|
507
507
|
|
|
508
508
|
def test_retry_activity_view_raises_permission_denied_with_missing_permissions(
|
|
509
|
-
self
|
|
509
|
+
self,
|
|
510
510
|
):
|
|
511
511
|
with self.assertRaises(PermissionDenied):
|
|
512
512
|
ActivityRetryView.as_view()(
|
|
@@ -522,7 +522,7 @@ class ProcesslibViewPermissionTest(TestCase):
|
|
|
522
522
|
self.assertEqual(response.status_code, 302)
|
|
523
523
|
|
|
524
524
|
def test_cancel_activity_view_raises_permission_denied_with_missing_permissions(
|
|
525
|
-
self
|
|
525
|
+
self,
|
|
526
526
|
):
|
|
527
527
|
next_activity = next(get_current_activities_in_process(self.process))
|
|
528
528
|
with self.assertRaises(PermissionDenied):
|
|
@@ -762,6 +762,56 @@ class ProcesslibViewTest(TestCase):
|
|
|
762
762
|
self.assertContains(response, str(self.process.pk))
|
|
763
763
|
|
|
764
764
|
|
|
765
|
+
end_direct_test_flow = (
|
|
766
|
+
Flow("end_direct_test_flow")
|
|
767
|
+
.start_with("start", StartActivity)
|
|
768
|
+
.and_then(
|
|
769
|
+
"success-view",
|
|
770
|
+
ViewActivity,
|
|
771
|
+
view=ProcessUpdateView.as_view(
|
|
772
|
+
fields=[],
|
|
773
|
+
success_url=lambda a: "/custom/{}".format(a),
|
|
774
|
+
),
|
|
775
|
+
)
|
|
776
|
+
.and_then("end", EndActivity)
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
class ProcesslibRedirectViewTest(TestCase):
|
|
781
|
+
def setUp(self):
|
|
782
|
+
self.user = User.objects.create(username="testuser")
|
|
783
|
+
self.user.set_password("password")
|
|
784
|
+
self.user.save()
|
|
785
|
+
|
|
786
|
+
self.start = end_direct_test_flow.get_start_activity()
|
|
787
|
+
self.start.start()
|
|
788
|
+
self.start.finish()
|
|
789
|
+
self.process = self.start.process
|
|
790
|
+
self.next_activity = next(get_current_activities_in_process(self.process))
|
|
791
|
+
|
|
792
|
+
self.get = RequestFactory().get("/")
|
|
793
|
+
self.get.user = self.user
|
|
794
|
+
self.client.login(username="testuser", password="password")
|
|
795
|
+
|
|
796
|
+
def test_end_redirect(self):
|
|
797
|
+
activity_instance = self.next_activity.instance
|
|
798
|
+
url = reverse(
|
|
799
|
+
"processlib:process-activity",
|
|
800
|
+
kwargs={
|
|
801
|
+
"flow_label": self.process.flow_label,
|
|
802
|
+
"activity_id": activity_instance.id,
|
|
803
|
+
},
|
|
804
|
+
)
|
|
805
|
+
response = self.client.post(url, {"_finish_go_to_next": True})
|
|
806
|
+
|
|
807
|
+
self.assertEqual(response.status_code, 302)
|
|
808
|
+
self.assertEqual(response.headers["location"], "/custom/success-view")
|
|
809
|
+
|
|
810
|
+
self.process.refresh_from_db()
|
|
811
|
+
|
|
812
|
+
self.assertIsNotNone(self.process.finished_at)
|
|
813
|
+
|
|
814
|
+
|
|
765
815
|
class ActivityTest(TestCase):
|
|
766
816
|
def test_function_activity_with_error_records_error(self):
|
|
767
817
|
function_error_flow = (
|
|
@@ -798,9 +848,9 @@ class ActivityTest(TestCase):
|
|
|
798
848
|
activity.instance.assigned_group = Group.objects.create(name="side-effect")
|
|
799
849
|
activity.instance.save()
|
|
800
850
|
|
|
801
|
-
function_error_retry_flow._activity_kwargs["function"][
|
|
802
|
-
|
|
803
|
-
|
|
851
|
+
function_error_retry_flow._activity_kwargs["function"]["callback"] = (
|
|
852
|
+
working_callback
|
|
853
|
+
)
|
|
804
854
|
|
|
805
855
|
activity_instance.activity.retry()
|
|
806
856
|
activity_instance.refresh_from_db()
|
|
@@ -13,6 +13,10 @@ from processlib.views import (
|
|
|
13
13
|
ProcessCancelView,
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
app_name = "processlib"
|
|
18
|
+
|
|
19
|
+
|
|
16
20
|
urlpatterns = [
|
|
17
21
|
re_path(r"^process/$", ProcessListView.as_view(), name="process-list"),
|
|
18
22
|
re_path(
|
|
@@ -20,7 +24,9 @@ urlpatterns = [
|
|
|
20
24
|
UserCurrentProcessListView.as_view(),
|
|
21
25
|
name="process-list-user-current",
|
|
22
26
|
),
|
|
23
|
-
re_path(
|
|
27
|
+
re_path(
|
|
28
|
+
r"^process/user/$", UserProcessListView.as_view(), name="process-list-user"
|
|
29
|
+
),
|
|
24
30
|
re_path(
|
|
25
31
|
r"^process/start/(?P<flow_label>.*)/$",
|
|
26
32
|
ProcessStartView.as_view(),
|
|
@@ -46,7 +52,9 @@ urlpatterns = [
|
|
|
46
52
|
ActivityRetryView.as_view(),
|
|
47
53
|
name="activity-retry",
|
|
48
54
|
),
|
|
49
|
-
re_path(
|
|
55
|
+
re_path(
|
|
56
|
+
r"^process/(?P<pk>.*)/$", ProcessDetailView.as_view(), name="process-detail"
|
|
57
|
+
),
|
|
50
58
|
re_path(
|
|
51
59
|
r"^process/(?P<pk>.*)/cancel$",
|
|
52
60
|
ProcessCancelView.as_view(),
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import six
|
|
2
1
|
from django.contrib import messages
|
|
3
2
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
|
4
3
|
from django.db.models import Q
|
|
@@ -58,7 +57,7 @@ class ProcessListView(CurrentAppMixin, ListView):
|
|
|
58
57
|
def get_queryset(self):
|
|
59
58
|
qs = super(ProcessListView, self).get_queryset()
|
|
60
59
|
qs = self.filter_queryset(qs)
|
|
61
|
-
return qs.filter(get_permission_filter(self.request.user))
|
|
60
|
+
return qs.filter(get_permission_filter(self.request.user)).distinct()
|
|
62
61
|
|
|
63
62
|
def get_search_query(self):
|
|
64
63
|
return self.request.GET.get("search", "").strip()
|
|
@@ -273,6 +272,7 @@ class ActivityMixin(CurrentAppMixin):
|
|
|
273
272
|
Mixin used by view activities, e.g. those defined as an ActivityView.
|
|
274
273
|
"""
|
|
275
274
|
|
|
275
|
+
success_url = None
|
|
276
276
|
activity = None
|
|
277
277
|
_finish_go_to_next = False
|
|
278
278
|
|
|
@@ -323,9 +323,7 @@ class ActivityMixin(CurrentAppMixin):
|
|
|
323
323
|
if self.activity.instance.status == self.activity.instance.STATUS_DONE:
|
|
324
324
|
messages.info(
|
|
325
325
|
request,
|
|
326
|
-
_("The activity {} has already been done.").format(
|
|
327
|
-
six.text_type(self.activity)
|
|
328
|
-
),
|
|
326
|
+
_("The activity {} has already been done.").format(str(self.activity)),
|
|
329
327
|
)
|
|
330
328
|
return HttpResponseRedirect(
|
|
331
329
|
reverse(
|
|
@@ -356,6 +354,13 @@ class ActivityMixin(CurrentAppMixin):
|
|
|
356
354
|
},
|
|
357
355
|
current_app=self.get_current_app(),
|
|
358
356
|
)
|
|
357
|
+
|
|
358
|
+
if self.success_url is not None:
|
|
359
|
+
if callable(self.success_url):
|
|
360
|
+
return self.success_url(self.activity)
|
|
361
|
+
else:
|
|
362
|
+
return str(self.success_url)
|
|
363
|
+
|
|
359
364
|
return reverse(
|
|
360
365
|
"processlib:process-detail",
|
|
361
366
|
args=(self.activity.process.id,),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: processlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: A workflow library for python
|
|
5
5
|
Home-page: https://github.com/RaphaelKimmig/processlib
|
|
6
|
-
Download-URL: https://github.com/RaphaelKimmig/processlib/archive/0.
|
|
6
|
+
Download-URL: https://github.com/RaphaelKimmig/processlib/archive/0.10.0.tar.gz
|
|
7
7
|
Author: Raphael Kimmig
|
|
8
8
|
Author-email: raphael@ampad.de
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: django>=3.2
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="processlib",
|
|
5
|
+
version="0.10.0",
|
|
6
|
+
url="https://github.com/RaphaelKimmig/processlib",
|
|
7
|
+
download_url="https://github.com/RaphaelKimmig/processlib/archive/0.10.0.tar.gz",
|
|
8
|
+
author="Raphael Kimmig",
|
|
9
|
+
author_email="raphael@ampad.de",
|
|
10
|
+
description="A workflow library for python",
|
|
11
|
+
include_package_data=True,
|
|
12
|
+
packages=find_packages(),
|
|
13
|
+
install_requires=[
|
|
14
|
+
"django >= 3.2",
|
|
15
|
+
],
|
|
16
|
+
)
|
processlib-0.9.0/setup.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from setuptools import setup, find_packages
|
|
2
|
-
|
|
3
|
-
setup(
|
|
4
|
-
name='processlib',
|
|
5
|
-
version='0.9.0',
|
|
6
|
-
url='https://github.com/RaphaelKimmig/processlib',
|
|
7
|
-
download_url='https://github.com/RaphaelKimmig/processlib/archive/0.9.0.tar.gz',
|
|
8
|
-
author='Raphael Kimmig',
|
|
9
|
-
author_email='raphael@ampad.de',
|
|
10
|
-
description='A workflow library for python',
|
|
11
|
-
include_package_data=True,
|
|
12
|
-
packages=find_packages(),
|
|
13
|
-
install_requires=['django >= 3.2', ],
|
|
14
|
-
)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_details_partial.html
RENAMED
|
File without changes
|
|
File without changes
|
{processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_list_item.html
RENAMED
|
File without changes
|
{processlib-0.9.0 → processlib-0.10.0}/processlib/templates/processlib/process_to_do_partial.html
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
|
|
File without changes
|