43 """ |
43 """ |
44 __docformat__ = "restructuredtext en" |
44 __docformat__ = "restructuredtext en" |
45 |
45 |
46 from warnings import warn |
46 from warnings import warn |
47 |
47 |
48 from logilab.common import dictattr |
48 from logilab.common import dictattr, tempattr |
49 from logilab.common.decorators import iclassmethod |
49 from logilab.common.decorators import iclassmethod |
50 from logilab.common.compat import any |
50 from logilab.common.compat import any |
|
51 from logilab.common.textutils import splitstrip |
51 from logilab.common.deprecation import deprecated |
52 from logilab.common.deprecation import deprecated |
52 |
53 |
53 from cubicweb import typed_eid |
54 from cubicweb import ValidationError, typed_eid |
54 from cubicweb.utils import support_args |
55 from cubicweb.utils import support_args |
55 from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset |
56 from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset |
|
57 from cubicweb.web import RequestError, ProcessFormError |
56 from cubicweb.web import uicfg, form, formwidgets as fwdgs |
58 from cubicweb.web import uicfg, form, formwidgets as fwdgs |
57 from cubicweb.web.formfields import relvoc_unrelated, guess_field |
59 from cubicweb.web.formfields import relvoc_unrelated, guess_field |
58 |
60 |
59 |
61 |
60 class FieldsForm(form.Form): |
62 class FieldsForm(form.Form): |
123 |
125 |
124 **Form rendering methods** |
126 **Form rendering methods** |
125 |
127 |
126 .. automethod:: cubicweb.web.views.forms.FieldsForm.render |
128 .. automethod:: cubicweb.web.views.forms.FieldsForm.render |
127 |
129 |
|
130 **Form posting methods** |
|
131 |
|
132 Once a form is posted, you can retrieve the form on the controller side and |
|
133 use the following methods to ease processing. For "simple" forms, this |
|
134 should looks like : |
|
135 |
|
136 .. sourcecode :: python |
|
137 |
|
138 form = self._cw.vreg['forms'].select('myformid', self._cw) |
|
139 posted = form.process_posted() |
|
140 # do something with the returned dictionary |
|
141 |
|
142 Notice that form related to entity edition should usually use the |
|
143 `edit` controller which will handle all the logic for you. |
|
144 |
|
145 .. automethod:: cubicweb.web.views.forms.FieldsForm.process_content |
|
146 .. automethod:: cubicweb.web.views.forms.FieldsForm.iter_modified_fields |
128 """ |
147 """ |
129 __regid__ = 'base' |
148 __regid__ = 'base' |
130 |
149 |
131 |
150 |
132 # attributes overrideable by subclasses or through __init__ |
151 # attributes overrideable by subclasses or through __init__ |
216 # use a copy in case fields are modified while context is built (eg |
235 # use a copy in case fields are modified while context is built (eg |
217 # __linkto handling for instance) |
236 # __linkto handling for instance) |
218 for field in self.fields[:]: |
237 for field in self.fields[:]: |
219 for field in field.actual_fields(self): |
238 for field in field.actual_fields(self): |
220 field.form_init(self) |
239 field.form_init(self) |
|
240 # store used field in an hidden input for later usage by a controller |
|
241 fields = set() |
|
242 eidfields = set() |
|
243 for field in self.fields: |
|
244 if field.eidparam: |
|
245 eidfields.add(field.role_name()) |
|
246 elif field.name not in self.control_fields: |
|
247 fields.add(field.role_name()) |
|
248 if fields: |
|
249 self.add_hidden('_cw_fields', u','.join(fields)) |
|
250 if eidfields: |
|
251 self.add_hidden('_cw_entity_fields', u','.join(eidfields), |
|
252 eidparam=True) |
221 |
253 |
222 _default_form_action_path = 'edit' |
254 _default_form_action_path = 'edit' |
223 def form_action(self): |
255 def form_action(self): |
224 try: |
256 try: |
225 action = self.get_action() # avoid spurious warning w/ autoform bw compat property |
257 action = self.get_action() # avoid spurious warning w/ autoform bw compat property |
226 except AttributeError: |
258 except AttributeError: |
227 action = self.action |
259 action = self.action |
228 if action is None: |
260 if action is None: |
229 return self._cw.build_url(self._default_form_action_path) |
261 return self._cw.build_url(self._default_form_action_path) |
230 return action |
262 return action |
|
263 |
|
264 # controller form processing methods ####################################### |
|
265 |
|
266 def iter_modified_fields(self, editedfields=None, entity=None): |
|
267 """return a generator on field that has been modified by the posted |
|
268 form. |
|
269 """ |
|
270 if editedfields is None: |
|
271 try: |
|
272 editedfields = self._cw.form['_cw_fields'] |
|
273 except KeyError: |
|
274 raise RequestError(self._cw._('no edited fields specified')) |
|
275 entityform = entity and self.field_by_name.im_func.func_code.co_argcount == 4 # XXX |
|
276 for editedfield in splitstrip(editedfields): |
|
277 try: |
|
278 name, role = editedfield.split('-') |
|
279 except: |
|
280 name = editedfield |
|
281 role = None |
|
282 if entityform: |
|
283 field = self.field_by_name(name, role, eschema=entity.e_schema) |
|
284 else: |
|
285 field = self.field_by_name(name, role) |
|
286 if field.has_been_modified(self): |
|
287 yield field |
|
288 |
|
289 def process_posted(self): |
|
290 """use this method to process the content posted by a simple form. it |
|
291 will return a dictionary with field names as key and typed value as |
|
292 associated value. |
|
293 """ |
|
294 with tempattr(self, 'formvalues', {}): # init fields value cache |
|
295 errors = [] |
|
296 processed = {} |
|
297 for field in self.iter_modified_fields(): |
|
298 try: |
|
299 for field, value in field.process_posted(self): |
|
300 processed[field.role_name()] = value |
|
301 except ProcessFormError, exc: |
|
302 errors.append((field, exc)) |
|
303 if errors: |
|
304 errors = dict((f.role_name(), unicode(ex)) for f, ex in errors) |
|
305 raise ValidationError(None, errors) |
|
306 return processed |
231 |
307 |
232 @deprecated('[3.6] use .add_hidden(name, value, **kwargs)') |
308 @deprecated('[3.6] use .add_hidden(name, value, **kwargs)') |
233 def form_add_hidden(self, name, value=None, **kwargs): |
309 def form_add_hidden(self, name, value=None, **kwargs): |
234 return self.add_hidden(name, value, **kwargs) |
310 return self.add_hidden(name, value, **kwargs) |
235 |
311 |
321 return '%s#%s' % (self.edited_entity.absolute_url(), self.domid) |
397 return '%s#%s' % (self.edited_entity.absolute_url(), self.domid) |
322 # XXX we should not consider some url parameters that may lead to |
398 # XXX we should not consider some url parameters that may lead to |
323 # different url after a validation error |
399 # different url after a validation error |
324 return '%s#%s' % (self._cw.url(), self.domid) |
400 return '%s#%s' % (self._cw.url(), self.domid) |
325 |
401 |
326 def build_context(self, formvalues=None): |
|
327 if self.formvalues is not None: |
|
328 return # already built |
|
329 super(EntityFieldsForm, self).build_context(formvalues) |
|
330 edited = set() |
|
331 for field in self.fields: |
|
332 if field.eidparam: |
|
333 edited.add(field.role_name()) |
|
334 self.add_hidden('_cw_edited_fields', u','.join(edited), eidparam=True) |
|
335 |
|
336 def default_renderer(self): |
402 def default_renderer(self): |
337 return self._cw.vreg['formrenderers'].select( |
403 return self._cw.vreg['formrenderers'].select( |
338 self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row, |
404 self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row, |
339 col=self.cw_col, entity=self.edited_entity) |
405 col=self.cw_col, entity=self.edited_entity) |
340 |
406 |