diff -r 4176a50c81c9 -r d321e4b62a10 web/formfields.py --- a/web/formfields.py Wed Apr 21 16:53:25 2010 +0200 +++ b/web/formfields.py Wed Apr 21 16:53:47 2010 +0200 @@ -1,11 +1,50 @@ -"""Fields are used 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. +# organization: Logilab +# copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +# contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +# license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +The Field class and basic fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. Note:: + Fields are used to control what's edited in forms. They makes the link between + something to edit and its display in the form. Actual display is handled by a + widget associated to the field. + +Let first see the base class for fields: + +.. autoclass:: cubicweb.web.formfields.Field + +Now, you usually don't use that class but one of the concret field classes +described below, according to what you want to edit. + +Basic fields +'''''''''''' -:organization: Logilab -:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +.. autoclass:: cubicweb.web.formfields.StringField() +.. autoclass:: cubicweb.web.formfields.PasswordField() +.. autoclass:: cubicweb.web.formfields.IntField() +.. autoclass:: cubicweb.web.formfields.FloatField() +.. autoclass:: cubicweb.web.formfields.BooleanField() +.. autoclass:: cubicweb.web.formfields.DateField() +.. autoclass:: cubicweb.web.formfields.DateTimeField() +.. autoclass:: cubicweb.web.formfields.TimeField() + +Compound fields +'''''''''''''''' + +.. autoclass:: cubicweb.web.formfields.RichTextField() +.. autoclass:: cubicweb.web.formfields.FileField() +.. autoclass:: cubicweb.web.formfields.CompoundField() + +.. autoclass cubicweb.web.formfields.EditableFileField() XXX should be a widget + +Entity specific fields and function +''''''''''''''''''''''''''''''''''' + +.. autoclass:: cubicweb.web.formfields.RelationField() +.. autofunction:: cubicweb.web.formfields.guess_field + """ __docformat__ = "restructuredtext en" @@ -53,48 +92,73 @@ of attributes which may be used for fine control of the behaviour of a concret field. + **Attributes** + All the attributes described below have sensible default value which may be - overriden by value given to field's constructor. + overriden by named arguments 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 + :attr:`name` + base name of the field (basestring). The actual input name is returned by + the :meth:`input_name` method and may differ from that name (for instance + if `eidparam` is true). + :attr:`id` + DOM identifier (default to the same value as `name`), should be unique in a form. - :label: + :attr:`label` label of the field (default to the same value as `name`). - :help: + :attr:`help` help message about this field. - :widget: + :attr:`widget` widget associated to the field. Each field class has a default widget class which may be overriden per instance. - :required: + :attr:`value` + field value. May be an actual value or a callable which should take the + form as argument and return a value. + :attr:`choices` + static vocabulary for this field. May be a list of values, a list of + (label, value) tuples or a callable which should take the form and field + as arguments and return a list of values or a list of (label, value). + :attr:`required` bool flag telling if the field is required or not. - :value: - field value (may be an actual value, a default value or nothing) - :choices: - static vocabulary for this field. May be a list of values or a list of - (label, value) tuples if specified. - :sort: + :attr:`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: + :attr:`internationalizable` bool flag telling if the vocabulary labels should be translated using the current request language. - :eidparam: + :attr:`eidparam` bool flag telling if this field is linked to a specific entity - :role: + :attr:`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') - :fieldset: + :attr:`fieldset` optional fieldset to which this field belongs to - :order: + :attr:`order` key used by automatic forms to sort fields - :ignore_req_params: + :attr:`ignore_req_params` when true, this field won't consider value potentialy specified using request's form parameters (eg you won't be able to specify a value using for instance url like http://mywebsite.com/form?field=value) + + .. currentmodule:: cubicweb.web.formfields + + **Generic methods** + + .. automethod:: Field.input_name + .. automethod:: Field.dom_id + .. automethod:: Field.actual_fields + + **Form generation methods** + + .. automethod:: form_init + .. automethod:: typed_value + + **Post handling methods** + + .. automethod:: process_posted + .. automethod:: process_form_value + """ # default widget associated to this class of fields. May be overriden per # instance @@ -164,8 +228,11 @@ return not isinstance(self.widget, fw.HiddenInput) def actual_fields(self, form): - """return actual fields composing this field in case of a compound - field, usually simply return self + """Fields may be composed of other fields. For instance the + :class:`~cubicweb.web.formfields.RichTextField` is containing a format + field to define the text format. This method returns actual fields that + should be considered for display / edition. It usually simply return + self. """ yield self @@ -190,7 +257,10 @@ return self.widget def input_name(self, form, suffix=None): - """return 'qualified name' for this field""" + """Return the 'qualified name' for this field, e.g. something suitable + to use as HTML input name. You can specify a suffix that will be + included in the name when widget needs several inputs. + """ # caching is necessary else we get some pb on entity creation : # entity.eid is modified from creation mark (eg 'X') to its actual eid # (eg 123), and then `field.input_name()` won't return the right key @@ -219,7 +289,10 @@ return self.name def dom_id(self, form, suffix=None): - """return an html dom identifier for this field""" + """Return the HTML DOM identifier for this field, e.g. something + suitable to use as HTML input id. You can specify a suffix that will be + included in the name when widget needs several inputs. + """ id = self.id or self.role_name() if suffix is not None: id += suffix @@ -228,6 +301,8 @@ return id def typed_value(self, form, load_bytes=False): + """Return the correctly typed value for this field in the form context. + """ if self.eidparam and self.role is not None: entity = form.edited_entity if form._cw.vreg.schema.rschema(self.name).final: @@ -324,8 +399,8 @@ return form._cw.encoding def form_init(self, form): - """method called before by build_context to trigger potential field - initialization requiring the form instance + """Method called at form initialization to trigger potential field + initialization requiring the form instance. Do nothing by default. """ pass @@ -359,7 +434,7 @@ return True def process_form_value(self, form): - """process posted form and return correctly typed value""" + """Return the correctly typed value posted for this field.""" try: return form.formvalues[(self, form)] except KeyError: @@ -379,6 +454,9 @@ return value or None def process_posted(self, form): + """Return an iterator on (field, value) that has been posted for + field returned by :meth:`~cubicweb.web.formfields.Field.actual_fields`. + """ for field in self.actual_fields(form): if field is self: try: @@ -396,6 +474,20 @@ class StringField(Field): + """Use this field to edit unicode string (`String` yams type). This field + additionaly support a `max_length` attribute that specify a maximum size for + the string (`None` meaning no limit). + + Unless explicitly specified, the widget for this field will be: + + * :class:`~cubicweb.web.formwidgets.Select` if some vocabulary is specified + using `choices` attribute + + * :class:`~cubicweb.web.formwidgets.TextInput` if maximum size is specified + using `max_length` attribute and this length is inferior to 257. + + * :class:`~cubicweb.web.formwidgets.TextArea` in all other cases + """ widget = fw.TextArea size = 45 @@ -428,6 +520,12 @@ class PasswordField(StringField): + """Use this field to edit password (`Password` yams type, encoded python + string). + + Unless explicitly specified, the widget for this field will be + a :class:`~cubicweb.web.formwidgets.PasswordInput`. + """ widget = fw.PasswordInput def form_init(self, form): if self.eidparam and form.edited_entity.has_eid(): @@ -445,6 +543,17 @@ class RichTextField(StringField): + """This compound field allow edition of text (unicode string) in + a particular format. It has an inner field holding the text format, + that can be specified using `format_field` argument. If not specified + one will be automaticall generated. + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.FCKEditor` or a + :class:`~cubicweb.web.formwidgets.TextArea`. according to the field's + format and to user's preferences. + """ + widget = None def __init__(self, format_field=None, **kwargs): super(RichTextField, self).__init__(**kwargs) @@ -516,6 +625,17 @@ class FileField(StringField): + """This compound field allow edition of binary stream (`Bytes` yams + type). Three inner fields may be specified: + + * `format_field`, holding the file's format. + * `encoding_field`, holding the file's content encoding. + * `name_field`, holding the file's name. + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.FileInput`. Inner fields, if any, + will be added to a drop down menu at the right of the file input. + """ widget = fw.FileInput needs_multipart = True @@ -604,6 +724,15 @@ # XXX turn into a widget class EditableFileField(FileField): + """This compound field allow edition of binary stream as + :class:`~cubicweb.web.formfields.FileField` but expect that stream to + actually contains some text. + + If the stream format is one of text/plain, text/html, text/rest, + then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly + displayed, allowing to directly the file's content when desired, instead + of choosing a file from user's file system. + """ editable_formats = ('text/plain', 'text/html', 'text/rest') def render(self, form, renderer): @@ -641,6 +770,13 @@ class IntField(Field): + """Use this field to edit integers (`Int` yams type). This field additionaly + support `min` and `max` attributes that specify a minimum and/or maximum + value for the integer (`None` meaning no boundary). + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.TextInput`. + """ def __init__(self, min=None, max=None, **kwargs): super(IntField, self).__init__(**kwargs) self.min = min @@ -662,6 +798,12 @@ class BooleanField(Field): + """Use this field to edit booleans (`Boolean` yams type). + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.Radio` with yes/no values. You + can change that values by specifing `choices`. + """ widget = fw.Radio def vocabulary(self, form): @@ -674,6 +816,13 @@ class FloatField(IntField): + """Use this field to edit floats (`Float` yams type). This field additionaly + support `min` and `max` attributes as the + :class:`~cubicweb.web.formfields.IntField`. + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.TextInput`. + """ def format_single_value(self, req, value): formatstr = req.property_value('ui.float-format') if value is None: @@ -696,6 +845,11 @@ class DateField(StringField): + """Use this field to edit date (`Date` yams type). + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.JQueryDatePicker`. + """ widget = fw.JQueryDatePicker format_prop = 'ui.date-format' etype = 'Date' @@ -721,17 +875,47 @@ class DateTimeField(DateField): + """Use this field to edit datetime (`Datetime` yams type). + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.JQueryDateTimePicker`. + """ widget = fw.JQueryDateTimePicker format_prop = 'ui.datetime-format' etype = 'Datetime' class TimeField(DateField): + """Use this field to edit time (`Time` yams type). + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.JQueryTimePicker`. + """ widget = fw.JQueryTimePicker format_prop = 'ui.time-format' etype = 'Time' +# XXX use cases where we don't actually want a better widget? +class CompoundField(Field): + """This field shouldn't be used directly, it's designed to hold inner + fields that should be conceptually groupped together. + """ + def __init__(self, fields, *args, **kwargs): + super(CompoundField, self).__init__(*args, **kwargs) + self.fields = fields + + def subfields(self, form): + return self.fields + + def actual_fields(self, form): + # don't add [self] to actual fields, compound field is usually kinda + # virtual, all interesting values are in subfield. Skipping it may avoid + # error when processed by the editcontroller : it may be marked as required + # while it has no value, hence generating a false error. + return list(self.fields) + + # relation vocabulary helper functions ######################################### def relvoc_linkedto(entity, rtype, role): @@ -786,7 +970,11 @@ class RelationField(Field): - """the relation field to edit non final relations of an entity""" + """Use this field to edit a relation of an entity. + + Unless explicitly specified, the widget for this field will be a + :class:`~cubicweb.web.formwidgets.Select`. + """ @staticmethod def fromcardinality(card, **kwargs): @@ -869,28 +1057,21 @@ eids.add(typed_eid) return eids -# XXX use cases where we don't actually want a better widget? -class CompoundField(Field): - def __init__(self, fields, *args, **kwargs): - super(CompoundField, self).__init__(*args, **kwargs) - self.fields = fields - - def subfields(self, form): - return self.fields - - def actual_fields(self, form): - # don't add [self] to actual fields, compound field is usually kinda - # virtual, all interesting values are in subfield. Skipping it may avoid - # error when processed by the editcontroller : it may be marked as required - # while it has no value, hence generating a false error. - return list(self.fields) - _AFF_KWARGS = uicfg.autoform_field_kwargs def guess_field(eschema, rschema, role='subject', skip_meta_attr=True, **kwargs): - """return the most adapated widget to edit the relation - 'subjschema rschema objschema' according to information found in the schema + """This function return the most adapted field to edit the given relation + (`rschema`) where the given entity type (`eschema`) is the subject or object + (`role`). + + The field is initialized according to information found in the schema, + though any value can be explicitly specified using `kwargs`. + + The `skip_meta_attr` flag is used to specify wether this function should + return a field for attributes considered as a meta-attributes + (e.g. describing an other attribute, such as the format or file name of a + file (`Bytes`) attribute). """ fieldclass = None rdef = eschema.rdef(rschema, role)