refactor the way field value/display value are handled to avoid getting a 'display' value when expected a 'typed' value tls-sprint
authorsylvain.thenault@logilab.fr
Fri, 17 Apr 2009 11:14:19 +0200
branchtls-sprint
changeset 1392 d6279efff7b3
parent 1391 2a80b14fc548
child 1393 ff6758d7b96f
child 1478 674fa3eb01d1
refactor the way field value/display value are handled to avoid getting a 'display' value when expected a 'typed' value
web/form.py
web/formfields.py
web/formwidgets.py
--- a/web/form.py	Fri Apr 17 11:13:44 2009 +0200
+++ b/web/form.py	Fri Apr 17 11:14:19 2009 +0200
@@ -337,49 +337,64 @@
         form_render()
         """
         self.context = context = {}
-        # on validation error, we get a dictionnary of previously submitted values
-        if values is None:
-            values = {}
-        previous_values = self.req.data.get('formvalues')
-        if previous_values:
-            values.update(previous_values)
+        # on validation error, we get a dictionary of previously submitted
+        # values
+        self._previous_values = self.req.data.get('formvalues', {})
+        # ensure rendervalues is a dict
+        if rendervalues is None:
+            rendervalues = {}
         for field in self.fields:
             for field in field.actual_fields(self):
                 field.form_init(self)
-                value = self.form_field_value(field, values)
-                context[field] = {'value': field.format_value(self.req, value),
-                                  'rawvalue': value,
+                value = self.form_field_display_value(field, rendervalues)
+                context[field] = {'value': value,
                                   'name': self.form_field_name(field),
                                   'id': self.form_field_id(field),
                                   }
 
-    def form_field_value(self, field, values, load_bytes=False):
-        """looks for field's value in
-        1. kw args given to render_form (including previously submitted form
-           values if any)
+    def form_field_display_value(self, field, rendervalues, load_bytes=False):
+        """return field's *string* value to use for display
+        
+        looks in
+        1. previously submitted form values if any (eg on validation error)
         2. req.form
-        3. default_<fieldname> attribute / method on the form
-        4. field's initial value
+        3. extra kw args given to render_form
+        4. field's typed value
+
+        values found in 1. and 2. are expected te be already some 'display'
+        value while those found in 3. and 4. are expected to be correctly typed.
         """
-        defaultattr = 'default_%s' % field.name
-        if field.name in values:
-            value = values[field.name]
+        if field.name in self._previous_values:
+            value = self._previous_values[field.name]
         elif field.name in self.req.form:
             value = self.req.form[field.name]
-        elif hasattr(self, defaultattr):
-            value = getattr(self, defaultattr)
-            if callable(value):
-                value = value()
         else:
-            value = field.initial
-            if callable(value):
-                value = value(self)
+            if field.name in rendervalues:
+                value = rendervalues[field.name]
+            else:
+                value = self.form_field_value(field, load_bytes)
+                if callable(value):
+                    value = value(self)
+            if value != INTERNAL_FIELD_VALUE: 
+                value = field.format_value(self.req, value)
         return value
+
+    def form_field_value(self, field, load_bytes=False):
+        """return field's *typed* value"""
+        value = field.initial
+        if callable(value):
+            value = value(self)
+        return value
+
+    def _errex_match_field(self, errex, field):
+        """return true if the field has some error in given validation exception
+        """
+        return field.name in errex.errors
     
     def form_field_error(self, field):
         """return validation error for widget's field, if any"""
         errex = self.req.data.get('formerrors')
-        if errex and field.name in errex.errors:
+        if errex and self._errex_match_field(errex, field):
             self.req.data['displayederrors'].add(field.name)
             return u'<span class="error">%s</span>' % errex.errors[field.name]
         return u''
@@ -447,64 +462,68 @@
             name = 'edito-%s' % field.name
         return HiddenInitialValueField(field, name=name)
         
-    def form_field_value(self, field, values, load_bytes=False):
-        """look for field's value with the following rules:
-        1. handle special __type and eid fields
-        2. looks in kw args given to render_form (including previously submitted
-           form values if any)
-        3. looks in req.form
-        4. if entity has an eid:
-             1. looks for an associated attribute / method
-             2. use field's initial value
-           else:
-             1. looks for a default_<fieldname> attribute / method on the form
-             2. use field's initial value
-             
-        values found in step 4 may be a callable which'll then be called.
+    def form_field_value(self, field, load_bytes=False):
+        """return field's *typed* value
+
+        overriden to deal with
+        * special eid / __type / edits- / edito- fields
+        * lookup for values on edited entities
         """
-        fieldname = field.name
-        if fieldname.startswith('edits-') or fieldname.startswith('edito-'):
+        attr = field.name
+        entity = self.edited_entity
+        if attr == 'eid':
+            return entity.eid
+        if not field.eidparam:
+            return super(EntityFieldsForm, self).form_field_value(field, load_bytes)
+        if attr.startswith('edits-') or attr.startswith('edito-'):
             # edit[s|o]- fieds must have the actual value stored on the entity
-            if hasattr(field, 'visible_field'):
-                if self.edited_entity.has_eid():
-                    value = self._form_field_entity_value(field.visible_field,
-                                                          default_initial=False)
-                else:
-                    value = INTERNAL_FIELD_VALUE
+            assert hasattr(field, 'visible_field')
+            vfield = field.visible_field
+            assert vfield.eidparam
+            if entity.has_eid():
+                return self.form_field_value(vfield)
+            return INTERNAL_FIELD_VALUE
+        if attr == '__type':
+            return entity.id
+        if field.role == 'object':
+            attr = 'reverse_' + attr
+        elif entity.e_schema.subject_relation(attr).is_final():
+            attrtype = entity.e_schema.destination(attr)
+            if attrtype == 'Password':
+                return entity.has_eid() and INTERNAL_FIELD_VALUE or ''
+            if attrtype == 'Bytes':
+                if entity.has_eid():
+                    if load_bytes:
+                        return getattr(entity, attr)
+                    # XXX value should reflect if some file is already attached
+                    return True
+                return False
+            if entity.has_eid():
+                value = getattr(entity, attr)
             else:
-                value = field.initial
-        elif fieldname == '__type':
-            value = self.edited_entity.id
-        elif fieldname == 'eid':
-            value = self.edited_entity.eid
-        elif fieldname in values:
-            value = values[fieldname]
-        elif fieldname in self.req.form:
-            value = self.req.form[fieldname]
+                value = self._form_field_default_value(field, load_bytes)
+            return value
+        # non final relation field
+        if entity.has_eid():
+            value = [ent.eid for ent in getattr(entity, attr)]
         else:
-            if self.edited_entity.has_eid() and field.eidparam:
-                # use value found on the entity or field's initial value if it's
-                # not an attribute of the entity (XXX may conflicts and get
-                # undesired value)
-                value = self._form_field_entity_value(field, default_initial=True,
-                                                      load_bytes=load_bytes)
-            else:
-                defaultattr = 'default_%s' % fieldname
-                if hasattr(self.edited_entity, defaultattr):
-                    # XXX bw compat, default_<field name> on the entity
-                    warn('found %s on %s, should be set on a specific form'
-                         % (defaultattr, self.edited_entity.id), DeprecationWarning)
-                    value = getattr(self.edited_entity, defaultattr)
-                elif hasattr(self, defaultattr):
-                    # search for default_<field name> on the form instance
-                    value = getattr(self, defaultattr)
-                else:
-                    # use field's initial value
-                    value = field.initial
+            value = self._form_field_default_value(field, load_bytes)
+        return value
+
+    def _form_field_default_value(self, field, load_bytes):
+        defaultattr = 'default_%s' % field.name
+        if hasattr(self.edited_entity, defaultattr):
+            # XXX bw compat, default_<field name> on the entity
+            warn('found %s on %s, should be set on a specific form'
+                 % (defaultattr, self.edited_entity.id), DeprecationWarning)
+            value = getattr(self.edited_entity, defaultattr)
             if callable(value):
-                value = value(self)
+                value = value()
+        else:
+            value = super(EntityFieldsForm, self).form_field_value(field,
+                                                                   load_bytes)
         return value
-    
+        
     def form_field_format(self, field):
         entity = self.edited_entity
         if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
@@ -519,38 +538,11 @@
             return self.edited_entity.attr_metadata(field.name, 'encoding')
         return super(EntityFieldsForm, self).form_field_encoding(field)
     
-    def form_field_error(self, field):
-        """return validation error for widget's field, if any"""
-        errex = self.req.data.get('formerrors')
-        if errex and errex.eid == self.edited_entity.eid and field.name in errex.errors:
-            self.req.data['displayederrors'].add(field.name)
-            return u'<span class="error">%s</span>' % errex.errors[field.name]
-        return u''
-
-    def _form_field_entity_value(self, field, default_initial=True, load_bytes=False):
-        attr = field.name
-        entity = self.edited_entity
-        if field.role == 'object':
-            attr = 'reverse_' + attr
-        elif entity.e_schema.subject_relation(attr).is_final():
-            attrtype = entity.e_schema.destination(attr)
-            if attrtype == 'Password':
-                return entity.has_eid() and INTERNAL_FIELD_VALUE or ''
-            if attrtype == 'Bytes':
-                if entity.has_eid():
-                    if load_bytes:
-                        return getattr(entity, attr)
-                    # XXX value should reflect if some file is already attached
-                    return True
-                return False
-        if default_initial:
-            value = getattr(entity, attr, field.initial)
-        else:
-            value = getattr(entity, attr)
-        if isinstance(field, RelationField):
-            # in this case, value is the list of related entities
-            value = [ent.eid for ent in value]
-        return value
+    
+    def _errex_match_field(self, errex, field):
+        """return true if the field has some error in given validation exception
+        """
+        return errex.eid == self.edited_entity.eid and field.name in errex.errors
     
     def form_field_name(self, field):
         if field.eidparam:
--- a/web/formfields.py	Fri Apr 17 11:13:44 2009 +0200
+++ b/web/formfields.py	Fri Apr 17 11:14:19 2009 +0200
@@ -303,7 +303,7 @@
     def render(self, form, renderer):
         wdgs = [super(EditableFileField, self).render(form, renderer)]
         if form.form_field_format(self) in self.editable_formats:
-            data = form.form_field_value(self, {}, load_bytes=True)
+            data = form.form_field_value(self, load_bytes=True)
             if data:
                 encoding = form.form_field_encoding(self)
                 try:
--- a/web/formwidgets.py	Fri Apr 17 11:13:44 2009 +0200
+++ b/web/formwidgets.py	Fri Apr 17 11:14:19 2009 +0200
@@ -251,19 +251,19 @@
         return txtwidget + cal_button
     
     def _render_calendar_popup(self, form, field):
-        req = form.req
-        value = form.context[field]['rawvalue']
+        value = form.form_field_value(field)
+        if not value:
+            value = date.today()
         inputid = form.context[field]['id']
         helperid = '%shelper' % inputid
-        if not value:
-            value = date.today()
         year, month = value.year, value.month
         return (u"""<a onclick="toggleCalendar('%s', '%s', %s, %s);" class="calhelper">
 <img src="%s" title="%s" alt="" /></a><div class="calpopup hidden" id="%s"></div>"""
                 % (helperid, inputid, year, month,
-                   req.external_resource('CALENDAR_ICON'),
-                   req._('calendar'), helperid) )
+                   form.req.external_resource('CALENDAR_ICON'),
+                   form.req._('calendar'), helperid) )
 
+        
 
 # ajax widgets ################################################################