web/formfields.py
branchstable
changeset 5368 d321e4b62a10
parent 5367 4176a50c81c9
child 5385 b6e250dd7a7d
child 5421 8167de96c523
equal deleted inserted replaced
5367:4176a50c81c9 5368:d321e4b62a10
     1 """Fields are used to control what's displayed in forms. It makes the link
     1 # organization: Logilab
     2 between something to edit and its display in the form. Actual display is handled
     2 # copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
     3 by a widget associated to the field.
     3 # contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     4 
     4 # license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     5 :organization: Logilab
     5 """
     6 :copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
     6 The Field class and basic fields
     7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     7 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     8 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     8 
       
     9 .. Note::
       
    10   Fields are used to control what's edited in forms. They makes the link between
       
    11   something to edit and its display in the form. Actual display is handled by a
       
    12   widget associated to the field.
       
    13 
       
    14 Let first see the base class for fields:
       
    15 
       
    16 .. autoclass:: cubicweb.web.formfields.Field
       
    17 
       
    18 Now, you usually don't use that class but one of the concret field classes
       
    19 described below, according to what you want to edit.
       
    20 
       
    21 Basic fields
       
    22 ''''''''''''
       
    23 
       
    24 .. autoclass:: cubicweb.web.formfields.StringField()
       
    25 .. autoclass:: cubicweb.web.formfields.PasswordField()
       
    26 .. autoclass:: cubicweb.web.formfields.IntField()
       
    27 .. autoclass:: cubicweb.web.formfields.FloatField()
       
    28 .. autoclass:: cubicweb.web.formfields.BooleanField()
       
    29 .. autoclass:: cubicweb.web.formfields.DateField()
       
    30 .. autoclass:: cubicweb.web.formfields.DateTimeField()
       
    31 .. autoclass:: cubicweb.web.formfields.TimeField()
       
    32 
       
    33 Compound fields
       
    34 ''''''''''''''''
       
    35 
       
    36 .. autoclass:: cubicweb.web.formfields.RichTextField()
       
    37 .. autoclass:: cubicweb.web.formfields.FileField()
       
    38 .. autoclass:: cubicweb.web.formfields.CompoundField()
       
    39 
       
    40 .. autoclass cubicweb.web.formfields.EditableFileField() XXX should be a widget
       
    41 
       
    42 Entity specific fields and function
       
    43 '''''''''''''''''''''''''''''''''''
       
    44 
       
    45 .. autoclass:: cubicweb.web.formfields.RelationField()
       
    46 .. autofunction:: cubicweb.web.formfields.guess_field
       
    47 
     9 """
    48 """
    10 __docformat__ = "restructuredtext en"
    49 __docformat__ = "restructuredtext en"
    11 
    50 
    12 from warnings import warn
    51 from warnings import warn
    13 from datetime import datetime
    52 from datetime import datetime
    51 class Field(object):
    90 class Field(object):
    52     """This class is the abstract base class for all fields. It hold a bunch
    91     """This class is the abstract base class for all fields. It hold a bunch
    53     of attributes which may be used for fine control of the behaviour of a
    92     of attributes which may be used for fine control of the behaviour of a
    54     concret field.
    93     concret field.
    55 
    94 
       
    95     **Attributes**
       
    96 
    56     All the attributes described below have sensible default value which may be
    97     All the attributes described below have sensible default value which may be
    57     overriden by value given to field's constructor.
    98     overriden by named arguments given to field's constructor.
    58 
    99 
    59     :name:
   100     :attr:`name`
    60        name of the field (basestring), should be unique in a form.
   101        base name of the field (basestring). The actual input name is returned by
    61     :id:
   102        the :meth:`input_name` method and may differ from that name (for instance
    62        dom identifier (default to the same value as `name`), should be unique in
   103        if `eidparam` is true).
       
   104     :attr:`id`
       
   105        DOM identifier (default to the same value as `name`), should be unique in
    63        a form.
   106        a form.
    64     :label:
   107     :attr:`label`
    65        label of the field (default to the same value as `name`).
   108        label of the field (default to the same value as `name`).
    66     :help:
   109     :attr:`help`
    67        help message about this field.
   110        help message about this field.
    68     :widget:
   111     :attr:`widget`
    69        widget associated to the field. Each field class has a default widget
   112        widget associated to the field. Each field class has a default widget
    70        class which may be overriden per instance.
   113        class which may be overriden per instance.
    71     :required:
   114     :attr:`value`
       
   115        field value. May be an actual value or a callable which should take the
       
   116        form as argument and return a value.
       
   117     :attr:`choices`
       
   118        static vocabulary for this field. May be a list of values, a list of
       
   119        (label, value) tuples or a callable which should take the form and field
       
   120        as arguments and return a list of values or a list of (label, value).
       
   121     :attr:`required`
    72        bool flag telling if the field is required or not.
   122        bool flag telling if the field is required or not.
    73     :value:
   123     :attr:`sort`
    74        field value (may be an actual value, a default value or nothing)
       
    75     :choices:
       
    76        static vocabulary for this field. May be a list of values or a list of
       
    77        (label, value) tuples if specified.
       
    78     :sort:
       
    79        bool flag telling if the vocabulary (either static vocabulary specified
   124        bool flag telling if the vocabulary (either static vocabulary specified
    80        in `choices` or dynamic vocabulary fetched from the form) should be
   125        in `choices` or dynamic vocabulary fetched from the form) should be
    81        sorted on label.
   126        sorted on label.
    82     :internationalizable:
   127     :attr:`internationalizable`
    83        bool flag telling if the vocabulary labels should be translated using the
   128        bool flag telling if the vocabulary labels should be translated using the
    84        current request language.
   129        current request language.
    85     :eidparam:
   130     :attr:`eidparam`
    86        bool flag telling if this field is linked to a specific entity
   131        bool flag telling if this field is linked to a specific entity
    87     :role:
   132     :attr:`role`
    88        when the field is linked to an entity attribute or relation, tells the
   133        when the field is linked to an entity attribute or relation, tells the
    89        role of the entity in the relation (eg 'subject' or 'object')
   134        role of the entity in the relation (eg 'subject' or 'object')
    90     :fieldset:
   135     :attr:`fieldset`
    91        optional fieldset to which this field belongs to
   136        optional fieldset to which this field belongs to
    92     :order:
   137     :attr:`order`
    93        key used by automatic forms to sort fields
   138        key used by automatic forms to sort fields
    94     :ignore_req_params:
   139     :attr:`ignore_req_params`
    95        when true, this field won't consider value potentialy specified using
   140        when true, this field won't consider value potentialy specified using
    96        request's form parameters (eg you won't be able to specify a value using for
   141        request's form parameters (eg you won't be able to specify a value using for
    97        instance url like http://mywebsite.com/form?field=value)
   142        instance url like http://mywebsite.com/form?field=value)
       
   143 
       
   144     .. currentmodule:: cubicweb.web.formfields
       
   145 
       
   146     **Generic methods**
       
   147 
       
   148     .. automethod:: Field.input_name
       
   149     .. automethod:: Field.dom_id
       
   150     .. automethod:: Field.actual_fields
       
   151 
       
   152     **Form generation methods**
       
   153 
       
   154     .. automethod:: form_init
       
   155     .. automethod:: typed_value
       
   156 
       
   157     **Post handling methods**
       
   158 
       
   159     .. automethod:: process_posted
       
   160     .. automethod:: process_form_value
       
   161 
    98     """
   162     """
    99     # default widget associated to this class of fields. May be overriden per
   163     # default widget associated to this class of fields. May be overriden per
   100     # instance
   164     # instance
   101     widget = fw.TextInput
   165     widget = fw.TextInput
   102     # does this field requires a multipart form
   166     # does this field requires a multipart form
   162     def is_visible(self):
   226     def is_visible(self):
   163         """return true if the field is not an hidden field"""
   227         """return true if the field is not an hidden field"""
   164         return not isinstance(self.widget, fw.HiddenInput)
   228         return not isinstance(self.widget, fw.HiddenInput)
   165 
   229 
   166     def actual_fields(self, form):
   230     def actual_fields(self, form):
   167         """return actual fields composing this field in case of a compound
   231         """Fields may be composed of other fields. For instance the
   168         field, usually simply return self
   232         :class:`~cubicweb.web.formfields.RichTextField` is containing a format
       
   233         field to define the text format. This method returns actual fields that
       
   234         should be considered for display / edition. It usually simply return
       
   235         self.
   169         """
   236         """
   170         yield self
   237         yield self
   171 
   238 
   172     def format_value(self, req, value):
   239     def format_value(self, req, value):
   173         """return value suitable for display where value may be a list or tuple
   240         """return value suitable for display where value may be a list or tuple
   188     def get_widget(self, form):
   255     def get_widget(self, form):
   189         """return the widget instance associated to this field"""
   256         """return the widget instance associated to this field"""
   190         return self.widget
   257         return self.widget
   191 
   258 
   192     def input_name(self, form, suffix=None):
   259     def input_name(self, form, suffix=None):
   193         """return 'qualified name' for this field"""
   260         """Return the 'qualified name' for this field, e.g. something suitable
       
   261         to use as HTML input name. You can specify a suffix that will be
       
   262         included in the name when widget needs several inputs.
       
   263         """
   194         # caching is necessary else we get some pb on entity creation :
   264         # caching is necessary else we get some pb on entity creation :
   195         # entity.eid is modified from creation mark (eg 'X') to its actual eid
   265         # entity.eid is modified from creation mark (eg 'X') to its actual eid
   196         # (eg 123), and then `field.input_name()` won't return the right key
   266         # (eg 123), and then `field.input_name()` won't return the right key
   197         # anymore if not cached (first call to input_name done *before* eventual
   267         # anymore if not cached (first call to input_name done *before* eventual
   198         # eid affectation).
   268         # eid affectation).
   217         if self.role is not None:
   287         if self.role is not None:
   218             return role_name(self.name, self.role)
   288             return role_name(self.name, self.role)
   219         return self.name
   289         return self.name
   220 
   290 
   221     def dom_id(self, form, suffix=None):
   291     def dom_id(self, form, suffix=None):
   222         """return an html dom identifier for this field"""
   292         """Return the HTML DOM identifier for this field, e.g. something
       
   293         suitable to use as HTML input id. You can specify a suffix that will be
       
   294         included in the name when widget needs several inputs.
       
   295         """
   223         id = self.id or self.role_name()
   296         id = self.id or self.role_name()
   224         if suffix is not None:
   297         if suffix is not None:
   225             id += suffix
   298             id += suffix
   226         if self.eidparam:
   299         if self.eidparam:
   227             return eid_param(id, form.edited_entity.eid)
   300             return eid_param(id, form.edited_entity.eid)
   228         return id
   301         return id
   229 
   302 
   230     def typed_value(self, form, load_bytes=False):
   303     def typed_value(self, form, load_bytes=False):
       
   304         """Return the correctly typed value for this field in the form context.
       
   305         """
   231         if self.eidparam and self.role is not None:
   306         if self.eidparam and self.role is not None:
   232             entity = form.edited_entity
   307             entity = form.edited_entity
   233             if form._cw.vreg.schema.rschema(self.name).final:
   308             if form._cw.vreg.schema.rschema(self.name).final:
   234                 if entity.has_eid() or self.name in entity:
   309                 if entity.has_eid() or self.name in entity:
   235                     value = getattr(entity, self.name)
   310                     value = getattr(entity, self.name)
   322                 entity.has_eid() or '%s_encoding' % self.name in entity):
   397                 entity.has_eid() or '%s_encoding' % self.name in entity):
   323                 return form.edited_entity.attr_metadata(self.name, 'encoding')
   398                 return form.edited_entity.attr_metadata(self.name, 'encoding')
   324         return form._cw.encoding
   399         return form._cw.encoding
   325 
   400 
   326     def form_init(self, form):
   401     def form_init(self, form):
   327         """method called before by build_context to trigger potential field
   402         """Method called at form initialization to trigger potential field
   328         initialization requiring the form instance
   403         initialization requiring the form instance. Do nothing by default.
   329         """
   404         """
   330         pass
   405         pass
   331 
   406 
   332     def has_been_modified(self, form):
   407     def has_been_modified(self, form):
   333         # fields not corresponding to an entity attribute / relations
   408         # fields not corresponding to an entity attribute / relations
   357         if previous_value == new_value:
   432         if previous_value == new_value:
   358             return False # not modified
   433             return False # not modified
   359         return True
   434         return True
   360 
   435 
   361     def process_form_value(self, form):
   436     def process_form_value(self, form):
   362         """process posted form and return correctly typed value"""
   437         """Return the correctly typed value posted for this field."""
   363         try:
   438         try:
   364             return form.formvalues[(self, form)]
   439             return form.formvalues[(self, form)]
   365         except KeyError:
   440         except KeyError:
   366             value = form.formvalues[(self, form)] = self._process_form_value(form)
   441             value = form.formvalues[(self, form)] = self._process_form_value(form)
   367             return value
   442             return value
   377         Override this method to type the value if necessary
   452         Override this method to type the value if necessary
   378         """
   453         """
   379         return value or None
   454         return value or None
   380 
   455 
   381     def process_posted(self, form):
   456     def process_posted(self, form):
       
   457         """Return an iterator on (field, value) that has been posted for
       
   458         field returned by :meth:`~cubicweb.web.formfields.Field.actual_fields`.
       
   459         """
   382         for field in self.actual_fields(form):
   460         for field in self.actual_fields(form):
   383             if field is self:
   461             if field is self:
   384                 try:
   462                 try:
   385                     value = field.process_form_value(form)
   463                     value = field.process_form_value(form)
   386                     if value is None and field.required:
   464                     if value is None and field.required:
   394                 for field, value in field.process_posted(form):
   472                 for field, value in field.process_posted(form):
   395                     yield field, value
   473                     yield field, value
   396 
   474 
   397 
   475 
   398 class StringField(Field):
   476 class StringField(Field):
       
   477     """Use this field to edit unicode string (`String` yams type). This field
       
   478     additionaly support a `max_length` attribute that specify a maximum size for
       
   479     the string (`None` meaning no limit).
       
   480 
       
   481     Unless explicitly specified, the widget for this field will be:
       
   482 
       
   483     * :class:`~cubicweb.web.formwidgets.Select` if some vocabulary is specified
       
   484       using `choices` attribute
       
   485 
       
   486     * :class:`~cubicweb.web.formwidgets.TextInput` if maximum size is specified
       
   487       using `max_length` attribute and this length is inferior to 257.
       
   488 
       
   489     * :class:`~cubicweb.web.formwidgets.TextArea` in all other cases
       
   490     """
   399     widget = fw.TextArea
   491     widget = fw.TextArea
   400     size = 45
   492     size = 45
   401 
   493 
   402     def __init__(self, name=None, max_length=None, **kwargs):
   494     def __init__(self, name=None, max_length=None, **kwargs):
   403         self.max_length = max_length # must be set before super call
   495         self.max_length = max_length # must be set before super call
   426             widget.attrs.setdefault('cols', 60)
   518             widget.attrs.setdefault('cols', 60)
   427             widget.attrs.setdefault('rows', 5)
   519             widget.attrs.setdefault('rows', 5)
   428 
   520 
   429 
   521 
   430 class PasswordField(StringField):
   522 class PasswordField(StringField):
       
   523     """Use this field to edit password (`Password` yams type, encoded python
       
   524     string).
       
   525 
       
   526     Unless explicitly specified, the widget for this field will be
       
   527     a :class:`~cubicweb.web.formwidgets.PasswordInput`.
       
   528     """
   431     widget = fw.PasswordInput
   529     widget = fw.PasswordInput
   432     def form_init(self, form):
   530     def form_init(self, form):
   433         if self.eidparam and form.edited_entity.has_eid():
   531         if self.eidparam and form.edited_entity.has_eid():
   434             # see below: value is probably set but we can't retreive it. Ensure
   532             # see below: value is probably set but we can't retreive it. Ensure
   435             # the field isn't show as a required field on modification
   533             # the field isn't show as a required field on modification
   443             return self.initial_typed_value(form, load_bytes)
   541             return self.initial_typed_value(form, load_bytes)
   444         return super(PasswordField, self).typed_value(form, load_bytes)
   542         return super(PasswordField, self).typed_value(form, load_bytes)
   445 
   543 
   446 
   544 
   447 class RichTextField(StringField):
   545 class RichTextField(StringField):
       
   546     """This compound field allow edition of text (unicode string) in
       
   547     a particular format. It has an inner field holding the text format,
       
   548     that can be specified using `format_field` argument. If not specified
       
   549     one will be automaticall generated.
       
   550 
       
   551     Unless explicitly specified, the widget for this field will be a
       
   552     :class:`~cubicweb.web.formwidgets.FCKEditor` or a
       
   553     :class:`~cubicweb.web.formwidgets.TextArea`. according to the field's
       
   554     format and to user's preferences.
       
   555     """
       
   556 
   448     widget = None
   557     widget = None
   449     def __init__(self, format_field=None, **kwargs):
   558     def __init__(self, format_field=None, **kwargs):
   450         super(RichTextField, self).__init__(**kwargs)
   559         super(RichTextField, self).__init__(**kwargs)
   451         self.format_field = format_field
   560         self.format_field = format_field
   452 
   561 
   514             result = u''
   623             result = u''
   515         return result + self.get_widget(form).render(form, self, renderer)
   624         return result + self.get_widget(form).render(form, self, renderer)
   516 
   625 
   517 
   626 
   518 class FileField(StringField):
   627 class FileField(StringField):
       
   628     """This compound field allow edition of binary stream (`Bytes` yams
       
   629     type). Three inner fields may be specified:
       
   630 
       
   631     * `format_field`, holding the file's format.
       
   632     * `encoding_field`, holding the file's content encoding.
       
   633     * `name_field`, holding the file's name.
       
   634 
       
   635     Unless explicitly specified, the widget for this field will be a
       
   636     :class:`~cubicweb.web.formwidgets.FileInput`. Inner fields, if any,
       
   637     will be added to a drop down menu at the right of the file input.
       
   638     """
   519     widget = fw.FileInput
   639     widget = fw.FileInput
   520     needs_multipart = True
   640     needs_multipart = True
   521 
   641 
   522     def __init__(self, format_field=None, encoding_field=None, name_field=None,
   642     def __init__(self, format_field=None, encoding_field=None, name_field=None,
   523                  **kwargs):
   643                  **kwargs):
   602         return value
   722         return value
   603 
   723 
   604 
   724 
   605 # XXX turn into a widget
   725 # XXX turn into a widget
   606 class EditableFileField(FileField):
   726 class EditableFileField(FileField):
       
   727     """This compound field allow edition of binary stream as
       
   728     :class:`~cubicweb.web.formfields.FileField` but expect that stream to
       
   729     actually contains some text.
       
   730 
       
   731     If the stream format is one of text/plain, text/html, text/rest,
       
   732     then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly
       
   733     displayed, allowing to directly the file's content when desired, instead
       
   734     of choosing a file from user's file system.
       
   735     """
   607     editable_formats = ('text/plain', 'text/html', 'text/rest')
   736     editable_formats = ('text/plain', 'text/html', 'text/rest')
   608 
   737 
   609     def render(self, form, renderer):
   738     def render(self, form, renderer):
   610         wdgs = [super(EditableFileField, self).render(form, renderer)]
   739         wdgs = [super(EditableFileField, self).render(form, renderer)]
   611         if self.format(form) in self.editable_formats:
   740         if self.format(form) in self.editable_formats:
   639             return Binary(value.encode(self.encoding(form)))
   768             return Binary(value.encode(self.encoding(form)))
   640         return super(EditableFileField, self)._process_form_value(form)
   769         return super(EditableFileField, self)._process_form_value(form)
   641 
   770 
   642 
   771 
   643 class IntField(Field):
   772 class IntField(Field):
       
   773     """Use this field to edit integers (`Int` yams type). This field additionaly
       
   774     support `min` and `max` attributes that specify a minimum and/or maximum
       
   775     value for the integer (`None` meaning no boundary).
       
   776 
       
   777     Unless explicitly specified, the widget for this field will be a
       
   778     :class:`~cubicweb.web.formwidgets.TextInput`.
       
   779     """
   644     def __init__(self, min=None, max=None, **kwargs):
   780     def __init__(self, min=None, max=None, **kwargs):
   645         super(IntField, self).__init__(**kwargs)
   781         super(IntField, self).__init__(**kwargs)
   646         self.min = min
   782         self.min = min
   647         self.max = max
   783         self.max = max
   648         if isinstance(self.widget, fw.TextInput):
   784         if isinstance(self.widget, fw.TextInput):
   660                 raise ProcessFormError(form._cw._('an integer is expected'))
   796                 raise ProcessFormError(form._cw._('an integer is expected'))
   661         return value
   797         return value
   662 
   798 
   663 
   799 
   664 class BooleanField(Field):
   800 class BooleanField(Field):
       
   801     """Use this field to edit booleans (`Boolean` yams type).
       
   802 
       
   803     Unless explicitly specified, the widget for this field will be a
       
   804     :class:`~cubicweb.web.formwidgets.Radio` with yes/no values. You
       
   805     can change that values by specifing `choices`.
       
   806     """
   665     widget = fw.Radio
   807     widget = fw.Radio
   666 
   808 
   667     def vocabulary(self, form):
   809     def vocabulary(self, form):
   668         if self.choices:
   810         if self.choices:
   669             return super(BooleanField, self).vocabulary(form)
   811             return super(BooleanField, self).vocabulary(form)
   672     def _ensure_correctly_typed(self, form, value):
   814     def _ensure_correctly_typed(self, form, value):
   673         return bool(value)
   815         return bool(value)
   674 
   816 
   675 
   817 
   676 class FloatField(IntField):
   818 class FloatField(IntField):
       
   819     """Use this field to edit floats (`Float` yams type). This field additionaly
       
   820     support `min` and `max` attributes as the
       
   821     :class:`~cubicweb.web.formfields.IntField`.
       
   822 
       
   823     Unless explicitly specified, the widget for this field will be a
       
   824     :class:`~cubicweb.web.formwidgets.TextInput`.
       
   825     """
   677     def format_single_value(self, req, value):
   826     def format_single_value(self, req, value):
   678         formatstr = req.property_value('ui.float-format')
   827         formatstr = req.property_value('ui.float-format')
   679         if value is None:
   828         if value is None:
   680             return u''
   829             return u''
   681         return formatstr % float(value)
   830         return formatstr % float(value)
   694                 raise ProcessFormError(form._cw._('a float is expected'))
   843                 raise ProcessFormError(form._cw._('a float is expected'))
   695         return None
   844         return None
   696 
   845 
   697 
   846 
   698 class DateField(StringField):
   847 class DateField(StringField):
       
   848     """Use this field to edit date (`Date` yams type).
       
   849 
       
   850     Unless explicitly specified, the widget for this field will be a
       
   851     :class:`~cubicweb.web.formwidgets.JQueryDatePicker`.
       
   852     """
   699     widget = fw.JQueryDatePicker
   853     widget = fw.JQueryDatePicker
   700     format_prop = 'ui.date-format'
   854     format_prop = 'ui.date-format'
   701     etype = 'Date'
   855     etype = 'Date'
   702 
   856 
   703     def format_single_value(self, req, value):
   857     def format_single_value(self, req, value):
   719                 raise ProcessFormError(unicode(ex))
   873                 raise ProcessFormError(unicode(ex))
   720         return value
   874         return value
   721 
   875 
   722 
   876 
   723 class DateTimeField(DateField):
   877 class DateTimeField(DateField):
       
   878     """Use this field to edit datetime (`Datetime` yams type).
       
   879 
       
   880     Unless explicitly specified, the widget for this field will be a
       
   881     :class:`~cubicweb.web.formwidgets.JQueryDateTimePicker`.
       
   882     """
   724     widget = fw.JQueryDateTimePicker
   883     widget = fw.JQueryDateTimePicker
   725     format_prop = 'ui.datetime-format'
   884     format_prop = 'ui.datetime-format'
   726     etype = 'Datetime'
   885     etype = 'Datetime'
   727 
   886 
   728 
   887 
   729 class TimeField(DateField):
   888 class TimeField(DateField):
       
   889     """Use this field to edit time (`Time` yams type).
       
   890 
       
   891     Unless explicitly specified, the widget for this field will be a
       
   892     :class:`~cubicweb.web.formwidgets.JQueryTimePicker`.
       
   893     """
   730     widget = fw.JQueryTimePicker
   894     widget = fw.JQueryTimePicker
   731     format_prop = 'ui.time-format'
   895     format_prop = 'ui.time-format'
   732     etype = 'Time'
   896     etype = 'Time'
       
   897 
       
   898 
       
   899 # XXX use cases where we don't actually want a better widget?
       
   900 class CompoundField(Field):
       
   901     """This field shouldn't be used directly, it's designed to hold inner
       
   902     fields that should be conceptually groupped together.
       
   903     """
       
   904     def __init__(self, fields, *args, **kwargs):
       
   905         super(CompoundField, self).__init__(*args, **kwargs)
       
   906         self.fields = fields
       
   907 
       
   908     def subfields(self, form):
       
   909         return self.fields
       
   910 
       
   911     def actual_fields(self, form):
       
   912         # don't add [self] to actual fields, compound field is usually kinda
       
   913         # virtual, all interesting values are in subfield. Skipping it may avoid
       
   914         # error when processed by the editcontroller : it may be marked as required
       
   915         # while it has no value, hence generating a false error.
       
   916         return list(self.fields)
   733 
   917 
   734 
   918 
   735 # relation vocabulary helper functions #########################################
   919 # relation vocabulary helper functions #########################################
   736 
   920 
   737 def relvoc_linkedto(entity, rtype, role):
   921 def relvoc_linkedto(entity, rtype, role):
   784         res.append((entity.view('combobox'), entity.eid))
   968         res.append((entity.view('combobox'), entity.eid))
   785     return res
   969     return res
   786 
   970 
   787 
   971 
   788 class RelationField(Field):
   972 class RelationField(Field):
   789     """the relation field to edit non final relations of an entity"""
   973     """Use this field to edit a relation of an entity.
       
   974 
       
   975     Unless explicitly specified, the widget for this field will be a
       
   976     :class:`~cubicweb.web.formwidgets.Select`.
       
   977     """
   790 
   978 
   791     @staticmethod
   979     @staticmethod
   792     def fromcardinality(card, **kwargs):
   980     def fromcardinality(card, **kwargs):
   793         kwargs.setdefault('widget', fw.Select(multiple=card in '*+'))
   981         kwargs.setdefault('widget', fw.Select(multiple=card in '*+'))
   794         return RelationField(**kwargs)
   982         return RelationField(**kwargs)
   867                 form._cw.data['pendingfields'].add( (form, self) )
  1055                 form._cw.data['pendingfields'].add( (form, self) )
   868                 return None
  1056                 return None
   869             eids.add(typed_eid)
  1057             eids.add(typed_eid)
   870         return eids
  1058         return eids
   871 
  1059 
   872 # XXX use cases where we don't actually want a better widget?
       
   873 class CompoundField(Field):
       
   874     def __init__(self, fields, *args, **kwargs):
       
   875         super(CompoundField, self).__init__(*args, **kwargs)
       
   876         self.fields = fields
       
   877 
       
   878     def subfields(self, form):
       
   879         return self.fields
       
   880 
       
   881     def actual_fields(self, form):
       
   882         # don't add [self] to actual fields, compound field is usually kinda
       
   883         # virtual, all interesting values are in subfield. Skipping it may avoid
       
   884         # error when processed by the editcontroller : it may be marked as required
       
   885         # while it has no value, hence generating a false error.
       
   886         return list(self.fields)
       
   887 
       
   888 
  1060 
   889 _AFF_KWARGS = uicfg.autoform_field_kwargs
  1061 _AFF_KWARGS = uicfg.autoform_field_kwargs
   890 
  1062 
   891 def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
  1063 def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs):
   892     """return the most adapated widget to edit the relation
  1064     """This function return the most adapted field to edit the given relation
   893     'subjschema rschema objschema' according to information found in the schema
  1065     (`rschema`) where the given entity type (`eschema`) is the subject or object
       
  1066     (`role`).
       
  1067 
       
  1068     The field is initialized according to information found in the schema,
       
  1069     though any value can be explicitly specified using `kwargs`.
       
  1070 
       
  1071     The `skip_meta_attr` flag is used to specify wether this function should
       
  1072     return a field for attributes considered as a meta-attributes
       
  1073     (e.g. describing an other attribute, such as the format or file name of a
       
  1074     file (`Bytes`) attribute).
   894     """
  1075     """
   895     fieldclass = None
  1076     fieldclass = None
   896     rdef = eschema.rdef(rschema, role)
  1077     rdef = eschema.rdef(rschema, role)
   897     if role == 'subject':
  1078     if role == 'subject':
   898         targetschema = rdef.object
  1079         targetschema = rdef.object