20 calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery |
20 calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery |
21 calendar). You can of course also write your own widget. |
21 calendar). You can of course also write your own widget. |
22 |
22 |
23 |
23 |
24 .. automodule:: cubicweb.web.views.autoform |
24 .. automodule:: cubicweb.web.views.autoform |
|
25 |
|
26 Anatomy of a choices function |
|
27 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
28 |
|
29 Let's have a look at the `ticket_done_in_choices` function given to |
|
30 the `choices` parameter of the relation tag that is applied to the |
|
31 ('Ticket', 'done_in', '*') relation definition, as it is both typical |
|
32 and sophisticated enough. This is a code snippet from the `tracker`_ |
|
33 cube. |
|
34 |
|
35 .. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker |
|
36 |
|
37 The ``Ticket`` entity type can be related to a ``Project`` and a |
|
38 ``Version``, respectively through the ``concerns`` and ``done_in`` |
|
39 relations. When a user is about to edit a ticket, we want to fill the |
|
40 combo box for the ``done_in`` relation with values pertinent with |
|
41 respect to the context. The important context here is: |
|
42 |
|
43 * creation or modification (we cannot fetch values the same way in |
|
44 either case) |
|
45 |
|
46 * ``__linkto`` url parameter given in a creation context |
|
47 |
|
48 .. sourcecode:: python |
|
49 |
|
50 from cubicweb.web import formfields |
|
51 |
|
52 def ticket_done_in_choices(form, field): |
|
53 entity = form.edited_entity |
|
54 # first see if its specified by __linkto form parameters |
|
55 linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject') |
|
56 if linkedto: |
|
57 return linkedto |
|
58 # it isn't, get initial values |
|
59 vocab = formfields.relvoc_init(entity, 'done_in', 'subject') |
|
60 veid = None |
|
61 # try to fetch the (already or pending) related version and project |
|
62 if not entity.has_eid(): |
|
63 peids = entity.linked_to('concerns', 'subject') |
|
64 peid = peids and peids[0] |
|
65 else: |
|
66 peid = entity.project.eid |
|
67 veid = entity.done_in and entity.done_in[0].eid |
|
68 if peid: |
|
69 # we can complete the vocabulary with relevant values |
|
70 rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version') |
|
71 rset = form._cw.execute( |
|
72 'Any V, VN ORDERBY version_sort_value(VN) ' |
|
73 'WHERE V version_of P, P eid %(p)s, V num VN, ' |
|
74 'V in_state ST, NOT ST name "published"', {'p': peid}, 'p') |
|
75 vocab += [(v.view('combobox'), v.eid) for v in rset.entities() |
|
76 if rschema.has_perm(form._cw, 'add', toeid=v.eid) |
|
77 and v.eid != veid] |
|
78 return vocab |
|
79 |
|
80 The first thing we have to do is fetch potential values from the |
|
81 ``__linkto`` url parameter that is often found in entity creation |
|
82 contexts (the creation action provides such a parameter with a |
|
83 predetermined value; for instance in this case, ticket creation could |
|
84 occur in the context of a `Version` entity). The |
|
85 :mod:`cubicweb.web.formfields` module provides a ``relvoc_linkedto`` |
|
86 utility function that gets a list suitably filled with vocabulary |
|
87 values. |
|
88 |
|
89 .. sourcecode:: python |
|
90 |
|
91 linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject') |
|
92 if linkedto: |
|
93 return linkedto |
|
94 |
|
95 Then, if no ``__linkto`` argument was given, we must prepare the |
|
96 vocabulary with an initial empty value (because `done_in` is not |
|
97 mandatory, we must allow the user to not select a verson) and already |
|
98 linked values. This is done with the ``relvoc_init`` function. |
|
99 |
|
100 .. sourcecode:: python |
|
101 |
|
102 vocab = formfields.relvoc_init(entity, 'done_in', 'subject') |
|
103 |
|
104 But then, we have to give more: if the ticket is related to a project, |
|
105 we should provide all the non published versions of this project |
|
106 (`Version` and `Project` can be related through the `version_of` |
|
107 relation). Conversely, if we do not know yet the project, it would not |
|
108 make sense to propose all existing versions as it could potentially |
|
109 lead to incoherences. Even if these will be caught by some |
|
110 RQLConstraint, it is wise not to tempt the user with error-inducing |
|
111 candidate values. |
|
112 |
|
113 The "ticket is related to a project" part must be decomposed as: |
|
114 |
|
115 * this is a new ticket which is created is the context of a project |
|
116 |
|
117 * this is an already existing ticket, linked to a project (through the |
|
118 `concerns` relation) |
|
119 |
|
120 * there is no related project (quite unlikely given the cardinality of |
|
121 the `concerns` relation, so it can only mean that we are creating a |
|
122 new ticket, and a project is about to be selected but there is no |
|
123 ``__linkto`` argument) |
|
124 |
|
125 .. note:: |
|
126 |
|
127 the last situation could happen in several ways, but of course in a |
|
128 polished application, the paths to ticket creation should be |
|
129 controlled so as to avoid a suboptimal end-user experience |
|
130 |
|
131 Hence, we try to fetch the related project. |
|
132 |
|
133 .. sourcecode:: python |
|
134 |
|
135 veid = None |
|
136 if not entity.has_eid(): |
|
137 peids = entity.linked_to('concerns', 'subject') |
|
138 peid = peids and peids[0] |
|
139 else: |
|
140 peid = entity.project.eid |
|
141 veid = entity.done_in and entity.done_in[0].eid |
|
142 |
|
143 We distinguish between entity creation and entity modification using |
|
144 the ``Entity.has_eid()`` method, which returns `False` on creation. At |
|
145 creation time the only way to get a project is through the |
|
146 ``__linkto`` parameter. Notice that we fetch the version in which the |
|
147 ticket is `done_in` if any, for later. |
|
148 |
|
149 .. note:: |
|
150 |
|
151 the implementation above assumes that if there is a ``__linkto`` |
|
152 parameter, it is only about a project. While it makes sense most of |
|
153 the time, it is not an absolute. Depending on how an entity creation |
|
154 action action url is built, several outcomes could be possible |
|
155 there |
|
156 |
|
157 If the ticket is already linked to a project, fetching it is |
|
158 trivial. Then we add the relevant version to the initial vocabulary. |
|
159 |
|
160 .. sourcecode:: python |
|
161 |
|
162 if peid: |
|
163 rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version') |
|
164 rset = form._cw.execute( |
|
165 'Any V, VN ORDERBY version_sort_value(VN) ' |
|
166 'WHERE V version_of P, P eid %(p)s, V num VN, ' |
|
167 'V in_state ST, NOT ST name "published"', {'p': peid}) |
|
168 vocab += [(v.view('combobox'), v.eid) for v in rset.entities() |
|
169 if rschema.has_perm(form._cw, 'add', toeid=v.eid) |
|
170 and v.eid != veid] |
|
171 |
|
172 .. warning:: |
|
173 |
|
174 we have to defend ourselves against lack of a project eid. Given |
|
175 the cardinality of the `concerns` relation, there *must* be a |
|
176 project, but this rule can only be enforced at validation time, |
|
177 which will happen of course only after form subsmission |
|
178 |
|
179 Here, given a project eid, we complete the vocabulary with all |
|
180 unpublished versions defined in the project (sorted by number) for |
|
181 which the current user is allowed to establish the relation. Of |
|
182 course, we take care *not* to provide a version the ticket is already |
|
183 linked to (through ``done_in``). |
25 |
184 |
26 |
185 |
27 Example of ad-hoc fields form |
186 Example of ad-hoc fields form |
28 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
187 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
29 |
188 |