--- a/web/test/unittest_application.py Wed Jun 29 18:43:33 2011 +0200
+++ b/web/test/unittest_application.py Wed Jun 29 18:57:23 2011 +0200
@@ -196,7 +196,7 @@
eid = unicode(user.eid)
req.form = {
'eid': eid,
- '__type:'+eid: 'CWUser', '_cw_edited_fields:'+eid: 'login-subject',
+ '__type:'+eid: 'CWUser', '_cw_entity_fields:'+eid: 'login-subject',
'login-subject:'+eid: '', # ERROR: no login specified
# just a sample, missing some necessary information for real life
'__errorurl': 'view?vid=edition...'
@@ -221,11 +221,11 @@
req = self.request()
# set Y before X to ensure both entities are edited, not only X
req.form = {'eid': ['Y', 'X'], '__maineid': 'X',
- '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject',
+ '__type:X': 'CWUser', '_cw_entity_fields:X': 'login-subject',
# missing required field
'login-subject:X': u'',
# but email address is set
- '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject',
+ '__type:Y': 'EmailAddress', '_cw_entity_fields:Y': 'address-subject',
'address-subject:Y': u'bougloup@logilab.fr',
'use_email-object:Y': 'X',
# necessary to get validation error handling
@@ -250,11 +250,11 @@
req = self.request()
# set Y before X to ensure both entities are edited, not only X
req.form = {'eid': ['Y', 'X'], '__maineid': 'X',
- '__type:X': 'CWUser', '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ '__type:X': 'CWUser', '_cw_entity_fields:X': 'login-subject,upassword-subject',
# already existent user
'login-subject:X': u'admin',
'upassword-subject:X': u'admin', 'upassword-subject-confirm:X': u'admin',
- '__type:Y': 'EmailAddress', '_cw_edited_fields:Y': 'address-subject',
+ '__type:Y': 'EmailAddress', '_cw_entity_fields:Y': 'address-subject',
'address-subject:Y': u'bougloup@logilab.fr',
'use_email-object:Y': 'X',
# necessary to get validation error handling
--- a/web/test/unittest_views_basecontrollers.py Wed Jun 29 18:43:33 2011 +0200
+++ b/web/test/unittest_views_basecontrollers.py Wed Jun 29 18:57:23 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -33,7 +33,7 @@
def req_form(user):
return {'eid': [str(user.eid)],
- '_cw_edited_fields:%s' % user.eid: '_cw_generic_field',
+ '_cw_entity_fields:%s' % user.eid: '_cw_generic_field',
'__type:%s' % user.eid: user.__regid__
}
@@ -59,7 +59,7 @@
user = self.user()
req = self.request()
req.form = {'eid': 'X', '__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ '_cw_entity_fields:X': 'login-subject,upassword-subject',
'login-subject:X': u'admin',
'upassword-subject:X': u'toto',
'upassword-subject-confirm:X': u'toto',
@@ -79,7 +79,7 @@
eid = u(user.eid)
req.form = {
'eid': eid, '__type:'+eid: 'CWUser',
- '_cw_edited_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject',
+ '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject,in_group-subject',
'login-subject:'+eid: u(user.login),
'surname-subject:'+eid: u'Th\xe9nault',
'firstname-subject:'+eid: u'Sylvain',
@@ -100,7 +100,7 @@
req.form = {
'eid': eid, '__maineid' : eid,
'__type:'+eid: 'CWUser',
- '_cw_edited_fields:'+eid: 'upassword-subject',
+ '_cw_entity_fields:'+eid: 'upassword-subject',
'upassword-subject:'+eid: 'tournicoton',
'upassword-subject-confirm:'+eid: 'tournicoton',
}
@@ -120,7 +120,7 @@
req.form = {
'eid': eid,
'__type:'+eid: 'CWUser',
- '_cw_edited_fields:'+eid: 'login-subject,firstname-subject,surname-subject',
+ '_cw_entity_fields:'+eid: 'login-subject,firstname-subject,surname-subject',
'login-subject:'+eid: u(user.login),
'firstname-subject:'+eid: u'Th\xe9nault',
'surname-subject:'+eid: u'Sylvain',
@@ -140,14 +140,14 @@
req.form = {'eid': ['X', 'Y'], '__maineid' : 'X',
'__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,upassword-subject,surname-subject,in_group-subject',
+ '_cw_entity_fields:X': 'login-subject,upassword-subject,surname-subject,in_group-subject',
'login-subject:X': u'adim',
'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
'surname-subject:X': u'Di Mascio',
'in_group-subject:X': u(gueid),
'__type:Y': 'EmailAddress',
- '_cw_edited_fields:Y': 'address-subject,use_email-object',
+ '_cw_entity_fields:Y': 'address-subject,use_email-object',
'address-subject:Y': u'dima@logilab.fr',
'use_email-object:Y': 'X',
}
@@ -165,11 +165,11 @@
req.form = {'eid': [peid, 'Y'], '__maineid': peid,
'__type:'+peid: u'CWUser',
- '_cw_edited_fields:'+peid: u'surname-subject',
+ '_cw_entity_fields:'+peid: u'surname-subject',
'surname-subject:'+peid: u'Di Masci',
'__type:Y': u'EmailAddress',
- '_cw_edited_fields:Y': u'address-subject,use_email-object',
+ '_cw_entity_fields:Y': u'address-subject,use_email-object',
'address-subject:Y': u'dima@logilab.fr',
'use_email-object:Y': peid,
}
@@ -185,11 +185,11 @@
req.form = {'eid': [peid, emaileid],
'__type:'+peid: u'CWUser',
- '_cw_edited_fields:'+peid: u'surname-subject',
+ '_cw_entity_fields:'+peid: u'surname-subject',
'surname-subject:'+peid: u'Di Masci',
'__type:'+emaileid: u'EmailAddress',
- '_cw_edited_fields:'+emaileid: u'address-subject,use_email-object',
+ '_cw_entity_fields:'+emaileid: u'address-subject,use_email-object',
'address-subject:'+emaileid: u'adim@logilab.fr',
'use_email-object:'+emaileid: peid,
}
@@ -205,7 +205,7 @@
req = self.request()
req.form = {'eid': 'X',
'__cloned_eid:X': u(user.eid), '__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ '_cw_entity_fields:X': 'login-subject,upassword-subject',
'login-subject:X': u'toto',
'upassword-subject:X': u'toto',
}
@@ -215,7 +215,7 @@
req = self.request()
req.form = {'__cloned_eid:X': u(user.eid),
'eid': 'X', '__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ '_cw_entity_fields:X': 'login-subject,upassword-subject',
'login-subject:X': u'toto',
'upassword-subject:X': u'toto',
'upassword-subject-confirm:X': u'tutu',
@@ -232,7 +232,7 @@
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
- '_cw_edited_fields:X': 'amount-subject,described_by_test-subject',
+ '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
'amount-subject:X': u'-10',
'described_by_test-subject:X': u(feid),
}
@@ -242,7 +242,7 @@
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
- '_cw_edited_fields:X': 'amount-subject,described_by_test-subject',
+ '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
'amount-subject:X': u'110',
'described_by_test-subject:X': u(feid),
}
@@ -252,7 +252,7 @@
req = self.request(rollbackfirst=True)
req.form = {'eid': ['X'],
'__type:X': 'Salesterm',
- '_cw_edited_fields:X': 'amount-subject,described_by_test-subject',
+ '_cw_entity_fields:X': 'amount-subject,described_by_test-subject',
'amount-subject:X': u'10',
'described_by_test-subject:X': u(feid),
}
@@ -298,7 +298,7 @@
req = self.request()
req.form = {
'eid': 'A', '__maineid' : 'A',
- '__type:A': 'BlogEntry', '_cw_edited_fields:A': 'content-subject,title-subject',
+ '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'content-subject,title-subject',
'content-subject:A': u'"13:03:43"',
'title-subject:A': u'huuu',
'__redirectrql': redirectrql,
@@ -321,7 +321,7 @@
req = self.request()
req.form = {
'eid': 'A', '__maineid' : 'A',
- '__type:A': 'BlogEntry', '_cw_edited_fields:A': 'content-subject,title-subject',
+ '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'content-subject,title-subject',
'content-subject:A': u'"13:03:43"',
'title-subject:A': u'huuu',
'__redirectrql': redirectrql,
@@ -377,7 +377,7 @@
req.form = {
'eid': cwetypeeid,
'__type:'+cwetypeeid: 'CWEType',
- '_cw_edited_fields:'+cwetypeeid: 'name-subject,final-subject,description-subject,read_permission-subject',
+ '_cw_entity_fields:'+cwetypeeid: 'name-subject,final-subject,description-subject,read_permission-subject',
'name-subject:'+cwetypeeid: u'CWEType',
'final-subject:'+cwetypeeid: '',
'description-subject:'+cwetypeeid: u'users group',
@@ -401,7 +401,7 @@
req = self.request()
req.form = {
'eid': 'A', '__maineid' : 'A',
- '__type:A': 'BlogEntry', '_cw_edited_fields:A': 'title-subject,content-subject',
+ '__type:A': 'BlogEntry', '_cw_entity_fields:A': 'title-subject,content-subject',
'title-subject:A': u'"13:03:40"',
'content-subject:A': u'"13:03:43"',}
path, params = self.expect_redirect_publish(req, 'edit')
@@ -418,13 +418,13 @@
req.form = {'eid': ['X', 'Y'],
'__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,upassword-subject,in_group-subject',
+ '_cw_entity_fields:X': 'login-subject,upassword-subject,in_group-subject',
'login-subject:X': u'adim',
'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
'in_group-subject:X': `gueid`,
'__type:Y': 'EmailAddress',
- '_cw_edited_fields:Y': 'address-subject,alias-subject,use_email-object',
+ '_cw_entity_fields:Y': 'address-subject,alias-subject,use_email-object',
'address-subject:Y': u'',
'alias-subject:Y': u'',
'use_email-object:Y': 'X',
@@ -438,7 +438,7 @@
req = self.request()
req.form = {'__maineid' : 'X', 'eid': 'X',
'__cloned_eid:X': user.eid, '__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,upassword-subject',
+ '_cw_entity_fields:X': 'login-subject,upassword-subject',
'login-subject:X': u'toto',
'upassword-subject:X': u'toto', 'upassword-subject-confirm:X': u'toto',
}
@@ -462,7 +462,7 @@
req = self.request()
req.form = {'eid': 'X',
'__cloned_eid:X': p.eid, '__type:X': 'CWUser',
- '_cw_edited_fields:X': 'login-subject,surname-subject',
+ '_cw_entity_fields:X': 'login-subject,surname-subject',
'login-subject': u'dodo',
'surname-subject:X': u'Boom',
'__errorurl' : "whatever but required",
--- a/web/views/forms.py Wed Jun 29 18:43:33 2011 +0200
+++ b/web/views/forms.py Wed Jun 29 18:57:23 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -45,14 +45,16 @@
from warnings import warn
-from logilab.common import dictattr
+from logilab.common import dictattr, tempattr
from logilab.common.decorators import iclassmethod
from logilab.common.compat import any
+from logilab.common.textutils import splitstrip
from logilab.common.deprecation import deprecated
-from cubicweb import typed_eid
+from cubicweb import ValidationError, typed_eid
from cubicweb.utils import support_args
from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset
+from cubicweb.web import RequestError, ProcessFormError
from cubicweb.web import uicfg, form, formwidgets as fwdgs
from cubicweb.web.formfields import relvoc_unrelated, guess_field
@@ -125,6 +127,23 @@
.. automethod:: cubicweb.web.views.forms.FieldsForm.render
+ **Form posting methods**
+
+ Once a form is posted, you can retrieve the form on the controller side and
+ use the following methods to ease processing. For "simple" forms, this
+ should looks like :
+
+ .. sourcecode :: python
+
+ form = self._cw.vreg['forms'].select('myformid', self._cw)
+ posted = form.process_posted()
+ # do something with the returned dictionary
+
+ Notice that form related to entity edition should usually use the
+ `edit` controller which will handle all the logic for you.
+
+ .. automethod:: cubicweb.web.views.forms.FieldsForm.process_content
+ .. automethod:: cubicweb.web.views.forms.FieldsForm.iter_modified_fields
"""
__regid__ = 'base'
@@ -218,6 +237,19 @@
for field in self.fields[:]:
for field in field.actual_fields(self):
field.form_init(self)
+ # store used field in an hidden input for later usage by a controller
+ fields = set()
+ eidfields = set()
+ for field in self.fields:
+ if field.eidparam:
+ eidfields.add(field.role_name())
+ elif field.name not in self.control_fields:
+ fields.add(field.role_name())
+ if fields:
+ self.add_hidden('_cw_fields', u','.join(fields))
+ if eidfields:
+ self.add_hidden('_cw_entity_fields', u','.join(eidfields),
+ eidparam=True)
_default_form_action_path = 'edit'
def form_action(self):
@@ -229,6 +261,50 @@
return self._cw.build_url(self._default_form_action_path)
return action
+ # controller form processing methods #######################################
+
+ def iter_modified_fields(self, editedfields=None, entity=None):
+ """return a generator on field that has been modified by the posted
+ form.
+ """
+ if editedfields is None:
+ try:
+ editedfields = self._cw.form['_cw_fields']
+ except KeyError:
+ raise RequestError(self._cw._('no edited fields specified'))
+ entityform = entity and self.field_by_name.im_func.func_code.co_argcount == 4 # XXX
+ for editedfield in splitstrip(editedfields):
+ try:
+ name, role = editedfield.split('-')
+ except:
+ name = editedfield
+ role = None
+ if entityform:
+ field = self.field_by_name(name, role, eschema=entity.e_schema)
+ else:
+ field = self.field_by_name(name, role)
+ if field.has_been_modified(self):
+ yield field
+
+ def process_posted(self):
+ """use this method to process the content posted by a simple form. it
+ will return a dictionary with field names as key and typed value as
+ associated value.
+ """
+ with tempattr(self, 'formvalues', {}): # init fields value cache
+ errors = []
+ processed = {}
+ for field in self.iter_modified_fields():
+ try:
+ for field, value in field.process_posted(self):
+ processed[field.role_name()] = value
+ except ProcessFormError, exc:
+ errors.append((field, exc))
+ if errors:
+ errors = dict((f.role_name(), unicode(ex)) for f, ex in errors)
+ raise ValidationError(None, errors)
+ return processed
+
@deprecated('[3.6] use .add_hidden(name, value, **kwargs)')
def form_add_hidden(self, name, value=None, **kwargs):
return self.add_hidden(name, value, **kwargs)
@@ -323,16 +399,6 @@
# different url after a validation error
return '%s#%s' % (self._cw.url(), self.domid)
- def build_context(self, formvalues=None):
- if self.formvalues is not None:
- return # already built
- super(EntityFieldsForm, self).build_context(formvalues)
- edited = set()
- for field in self.fields:
- if field.eidparam:
- edited.add(field.role_name())
- self.add_hidden('_cw_edited_fields', u','.join(edited), eidparam=True)
-
def default_renderer(self):
return self._cw.vreg['formrenderers'].select(
self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row,