Skip to content

Commit 8851196

Browse files
committed
Ref #19 -- Add create_task documentation
1 parent 8d6d016 commit 8851196

File tree

9 files changed

+132
-7
lines changed

9 files changed

+132
-7
lines changed

docs/inbox.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=====
2+
Inbox
3+
=====
4+
5+
In a multi user workflow it can become handy to assign and reassign tasks and to have
6+
some kind of inbox to keep track of incomplete tasks.
7+
8+
Automatic assignment
9+
--------------------
10+
11+
To implement a default assignee you need to override implement your own
12+
:meth:`create_task` method.
13+
14+
15+
.. code-block:: python
16+
17+
from joeflow import tasks
18+
19+
20+
class UpdateWithPrevUserView(tasks.UpdateView):
21+
22+
def create_task(self, workflow, prev_task):
23+
"""Assign a new task to the user who completed the previous task."""
24+
new_task = workflow.task_set.create(
25+
workflow=workflow,
26+
name=self.name,
27+
type=self.type,
28+
)
29+
if prev_task.completed_by_user:
30+
# If the previous tasks was a machine task, no user is set.
31+
new_task.assignees.add(prev_task.completed_by_user)
32+
return new_task
33+
34+
35+
In this implementation the new task is assigned to the user who completed the previous
36+
task. The current workflow and the previous task are always given :meth:`create_task`
37+
method. Note, that a request is not available, since this method might not be called
38+
within a view but in a machine task. The parent task relation must not be created as a
39+
part of the :meth:`create_task` method.
40+
41+
API
42+
===
43+
44+
.. automethod:: joeflow.views.TaskViewMixin.create_task

joeflow/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ def start_next_tasks(self, next_nodes: list = None):
571571
for node in next_nodes:
572572
try:
573573
# Some nodes – like Join – implement their own method to create new tasks.
574-
task = node.create_task(self.workflow)
574+
task = node.create_task(self.workflow, self)
575575
except AttributeError:
576576
task = self.workflow.task_set.create(
577577
name=node.name, type=node.type, workflow=self.workflow

joeflow/tasks/machine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def __init__(self, *parents: Iterable[str]):
108108
def __call__(self, workflow, task):
109109
return set(task.parent_task_set.values_list("name", flat=True)) == self.parents
110110

111-
def create_task(self, workflow):
111+
def create_task(self, workflow, prev_task):
112112
return workflow.task_set.get_or_create(
113113
name=self.name,
114114
type=self.type,

joeflow/views.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,28 @@ def form_valid(self, form):
7474
self.next_task()
7575
return response
7676

77+
def create_task(self, workflow, prev_task):
78+
"""
79+
Return a new database instance of this task.
80+
81+
The factory method should be overridden, to create custom database instances
82+
based on the task node's class, the workflow or the previous task.
83+
84+
Note:
85+
This is not a view method, a request via ``self.request`` is not available.
86+
87+
Args:
88+
workflow (joeflow.models.Workflow): Current workflow instance.
89+
prev_task (joeflow.models.Task): Instance of the previous Task.
90+
91+
Returns:
92+
joeflow.models.Task: New task instance.
93+
94+
"""
95+
return workflow.task_set.create(
96+
name=self.name, type=self.type, workflow=workflow
97+
)
98+
7799

78100
class WorkflowDetailView(WorkflowTemplateNameViewMixin, generic.DetailView):
79101
pass

tests/tasks/test_machine.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ def test_create_task(self, db):
3030
node = tasks.Join()
3131
node.name = "test"
3232
node.type = "machine"
33-
obj = node.create_task(wf)
34-
obj2 = node.create_task(wf)
33+
obj = node.create_task(wf, None)
34+
obj2 = node.create_task(wf, None)
3535
assert obj == obj2
3636
obj.finish()
37-
obj3 = node.create_task(wf)
37+
obj3 = node.create_task(wf, None)
3838
assert obj != obj3
3939

4040

tests/test_views.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from django.urls import reverse
22

3-
from .testapp import models
3+
from .testapp import models, workflows
44

55

66
class TestTaskViewMixin:
7-
def test_create_task__form_errors(self, db, admin_client, admin_user):
7+
def test_next_task__form_errors(self, db, admin_client, admin_user):
88
url = reverse("shippingworkflow:checkout")
99
response = admin_client.post(
1010
url, data={"shipping_address": 123, "email": "not a email"}
@@ -15,3 +15,27 @@ def test_create_task__form_errors(self, db, admin_client, admin_user):
1515
}
1616

1717
assert models.Shipment.objects.count() == 0
18+
19+
def test_create_task(self, db, admin_client, admin_user):
20+
url = reverse("simpleworkflow:start_view")
21+
response = admin_client.post(url)
22+
assert response.status_code == 302
23+
24+
wf = workflows.SimpleWorkflow.objects.get()
25+
26+
assert wf.task_set.count() == 2
27+
new_task = wf.task_set.get(name="save_the_princess")
28+
assert new_task.name == "save_the_princess"
29+
assert new_task.type == "human"
30+
assert not new_task.assignees.all()
31+
32+
def test_custom_create_task(self, db, admin_client, admin_user):
33+
url = reverse("assigneeworkflow:start_view")
34+
response = admin_client.post(url)
35+
assert response.status_code == 302
36+
37+
wf = workflows.AssigneeWorkflow.objects.get()
38+
39+
assert wf.task_set.count() == 2
40+
new_task = wf.task_set.get(name="save_the_princess")
41+
assert admin_user in new_task.assignees.all()

tests/testapp/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
path("admin/", admin.site.urls),
2323
path("shipment/", include(workflows.ShippingWorkflow.urls())),
2424
path("simple/", include(workflows.SimpleWorkflow.urls())),
25+
path("assignee/", include(workflows.AssigneeWorkflow.urls())),
2526
path("gateway/", include(workflows.GatewayWorkflow.urls())),
2627
path("splitjoin/", include(workflows.SplitJoinWorkflow.urls())),
2728
path("loop/", include(workflows.LoopWorkflow.urls())),

tests/testapp/views.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from joeflow import tasks
2+
3+
4+
class UpdateWithPrevUserView(tasks.UpdateView):
5+
def create_task(self, workflow, prev_task):
6+
"""Assign a new task to the user who completed the previous task."""
7+
new_task = workflow.task_set.create(
8+
workflow=workflow,
9+
name=self.name,
10+
type=self.type,
11+
)
12+
if prev_task.completed_by_user:
13+
# If the previous tasks was a machine task, no user is set.
14+
new_task.assignees.add(prev_task.completed_by_user)
15+
return new_task

tests/testapp/workflows.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from joeflow import tasks
66

77
from . import models
8+
from .views import UpdateWithPrevUserView
89

910

1011
class ShippingWorkflow(models.Shipment):
@@ -86,6 +87,24 @@ class Meta:
8687
proxy = True
8788

8889

90+
class AssigneeWorkflow(models.SimpleWorkflowState):
91+
start_view = tasks.StartView(fields="__all__", path="custom/postfix/")
92+
start_method = tasks.Start()
93+
save_the_princess = UpdateWithPrevUserView(fields="__all__")
94+
95+
def end(self):
96+
pass
97+
98+
edges = (
99+
(start_view, save_the_princess),
100+
(start_method, save_the_princess),
101+
(save_the_princess, end),
102+
)
103+
104+
class Meta:
105+
proxy = True
106+
107+
89108
class GatewayWorkflow(models.GatewayWorkflowState):
90109
start = tasks.StartView(fields="__all__")
91110

0 commit comments

Comments
 (0)