6 General |
6 General |
7 ------- |
7 ------- |
8 |
8 |
9 A workflow describes how certain entities have to evolve between |
9 A workflow describes how certain entities have to evolve between |
10 different states. Hence we have a set of states, and a "transition graph", |
10 different states. Hence we have a set of states, and a "transition graph", |
11 i.e. a list of possible transitions from one state to another state. |
11 i.e. a set of possible transitions from one state to another state. |
12 |
12 |
13 We will define a simple workflow for a blog, with only the following |
13 We will define a simple workflow for a blog, with only the following |
14 two states: `submitted` and `published`. So first, we create a simple |
14 two states: `submitted` and `published`. So first, we create a simple |
15 *CubicWeb* in ten minutes (see :ref:`BlogFiveMinutes`). |
15 *CubicWeb* instance in ten minutes (see :ref:`BlogFiveMinutes`). |
16 |
16 |
17 Set-up a workflow |
17 Set-up a workflow |
18 ----------------- |
18 ----------------- |
19 |
19 |
20 We want to create a workflow to control the quality of the BlogEntry |
20 We want to create a workflow to control the quality of the BlogEntry |
21 submitted on your instance. When a BlogEntry is created by a user |
21 submitted on the instance. When a BlogEntry is created by a user |
22 its state should be `submitted`. To be visible to all, it has to |
22 its state should be `submitted`. To be visible to all, it has to |
23 be in the state `published`. To move it from `submitted` to `published`, |
23 be in the state `published`. To move it from `submitted` to `published`, |
24 we need a transition that we can call `approve_blogentry`. |
24 we need a transition that we can call `approve_blogentry`. |
25 |
25 |
26 A BlogEntry state should not be modifiable by every user. |
26 A BlogEntry state should not be modifiable by every user. |
27 So we have to define a group of users, `moderators`, and |
27 So we have to define a group of users, `moderators`, and |
28 this group will have appropriate permissions to publish a BlogEntry. |
28 this group will have appropriate permissions to publish a BlogEntry. |
29 |
29 |
30 There are two ways to create a workflow: from the user interface, |
30 There are two ways to create a workflow: from the user interface, or |
31 or by defining it in ``migration/postcreate.py``. |
31 by defining it in ``migration/postcreate.py``. This script is executed |
32 This script is executed each time a new ``cubicweb-ctl db-init`` is done. |
32 each time a new ``cubicweb-ctl db-init`` is done. We strongly |
33 We strongly recommend to create the workflow in ``migration/postcreate.py`` |
33 recommend to create the workflow in ``migration/postcreate.py`` and we |
34 and we will now show you how. Read `Under the hood`_ to understand why. |
34 will now show you how. Read `Two bits of warning`_ to understand why. |
35 |
35 |
36 The state of a entity is managed by the `in_state` attribute which can be added to you entity schema by two ways: |
36 The state of an entity is managed by the `in_state` attribute which |
|
37 can be added to your entity schema by inheriting from |
|
38 `cubicweb.schema.WorkflowableEntityType`. |
37 |
39 |
38 * direct inheritance by subclassing your class from `cubicweb.schema.WorkflowableEntityType` |
|
39 * by delegation using `cubicweb.schema.make_worflowable` (usable as a decorator) |
|
40 |
40 |
41 About our example of BlogEntry, we must have: |
41 About our example of BlogEntry, we must have: |
42 |
42 |
43 .. sourcecode:: python |
43 .. sourcecode:: python |
44 |
44 |
45 from cubicweb.schema import WorkflowableEntityType |
45 from cubicweb.schema import WorkflowableEntityType |
46 |
46 |
47 class BlogEntry(EntityType, WorkflowableEntityType): |
47 class BlogEntry(WorkflowableEntityType): |
48 ... |
48 ... |
49 |
49 |
50 |
50 |
51 Create states, transitions and group permissions |
51 Create states, transitions and group permissions |
52 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
52 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
53 |
53 |
54 The ``postcreate.py`` script is executed in a special environment, adding |
54 The ``postcreate.py`` script is executed in a special environment, adding |
55 several *CubicWeb* primitives that can be used. |
55 several *CubicWeb* primitives that can be used. |
|
56 |
56 They are all defined in the ``class ServerMigrationHelper``. |
57 They are all defined in the ``class ServerMigrationHelper``. |
57 We will only discuss the methods we use to create a workflow in this example. |
58 We will only discuss the methods we use to create a workflow in this example. |
58 |
59 |
59 To define our workflow for BlogDemo, please add the following lines |
60 A workflow is a collection of entities of type ``State`` and of type |
|
61 ``Transition`` which are standard *CubicWeb* entity types. |
|
62 |
|
63 To define a workflow for BlogDemo, please add the following lines |
60 to ``migration/postcreate.py``: |
64 to ``migration/postcreate.py``: |
61 |
65 |
62 .. sourcecode:: python |
66 .. sourcecode:: python |
63 |
67 |
64 _ = unicode |
68 _ = unicode |
67 |
71 |
68 This adds the `moderators` user group. |
72 This adds the `moderators` user group. |
69 |
73 |
70 .. sourcecode:: python |
74 .. sourcecode:: python |
71 |
75 |
72 wf = add_workflow(u'your workflow description', 'BlogEntry') |
76 wf = add_workflow(u'blog publication workflow', 'BlogEntry') |
73 |
77 |
74 At first, instanciate a new workflow object with a gentle description and the concerned entity types (this one can be a tuple for multiple value). |
78 At first, instanciate a new workflow object with a gentle description |
|
79 and the concerned entity types (this one can be a tuple for multiple |
|
80 value). |
75 |
81 |
76 .. sourcecode:: python |
82 .. sourcecode:: python |
77 |
83 |
78 submitted = wf.add_state(_('submitted'), initial=True) |
84 submitted = wf.add_state(_('submitted'), initial=True) |
79 published = wf.add_state(_('published')) |
85 published = wf.add_state(_('published')) |
80 |
86 |
81 ``add_state`` expects as first argument the name of the state you want to create and an optional argument to say if it is supposed to be the initial state of the entity type. |
87 This will create two entities of type ``State``, one with name |
|
88 'submitted', and the other with name 'published'. |
|
89 |
|
90 ``add_state`` expects as first argument the name of the state you want |
|
91 to create and an optional argument to say if it is supposed to be the |
|
92 initial state of the entity type. |
82 |
93 |
83 .. sourcecode:: python |
94 .. sourcecode:: python |
84 |
95 |
85 wf.add_transition(_('approve_blogentry'), (submitted,), published, ('moderators', 'managers'),) |
96 wf.add_transition(_('approve_blogentry'), (submitted,), published, ('moderators', 'managers'),) |
86 |
97 |
|
98 This will create an entity of type ``Transition`` with name |
|
99 `approve_blogentry` which will be linked to the ``State`` entities |
|
100 created before. |
87 |
101 |
88 ``add_transition`` expects |
102 ``add_transition`` expects |
89 |
103 |
90 * as the first argument the name of the transition |
104 * as the first argument: the name of the transition |
91 * then the list of states on which the transition can be triggered, |
105 * then the list of states on which the transition can be triggered, |
92 * the target state of the transition, |
106 * the target state of the transition, |
93 * and the permissions |
107 * and the permissions |
94 (e.g. a list of user groups who can apply the transition; the user |
108 (e.g. a list of user groups who can apply the transition; the user |
95 has to belong to at least one of the listed group to perform the action). |
109 has to belong to at least one of the listed group to perform the action). |
100 |
114 |
101 .. note:: |
115 .. note:: |
102 Do not forget to add the `_()` in front of all states and transitions names while creating |
116 Do not forget to add the `_()` in front of all states and transitions names while creating |
103 a workflow so that they will be identified by the i18n catalog scripts. |
117 a workflow so that they will be identified by the i18n catalog scripts. |
104 |
118 |
105 In addition to the user group conditions which the user needs to belong to one of those, we could have added a RQL condition. |
119 In addition to the user groups (one of which the user needs to belong |
106 In this case, the user can only perform the action if the two conditions are satisfied. |
120 to), we could have added a RQL condition. In this case, the user can |
|
121 only perform the action if the two conditions are satisfied. |
107 |
122 |
108 If we use a RQL condition on a transition, we can use the following variables: |
123 If we use an RQL condition on a transition, we can use the following variables: |
109 |
124 |
110 * `%(eid)s`, object's eid |
125 * `%(eid)s`, object's eid |
111 * `%(ueid)s`, user executing the query eid |
126 * `%(ueid)s`, user executing the query eid |
112 * `%(seid)s`, the object's current state eid |
127 * `%(seid)s`, the object's current state eid |
113 |
128 |
114 |
129 |
115 .. image:: ../../images/03-transitions-view.en.png |
130 .. image:: ../../images/03-transitions-view.en.png |
116 |
131 |
117 You can notice that in the action box of a BlogEntry, the state |
132 You can notice that in the action box of a BlogEntry, the state is now |
118 is now listed as well as the possible transitions for the current state defined by the workflow. |
133 listed as well as the possible transitions for the current state |
|
134 defined by the workflow. |
|
135 |
119 The transitions will only be displayed for users having the right permissions. |
136 The transitions will only be displayed for users having the right permissions. |
120 In our example, the transition `approve_blogentry` will only be displayed |
137 In our example, the transition `approve_blogentry` will only be displayed |
121 for the users belonging to the group `moderators` or `managers`. |
138 for the users belonging to the group `moderators` or `managers`. |
122 |
139 |
123 |
140 |
124 Under the hood |
141 Two bits of warning |
125 ~~~~~~~~~~~~~~ |
142 ~~~~~~~~~~~~~~~~~~~ |
126 |
143 |
127 A workflow is a collection of entities of type ``State`` and of type ``Transition`` |
144 We could perfectly use the administration interface to do these |
128 which are standard *CubicWeb* entity types. |
145 operations. It is a convenient thing to do at times (when doing |
|
146 development, to quick-check things). But it is not recommended beyond |
|
147 that because it is a bit complicated to do it right and it will be |
|
148 only local to your instance (or, said a bit differently, such a |
|
149 workflow only exists in an instance database). Furthermore, you cannot |
|
150 write unit tests against deployed instances, and experience shows it |
|
151 is mandatory to have tests for any mildly complicated workflow |
|
152 setup. |
129 |
153 |
130 For instance, the preceding lines: |
154 Indeed, if you create the states and transitions through the user |
131 |
155 interface, next time you initialize the database you will have to |
132 .. sourcecode:: python |
156 re-create all the workflow entities. The user interface should only be |
133 |
157 a reference for you to view the states and transitions, but is not the |
134 submitted = wf.add_state(_('submitted'), initial=True) |
158 appropriate interface to define your application workflow. |
135 published = wf.add_state(_('published')) |
|
136 |
|
137 will create two entities of type ``State``, one with name 'submitted', and the other |
|
138 with name 'published'. Whereas: |
|
139 |
|
140 .. sourcecode:: python |
|
141 |
|
142 wf.add_transition(_('approve_blogentry'), (submitted,), published, ('moderators', 'managers'),) |
|
143 |
|
144 will create an entity of type ``Transition`` with name `approve_blogentry` which will |
|
145 be linked to the ``State`` entities created before. |
|
146 As a consequence, we could use the administration interface to do these operations. But it is not recommended because it will be uselessly complicated and will be only local to your instance. |
|
147 |
|
148 Indeed, if you create the states and transitions through the user interface, next time you initialize the database you will have to re-create all the entities. |
|
149 The user interface should only be a reference for you to view the states and transitions, but is not the appropriate interface to define your application workflow. |
|
150 |
|
151 |
|