web/formfields.py
changeset 4159 6b2b20c73d59
parent 4158 0e97cf2cf55b
child 4160 3fbdeef9a610
equal deleted inserted replaced
4158:0e97cf2cf55b 4159:6b2b20c73d59
    37         else:
    37         else:
    38             partresult.append( (label, value) )
    38             partresult.append( (label, value) )
    39     result += sorted(partresult)
    39     result += sorted(partresult)
    40     return result
    40     return result
    41 
    41 
       
    42 _MARKER = object()
    42 
    43 
    43 class Field(object):
    44 class Field(object):
    44     """field class is introduced to control what's displayed in forms. It makes
    45     """field class is introduced to control what's displayed in forms. It makes
    45     the link between something to edit and its display in the form. Actual
    46     the link between something to edit and its display in the form. Actual
    46     display is handled by a widget associated to the field.
    47     display is handled by a widget associated to the field.
    62     :widget:
    63     :widget:
    63        widget associated to the field. Each field class has a default widget
    64        widget associated to the field. Each field class has a default widget
    64        class which may be overriden per instance.
    65        class which may be overriden per instance.
    65     :required:
    66     :required:
    66        bool flag telling if the field is required or not.
    67        bool flag telling if the field is required or not.
    67     :initial:
    68     :value:
    68        initial value, used when no value specified by other means.
    69        field's value, used when no value specified by other means. XXX explain
    69     :choices:
    70     :choices:
    70        static vocabulary for this field. May be a list of values or a list of
    71        static vocabulary for this field. May be a list of values or a list of
    71        (label, value) tuples if specified.
    72        (label, value) tuples if specified.
    72     :sort:
    73     :sort:
    73        bool flag telling if the vocabulary (either static vocabulary specified
    74        bool flag telling if the vocabulary (either static vocabulary specified
    93     # does this field requires a multipart form
    94     # does this field requires a multipart form
    94     needs_multipart = False
    95     needs_multipart = False
    95     # class attribute used for ordering of fields in a form
    96     # class attribute used for ordering of fields in a form
    96     __creation_rank = 0
    97     __creation_rank = 0
    97 
    98 
    98     def __init__(self, name=None, id=None, label=None, help=None,
    99     eidparam = False
    99                  widget=None, required=False, initial=None,
   100     role = None
   100                  choices=None, sort=True, internationalizable=False,
   101     id = None
   101                  eidparam=False, role='subject', fieldset=None, order=None):
   102     help = None
       
   103     required = False
       
   104     choices = None
       
   105     sort = True
       
   106     internationalizable = False
       
   107     fieldset = None
       
   108     order = None
       
   109     value = _MARKER
       
   110 
       
   111     def __init__(self, name=None, label=None, widget=None, **kwargs):
       
   112         for key, val in kwargs.items():
       
   113             if key == 'initial':
       
   114                 warn('[3.6] use value instead of initial', DeprecationWarning,
       
   115                      stacklevel=3)
       
   116                 key = 'value'
       
   117             assert hasattr(self.__class__, key) and not key[0] == '_', key
       
   118             setattr(self, key, val)
   102         self.name = name
   119         self.name = name
   103         self.id = id or name
       
   104         self.label = label or name
   120         self.label = label or name
   105         self.help = help
   121         # has to be done after other attributes initialization
   106         self.required = required
       
   107         self.initial = initial
       
   108         self.choices = choices
       
   109         self.sort = sort
       
   110         self.internationalizable = internationalizable
       
   111         self.eidparam = eidparam
       
   112         self.role = role
       
   113         self.fieldset = fieldset
       
   114         self.init_widget(widget)
   122         self.init_widget(widget)
   115         self.order = order
       
   116         # ordering number for this field instance
   123         # ordering number for this field instance
   117         self.creation_rank = Field.__creation_rank
   124         self.creation_rank = Field.__creation_rank
   118         Field.__creation_rank += 1
   125         Field.__creation_rank += 1
   119 
   126 
   120     def __unicode__(self):
   127     def __unicode__(self):
   121         return u'<%s name=%r label=%r id=%r initial=%r visible=%r @%x>' % (
   128         return u'<%s name=%r eidparam=%s role=%r id=%r value=%r visible=%r @%x>' % (
   122             self.__class__.__name__, self.name, self.label,
   129             self.__class__.__name__, self.name, self.eidparam, self.role,
   123             self.id, self.initial, self.is_visible(), id(self))
   130             self.id, self.value, self.is_visible(), id(self))
   124 
   131 
   125     def __repr__(self):
   132     def __repr__(self):
   126         return self.__unicode__().encode('utf-8')
   133         return self.__unicode__().encode('utf-8')
   127 
   134 
   128     def init_widget(self, widget):
   135     def init_widget(self, widget):
   132             self.widget = Select()
   139             self.widget = Select()
   133         if isinstance(self.widget, type):
   140         if isinstance(self.widget, type):
   134             self.widget = self.widget()
   141             self.widget = self.widget()
   135 
   142 
   136     def set_name(self, name):
   143     def set_name(self, name):
   137         """automatically set .id and .label when name is set"""
   144         """automatically set .label when name is set"""
   138         assert name
   145         assert name
   139         self.name = name
   146         self.name = name
   140         if not self.id:
       
   141             self.id = name
       
   142         if not self.label:
   147         if not self.label:
   143             self.label = name
   148             self.label = name
   144 
   149 
   145     def is_visible(self):
   150     def is_visible(self):
   146         """return true if the field is not an hidden field"""
   151         """return true if the field is not an hidden field"""
   197         """return an html dom identifier for this field"""
   202         """return an html dom identifier for this field"""
   198         id = self.id or self.role_name()
   203         id = self.id or self.role_name()
   199         if self.eidparam:
   204         if self.eidparam:
   200             return eid_param(id, form.edited_entity.eid)
   205             return eid_param(id, form.edited_entity.eid)
   201         return id
   206         return id
       
   207 
       
   208     def display_value(self, form):
       
   209         """return field's *string* value to use for display
       
   210 
       
   211         looks in
       
   212         1. previously submitted form values if any (eg on validation error)
       
   213         2. req.form
       
   214         3. extra form args given to render_form
       
   215         4. field's typed value
       
   216 
       
   217         values found in 1. and 2. are expected te be already some 'display'
       
   218         value while those found in 3. and 4. are expected to be correctly typed.
       
   219         """
       
   220         qname = self.input_name(form)
       
   221         if qname in form.form_previous_values:
       
   222             return form.form_previous_values[qname]
       
   223         if qname in form._cw.form:
       
   224             return form._cw.form[qname]
       
   225         if self.name != qname and self.name in form._cw.form:
       
   226             return form._cw.form[self.name]
       
   227         for key in (self, qname):
       
   228             try:
       
   229                 value = form.formvalues[key]
       
   230                 break
       
   231             except:
       
   232                 continue
       
   233         else:
       
   234             if self.name != qname and self.name in form.formvalues:
       
   235                 value = form.formvalues[self.name]
       
   236             else:
       
   237                 value = self.typed_value(form)
       
   238         if value != INTERNAL_FIELD_VALUE:
       
   239             value = self.format_value(form._cw, value)
       
   240         return value
       
   241 
       
   242     def typed_value(self, form, load_bytes=False):
       
   243         if self.value is not _MARKER:
       
   244             if callable(self.value):
       
   245                 return self.value(form)
       
   246             return self.value
       
   247         return self._typed_value(form, load_bytes)
       
   248 
       
   249     def _typed_value(self, form, load_bytes=False):
       
   250         if self.eidparam:
       
   251             assert form._cw.vreg.schema.rschema(self.name).final
       
   252             entity = form.edited_entity
       
   253             if entity.has_eid() or self.name in entity:
       
   254                 return getattr(entity, self.name)
       
   255         formattr = '%s_%s_default' % (self.role, self.name)
       
   256         if hasattr(form, formattr):
       
   257             warn('[3.6] %s.%s deprecated, use field.value' % (
       
   258                 form.__class__.__name__, formattr), DeprecationWarning)
       
   259             return getattr(form, formattr)()
       
   260         if self.eidparam:
       
   261             return entity.e_schema.default(self.name)
       
   262         return None
   202 
   263 
   203     def example_format(self, req):
   264     def example_format(self, req):
   204         """return a sample string describing what can be given as input for this
   265         """return a sample string describing what can be given as input for this
   205         field
   266         field
   206         """
   267         """
   290         if self.max_length < 513:
   351         if self.max_length < 513:
   291             widget.attrs.setdefault('cols', 60)
   352             widget.attrs.setdefault('cols', 60)
   292             widget.attrs.setdefault('rows', 5)
   353             widget.attrs.setdefault('rows', 5)
   293 
   354 
   294 
   355 
       
   356 class PasswordField(StringField):
       
   357     widget = PasswordInput
       
   358 
       
   359     def _typed_value(self, form, load_bytes=False):
       
   360         if self.eidparam:
       
   361             # no way to fetch actual password value with cw
       
   362             if form.edited_entity.has_eid():
       
   363                 return INTERNAL_FIELD_VALUE
       
   364             return form.edited_entity.e_schema.default(self.name)
       
   365         return super(PasswordField, self)._typed_value(form, load_bytes)
       
   366 
       
   367 
   295 class RichTextField(StringField):
   368 class RichTextField(StringField):
   296     widget = None
   369     widget = None
   297     def __init__(self, format_field=None, **kwargs):
   370     def __init__(self, format_field=None, **kwargs):
   298         super(RichTextField, self).__init__(**kwargs)
   371         super(RichTextField, self).__init__(**kwargs)
   299         self.format_field = format_field
   372         self.format_field = format_field
   322             fkwargs = {'eidparam': self.eidparam}
   395             fkwargs = {'eidparam': self.eidparam}
   323             if self.use_fckeditor(form):
   396             if self.use_fckeditor(form):
   324                 # if fckeditor is used and format field isn't explicitly
   397                 # if fckeditor is used and format field isn't explicitly
   325                 # deactivated, we want an hidden field for the format
   398                 # deactivated, we want an hidden field for the format
   326                 fkwargs['widget'] = HiddenInput()
   399                 fkwargs['widget'] = HiddenInput()
   327                 fkwargs['initial'] = 'text/html'
   400                 fkwargs['value'] = 'text/html'
   328             else:
   401             else:
   329                 # else we want a format selector
   402                 # else we want a format selector
   330                 fkwargs['widget'] = Select()
   403                 fkwargs['widget'] = Select()
   331                 fcstr = FormatConstraint()
   404                 fcstr = FormatConstraint()
   332                 fkwargs['choices'] = fcstr.vocabulary(form=form)
   405                 fkwargs['choices'] = fcstr.vocabulary(form=form)
   333                 fkwargs['internationalizable'] = True
   406                 fkwargs['internationalizable'] = True
   334                 fkwargs['initial'] = lambda f: f.form_field_format(self)
   407                 fkwargs['value'] = self.format
   335             fkwargs['eidparam'] = self.eidparam
   408             fkwargs['eidparam'] = self.eidparam
   336             field = StringField(name=self.name + '_format', **fkwargs)
   409             field = StringField(name=self.name + '_format', **fkwargs)
   337             req.data[self] = field
   410             req.data[self] = field
   338             return field
   411             return field
   339 
   412 
   380             yield self.format_field
   453             yield self.format_field
   381         if self.encoding_field:
   454         if self.encoding_field:
   382             yield self.encoding_field
   455             yield self.encoding_field
   383         if self.name_field:
   456         if self.name_field:
   384             yield self.name_field
   457             yield self.name_field
       
   458 
       
   459     def _typed_value(self, form, load_bytes=False):
       
   460         if self.eidparam:
       
   461             if form.edited_entity.has_eid():
       
   462                 if load_bytes:
       
   463                     return getattr(form.edited_entity, self.name)
       
   464                 # don't actually load data
       
   465                 # XXX value should reflect if some file is already attached
       
   466                 # * try to display name metadata
       
   467                 # * check length(data) / data != null
       
   468                 return True
       
   469             return False
       
   470         return super(FileField, self)._typed_value(form, load_bytes)
   385 
   471 
   386     def render(self, form, renderer):
   472     def render(self, form, renderer):
   387         wdgs = [self.get_widget(form).render(form, self, renderer)]
   473         wdgs = [self.get_widget(form).render(form, self, renderer)]
   388         if self.format_field or self.encoding_field:
   474         if self.format_field or self.encoding_field:
   389             divid = '%s-advanced' % self.input_name(form)
   475             divid = '%s-advanced' % self.input_name(form)
   398             if self.format_field:
   484             if self.format_field:
   399                 wdgs.append(self.render_subfield(form, self.format_field, renderer))
   485                 wdgs.append(self.render_subfield(form, self.format_field, renderer))
   400             if self.encoding_field:
   486             if self.encoding_field:
   401                 wdgs.append(self.render_subfield(form, self.encoding_field, renderer))
   487                 wdgs.append(self.render_subfield(form, self.encoding_field, renderer))
   402             wdgs.append(u'</div>')
   488             wdgs.append(u'</div>')
   403         if not self.required and form.context[self]['value']:
   489         if not self.required and self.display_value(form):
   404             # trick to be able to delete an uploaded file
   490             # trick to be able to delete an uploaded file
   405             wdgs.append(u'<br/>')
   491             wdgs.append(u'<br/>')
   406             wdgs.append(tags.input(name=self.input_name(form, u'__detach'),
   492             wdgs.append(tags.input(name=self.input_name(form, u'__detach'),
   407                                    type=u'checkbox'))
   493                                    type=u'checkbox'))
   408             wdgs.append(form._cw._('detach attached file'))
   494             wdgs.append(form._cw._('detach attached file'))
   437 class EditableFileField(FileField):
   523 class EditableFileField(FileField):
   438     editable_formats = ('text/plain', 'text/html', 'text/rest')
   524     editable_formats = ('text/plain', 'text/html', 'text/rest')
   439 
   525 
   440     def render(self, form, renderer):
   526     def render(self, form, renderer):
   441         wdgs = [super(EditableFileField, self).render(form, renderer)]
   527         wdgs = [super(EditableFileField, self).render(form, renderer)]
   442         if form.form_field_format(self) in self.editable_formats:
   528         if self.format(form) in self.editable_formats:
   443             data = form.form_field_value(self, load_bytes=True)
   529             data = self.typed_value(form, load_bytes=True)
   444             if data:
   530             if data:
   445                 encoding = form.form_field_encoding(self)
   531                 encoding = form.form_field_encoding(self)
   446                 try:
   532                 try:
   447                     form.context[self]['value'] = unicode(data.getvalue(), encoding)
   533                     form.formvalues[self] = unicode(data.getvalue(), encoding)
   448                 except UnicodeError:
   534                 except UnicodeError:
   449                     pass
   535                     pass
   450                 else:
   536                 else:
   451                     if not self.required:
   537                     if not self.required:
   452                         msg = form._cw._(
   538                         msg = form._cw._(
   590         vocab = res + form.form_field_vocabulary(self) + relatedvocab
   676         vocab = res + form.form_field_vocabulary(self) + relatedvocab
   591         if self.sort:
   677         if self.sort:
   592             vocab = vocab_sort(vocab)
   678             vocab = vocab_sort(vocab)
   593         return vocab
   679         return vocab
   594 
   680 
       
   681     def form_init(self, form):
       
   682         if not self.display_value(form):
       
   683             value = form.edited_entity.linked_to(self.name, self.role)
       
   684             if value:
       
   685                 searchedvalues = ['%s:%s:%s' % (self.name, eid, self.role)
       
   686                                   for eid in value]
       
   687                 # remove associated __linkto hidden fields
       
   688                 for field in form.root_form.fields_by_name('__linkto'):
       
   689                     if field.value in searchedvalues:
       
   690                         form.root_form.remove_field(field)
       
   691                 form.formvalues[self] = value
       
   692 
       
   693     def _typed_value(self, form, load_bytes=False):
       
   694         entity = form.edited_entity
       
   695         # non final relation field
       
   696         if entity.has_eid() or entity.relation_cached(self.name, self.role):
       
   697             return [r[0] for r in entity.related(self.name, self.role)]
       
   698         return ()
       
   699 
   595     def format_single_value(self, req, value):
   700     def format_single_value(self, req, value):
   596         return value
   701         return value
   597 
   702 
   598     def process_form_value(self, form):
   703     def process_form_value(self, form):
   599         """process posted form and return correctly typed value"""
   704         """process posted form and return correctly typed value"""
   626     if role == 'subject':
   731     if role == 'subject':
   627         targetschema = rdef.object
   732         targetschema = rdef.object
   628         if rschema.final:
   733         if rschema.final:
   629             if rdef.get('internationalizable'):
   734             if rdef.get('internationalizable'):
   630                 kwargs.setdefault('internationalizable', True)
   735                 kwargs.setdefault('internationalizable', True)
   631             def get_default(form, es=eschema, rs=rschema):
       
   632                 return es.default(rs)
       
   633             kwargs.setdefault('initial', get_default)
       
   634     else:
   736     else:
   635         targetschema = rdef.subject
   737         targetschema = rdef.subject
   636     card = rdef.role_cardinality(role)
   738     card = rdef.role_cardinality(role)
   637     kwargs['required'] = card in '1+'
   739     kwargs['required'] = card in '1+'
   638     kwargs['name'] = rschema.type
   740     kwargs['name'] = rschema.type
   645     if rschema.final:
   747     if rschema.final:
   646         if skip_meta_attr and rschema in eschema.meta_attributes():
   748         if skip_meta_attr and rschema in eschema.meta_attributes():
   647             return None
   749             return None
   648         fieldclass = FIELDS[targetschema]
   750         fieldclass = FIELDS[targetschema]
   649         if fieldclass is StringField:
   751         if fieldclass is StringField:
   650             if targetschema == 'Password':
       
   651                 # special case for Password field: specific PasswordInput widget
       
   652                 kwargs.setdefault('widget', PasswordInput())
       
   653                 return StringField(**kwargs)
       
   654             if eschema.has_metadata(rschema, 'format'):
   752             if eschema.has_metadata(rschema, 'format'):
   655                 # use RichTextField instead of StringField if the attribute has
   753                 # use RichTextField instead of StringField if the attribute has
   656                 # a "format" metadata. But getting information from constraints
   754                 # a "format" metadata. But getting information from constraints
   657                 # may be useful anyway...
   755                 # may be useful anyway...
   658                 for cstr in rdef.constraints:
   756                 for cstr in rdef.constraints:
   685     'Date':     DateField,
   783     'Date':     DateField,
   686     'Datetime': DateTimeField,
   784     'Datetime': DateTimeField,
   687     'Int':      IntField,
   785     'Int':      IntField,
   688     'Float':    FloatField,
   786     'Float':    FloatField,
   689     'Decimal':  StringField,
   787     'Decimal':  StringField,
   690     'Password': StringField,
   788     'Password': PasswordField,
   691     'String' :  StringField,
   789     'String' :  StringField,
   692     'Time':     TimeField,
   790     'Time':     TimeField,
   693     }
   791     }