handle sort/internationalizable on Field base class + fix guess_field to deal with internationalizable and default values tls-sprint
authorsylvain.thenault@logilab.fr
Tue, 07 Apr 2009 17:18:54 +0200
branchtls-sprint
changeset 1265 e5cdd5c0dce3
parent 1264 fe2934a7df7f
child 1266 5f16f45e9bae
handle sort/internationalizable on Field base class + fix guess_field to deal with internationalizable and default values
web/form.py
web/formfields.py
web/test/unittest_formfields.py
--- a/web/form.py	Tue Apr 07 17:17:55 2009 +0200
+++ b/web/form.py	Tue Apr 07 17:18:54 2009 +0200
@@ -480,25 +480,24 @@
         
     def form_field_vocabulary(self, field, limit=None):
         role, rtype = field.role, field.name
+        method = '%s_%s_vocabulary' % (role, rtype)
         try:
-            vocabfunc = getattr(self.edited_entity, '%s_%s_vocabulary' % (role, rtype))
+            vocabfunc = getattr(self, method)
         except AttributeError:
-            vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
-        else:
-            # XXX bw compat, default_<field name> on the entity
-            warn('found %s_%s_vocabulary on %s, should be set on a specific form'
-                 % (role, rtype, self.edited_entity.id), DeprecationWarning)
+            try:
+                # XXX bw compat, <role>_<rtype>_vocabulary on the entity
+                vocabfunc = getattr(self.edited_entity, method)
+            except AttributeError:
+                vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
+            else:
+                warn('found %s on %s, should be set on a specific form'
+                     % (method, self.edited_entity.id), DeprecationWarning)
         # NOTE: it is the responsibility of `vocabfunc` to sort the result
         #       (direclty through RQL or via a python sort). This is also
         #       important because `vocabfunc` might return a list with
         #       couples (label, None) which act as separators. In these
         #       cases, it doesn't make sense to sort results afterwards.
         return vocabfunc(rtype, limit)
-## XXX BACKPORT ME
-##         if self.sort:
-##             choices = sorted(choices)
-##         if self.rschema.rproperty(self.subjtype, self.objtype, 'internationalizable'):
-##             return zip((entity.req._(v) for v in choices), choices)
 
     def subject_relation_vocabulary(self, rtype, limit=None):
         """defaut vocabulary method for the given relation, looking for
--- a/web/formfields.py	Tue Apr 07 17:17:55 2009 +0200
+++ b/web/formfields.py	Tue Apr 07 17:18:54 2009 +0200
@@ -15,36 +15,83 @@
 from cubicweb.utils import ustrftime
 from cubicweb.common import tags, uilib
 from cubicweb.web import INTERNAL_FIELD_VALUE
-from cubicweb.web.formwidgets import (HiddenInput, TextInput, FileInput, PasswordInput,
-                                      TextArea, FCKEditor, Radio, Select,
-                                      DateTimePicker) 
+from cubicweb.web.formwidgets import (
+    HiddenInput, TextInput, FileInput, PasswordInput, TextArea, FCKEditor,
+    Radio, Select, DateTimePicker) 
 
 class Field(object):
-    """field class is introduced to control what's displayed in edition form
+    """field class is introduced to control what's displayed in forms. It makes
+    the link between something to edit and its display in the form. Actual
+    display is handled by a widget associated to the field.
+
+    Attributes
+    ----------
+    all the attributes described below have sensible default value which may be
+    overriden by value given to field's constructor.
+    
+    :name:
+       name of the field (basestring), should be unique in a form.
+    :id:
+       dom identifier (default to the same value as `name`), should be unique in
+       a form.
+    :label:
+       label of the field (default to the same value as `name`).
+    :help:
+       help message about this field.
+    :widget:
+       widget associated to the field. Each field class has a default widget
+       class which may be overriden per instance.
+    :required:
+       bool flag telling if the field is required or not.
+    :initial:
+       initial value, used when no value specified by other means.
+    :choices:
+       static vocabulary for this field. May be a list of values or a list of
+       (label, value) tuples if specified.
+    :sort:
+       bool flag telling if the vocabulary (either static vocabulary specified
+       in `choices` or dynamic vocabulary fetched from the form) should be
+       sorted on label.
+    :internationalizable:
+       bool flag telling if the vocabulary labels should be translated using the
+       current request language.
+    :eidparam:
+       bool flag telling if this field is linked to a specific entity
+    :role:
+       when the field is linked to an entity attribute or relation, tells the
+       role of the entity in the relation (eg 'subject' or 'object')
+    
     """
+    # default widget associated to this class of fields. May be overriden per
+    # instance
     widget = TextInput
+    # does this field requires a multipart form
     needs_multipart = False
-    creation_rank = 0
+    # class attribute used for ordering of fields in a form
+    __creation_rank = 0
 
-    def __init__(self, name=None, id=None, label=None,
+    def __init__(self, name=None, id=None, label=None, help=None, 
                  widget=None, required=False, initial=None,
-                 choices=None, help=None, eidparam=False, role='subject'):
+                 choices=None, sort=True, internationalizable=False,
+                 eidparam=False, role='subject'):
+        self.name = name
+        self.id = id or name
+        self.label = label or name
+        self.help = help
         self.required = required
         if widget is not None:
             self.widget = widget
         if isinstance(self.widget, type):
             self.widget = self.widget()
-        self.name = name
-        self.label = label or name
-        self.id = id or name
         self.initial = initial
         self.choices = choices
-        self.help = help
+        self.sort = sort
+        self.internationalizable = internationalizable
         self.eidparam = eidparam
         self.role = role
-        # global fields ordering in forms
-        self.creation_rank = Field.creation_rank
-        Field.creation_rank += 1
+        # ordering number for this field instance
+        self.creation_rank = Field.__creation_rank
+        Field.__creation_rank += 1
     
     def __unicode__(self):
         return u'<%s name=%r label=%r id=%r initial=%r @%x>' % (
@@ -55,6 +102,7 @@
         return self.__unicode__().encode('utf-8')
 
     def set_name(self, name):
+        """automatically set .id and .label when name is set"""
         assert name
         self.name = name
         if not self.id:
@@ -63,31 +111,48 @@
             self.label = name
             
     def is_visible(self):
+        """return true if the field is not an hidden field"""
         return not isinstance(self.widget, HiddenInput)
     
     def actual_fields(self, form):
+        """return actual fields composing this field in case of a compound
+        field, usually simply return self
+        """
         yield self
     
     def format_value(self, req, value):
+        """return value suitable for display where value may be a list or tuple
+        of values
+        """
         if isinstance(value, (list, tuple)):
             return [self.format_single_value(req, val) for val in value]
         return self.format_single_value(req, value)
     
     def format_single_value(self, req, value):
+        """return value suitable for display"""
         if value is None:
             return u''
         return unicode(value)
 
     def get_widget(self, form):
+        """return the widget instance associated to this field"""
         return self.widget
     
     def example_format(self, req):
+        """return a sample string describing what can be given as input for this
+        field
+        """        
         return u''
 
     def render(self, form, renderer):
+        """render this field, which is part of form, using the given form
+        renderer
+        """
         return self.get_widget(form).render(form, self)
 
     def vocabulary(self, form):
+        """return vocabulary for this field. This method will be called by
+        widgets which desire it."""        
         if self.choices is not None:
             if callable(self.choices):
                 vocab = self.choices(req=form.req)
@@ -95,8 +160,13 @@
                 vocab = self.choices
             if vocab and not isinstance(vocab[0], (list, tuple)):
                 vocab = [(x, x) for x in vocab]
-            return vocab
-        return form.form_field_vocabulary(self)
+        else:
+            vocab = form.form_field_vocabulary(self)
+        if self.internationalizable:
+            vocab = [(form.req._(label), value) for label, value in vocab]
+        if self.sort:
+            vocab = sorted(vocab)
+        return vocab
 
     
 class StringField(Field):
@@ -369,6 +439,10 @@
     if role == 'subject':
         targetschema = rschema.objects(eschema)[0]
         card = rschema.rproperty(eschema, targetschema, 'cardinality')[0]
+        if rschema.is_final():
+            if rschema.rproperty(eschema, targetschema, 'internationalizable'):
+                kwargs['internationalizable'] = True
+            kwargs['initial'] = rschema.rproperty(eschema, targetschema, 'default')
     else:
         targetschema = rschema.subjects(eschema)[0]
         card = rschema.rproperty(targetschema, eschema, 'cardinality')[1]
--- a/web/test/unittest_formfields.py	Tue Apr 07 17:17:55 2009 +0200
+++ b/web/test/unittest_formfields.py	Tue Apr 07 17:18:54 2009 +0200
@@ -32,10 +32,16 @@
         content_field = guess_field(Card, schema['content'])
         self.assertIsInstance(content_field, RichTextField)
         self.assertEquals(content_field.required, False)
-        
+        self.assertEquals(content_field.format_field, None)
+                          
         content_format_field = guess_field(Card, schema['content_format'])
         self.assertEquals(content_format_field, None)
         
+        content_format_field = guess_field(Card, schema['content_format'], skip_meta_attr=False)
+        self.assertEquals(content_format_field.internationalizable, True)
+        self.assertEquals(content_format_field.sort, True)
+        self.assertEquals(content_format_field.initial, 'text/rest')
+
         wikiid_field = guess_field(Card, schema['wikiid'])
         self.assertIsInstance(wikiid_field, StringField)
         self.assertEquals(wikiid_field.required, False)