web/form.py
branchtls-sprint
changeset 1393 ff6758d7b96f
parent 1392 d6279efff7b3
child 1400 5926626fb27b
equal deleted inserted replaced
1392:d6279efff7b3 1393:ff6758d7b96f
   204 
   204 
   205 
   205 
   206 ###############################################################################
   206 ###############################################################################
   207 
   207 
   208 class metafieldsform(type):
   208 class metafieldsform(type):
       
   209     """metaclass for FieldsForm to retreive fields defined as class attribute
       
   210     and put them into a single ordered list, '_fields_'.
       
   211     """
   209     def __new__(mcs, name, bases, classdict):
   212     def __new__(mcs, name, bases, classdict):
   210         allfields = []
   213         allfields = []
   211         for base in bases:
   214         for base in bases:
   212             if hasattr(base, '_fields_'):
   215             if hasattr(base, '_fields_'):
   213                 allfields += base._fields_
   216                 allfields += base._fields_
   383         """return field's *typed* value"""
   386         """return field's *typed* value"""
   384         value = field.initial
   387         value = field.initial
   385         if callable(value):
   388         if callable(value):
   386             value = value(self)
   389             value = value(self)
   387         return value
   390         return value
   388 
       
   389     def _errex_match_field(self, errex, field):
       
   390         """return true if the field has some error in given validation exception
       
   391         """
       
   392         return field.name in errex.errors
       
   393     
   391     
   394     def form_field_error(self, field):
   392     def form_field_error(self, field):
   395         """return validation error for widget's field, if any"""
   393         """return validation error for widget's field, if any"""
   396         errex = self.req.data.get('formerrors')
   394         errex = self.req.data.get('formerrors')
   397         if errex and self._errex_match_field(errex, field):
   395         if errex and self._errex_match_field(errex, field):
   398             self.req.data['displayederrors'].add(field.name)
   396             self.req.data['displayederrors'].add(field.name)
   399             return u'<span class="error">%s</span>' % errex.errors[field.name]
   397             return u'<span class="error">%s</span>' % errex.errors[field.name]
   400         return u''
   398         return u''
   401 
   399 
   402     def form_field_format(self, field):
   400     def form_field_format(self, field):
       
   401         """return MIME type used for the given (text or bytes) field"""
   403         return self.req.property_value('ui.default-text-format')
   402         return self.req.property_value('ui.default-text-format')
   404     
   403     
   405     def form_field_encoding(self, field):
   404     def form_field_encoding(self, field):
       
   405         """return encoding used for the given (text) field"""
   406         return self.req.encoding
   406         return self.req.encoding
   407     
   407     
   408     def form_field_name(self, field):
   408     def form_field_name(self, field):
       
   409         """return qualified name for the given field"""
   409         return field.name
   410         return field.name
   410 
   411 
   411     def form_field_id(self, field):
   412     def form_field_id(self, field):
       
   413         """return dom id for the given field"""
   412         return field.id
   414         return field.id
   413    
   415    
   414     def form_field_vocabulary(self, field, limit=None):
   416     def form_field_vocabulary(self, field, limit=None):
       
   417         """return vocabulary for the given field. Should be overriden in
       
   418         specific forms using fields which requires some vocabulary
       
   419         """
   415         raise NotImplementedError
   420         raise NotImplementedError
       
   421 
       
   422     def _errex_match_field(self, errex, field):
       
   423         """return true if the field has some error in given validation exception
       
   424         """
       
   425         return field.name in errex.errors
   416 
   426 
   417    
   427    
   418 class EntityFieldsForm(FieldsForm):
   428 class EntityFieldsForm(FieldsForm):
   419     __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
   429     __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
   420     
   430     
   433             # If we need to directly attach the new object to another one
   443             # If we need to directly attach the new object to another one
   434             for linkto in self.req.list_form_param('__linkto'):
   444             for linkto in self.req.list_form_param('__linkto'):
   435                 self.form_add_hidden('__linkto', linkto)
   445                 self.form_add_hidden('__linkto', linkto)
   436                 msg = '%s %s' % (msg, self.req._('and linked'))
   446                 msg = '%s %s' % (msg, self.req._('and linked'))
   437             self.form_add_hidden('__message', msg)
   447             self.form_add_hidden('__message', msg)
       
   448     
       
   449     def _errex_match_field(self, errex, field):
       
   450         """return true if the field has some error in given validation exception
       
   451         """
       
   452         return errex.eid == self.edited_entity.eid and field.name in errex.errors
       
   453 
       
   454     def _relation_vocabulary(self, rtype, targettype, role,
       
   455                             limit=None, done=None):
       
   456         """return unrelated entities for a given relation and target entity type
       
   457         for use in vocabulary
       
   458         """
       
   459         if done is None:
       
   460             done = set()
       
   461         rset = self.edited_entity.unrelated(rtype, targettype, role, limit)
       
   462         res = []
       
   463         for entity in rset.entities():
       
   464             if entity.eid in done:
       
   465                 continue
       
   466             done.add(entity.eid)
       
   467             res.append((entity.view('combobox'), entity.eid))
       
   468         return res
       
   469 
       
   470     def _form_field_default_value(self, field, load_bytes):
       
   471         defaultattr = 'default_%s' % field.name
       
   472         if hasattr(self.edited_entity, defaultattr):
       
   473             # XXX bw compat, default_<field name> on the entity
       
   474             warn('found %s on %s, should be set on a specific form'
       
   475                  % (defaultattr, self.edited_entity.id), DeprecationWarning)
       
   476             value = getattr(self.edited_entity, defaultattr)
       
   477             if callable(value):
       
   478                 value = value()
       
   479         else:
       
   480             value = super(EntityFieldsForm, self).form_field_value(field,
       
   481                                                                    load_bytes)
       
   482         return value
   438         
   483         
   439     def form_build_context(self, values=None):
   484     def form_build_context(self, values=None):
   440         self.form_add_entity_hiddens(self.edited_entity.e_schema)
   485         """overriden to add edit[s|o] hidden fields and to ensure schema fields
   441         return super(EntityFieldsForm, self).form_build_context(values)
   486         have eidparam set to True
   442 
   487 
   443     def form_add_entity_hiddens(self, eschema):
   488         edit[s|o] hidden fields are used t o indicate the value for the
       
   489         associated field before the (potential) modification made when
       
   490         submitting the form.
       
   491         """
       
   492         eschema = self.edited_entity.e_schema
   444         for field in self.fields[:]:
   493         for field in self.fields[:]:
   445             for field in field.actual_fields(self):
   494             for field in field.actual_fields(self):
   446                 fieldname = field.name
   495                 fieldname = field.name
   447                 if fieldname != 'eid' and (
   496                 if fieldname != 'eid' and (
   448                     (eschema.has_subject_relation(fieldname) or
   497                     (eschema.has_subject_relation(fieldname) or
   449                      eschema.has_object_relation(fieldname))):
   498                      eschema.has_object_relation(fieldname))):
   450                     field.eidparam = True
   499                     field.eidparam = True
   451                     self.fields.append(self.form_entity_hidden_field(field))
   500                     self.fields.append(HiddenInitialValueField(field))
   452 
   501         return super(EntityFieldsForm, self).form_build_context(values)
   453     def form_entity_hidden_field(self, field):
       
   454         """returns the hidden field which will indicate the value
       
   455         before the modification
       
   456         """
       
   457         # Only RelationField has a `role` attribute, others are used
       
   458         # to describe attribute fields => role is 'subject'
       
   459         if getattr(field, 'role', 'subject') == 'subject':
       
   460             name = 'edits-%s' % field.name
       
   461         else:
       
   462             name = 'edito-%s' % field.name
       
   463         return HiddenInitialValueField(field, name=name)
       
   464         
   502         
   465     def form_field_value(self, field, load_bytes=False):
   503     def form_field_value(self, field, load_bytes=False):
   466         """return field's *typed* value
   504         """return field's *typed* value
   467 
   505 
   468         overriden to deal with
   506         overriden to deal with
   507         if entity.has_eid():
   545         if entity.has_eid():
   508             value = [ent.eid for ent in getattr(entity, attr)]
   546             value = [ent.eid for ent in getattr(entity, attr)]
   509         else:
   547         else:
   510             value = self._form_field_default_value(field, load_bytes)
   548             value = self._form_field_default_value(field, load_bytes)
   511         return value
   549         return value
   512 
       
   513     def _form_field_default_value(self, field, load_bytes):
       
   514         defaultattr = 'default_%s' % field.name
       
   515         if hasattr(self.edited_entity, defaultattr):
       
   516             # XXX bw compat, default_<field name> on the entity
       
   517             warn('found %s on %s, should be set on a specific form'
       
   518                  % (defaultattr, self.edited_entity.id), DeprecationWarning)
       
   519             value = getattr(self.edited_entity, defaultattr)
       
   520             if callable(value):
       
   521                 value = value()
       
   522         else:
       
   523             value = super(EntityFieldsForm, self).form_field_value(field,
       
   524                                                                    load_bytes)
       
   525         return value
       
   526         
   550         
   527     def form_field_format(self, field):
   551     def form_field_format(self, field):
       
   552         """return MIME type used for the given (text or bytes) field"""
   528         entity = self.edited_entity
   553         entity = self.edited_entity
   529         if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
   554         if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
   530             entity.has_eid() or '%s_format' % field.name in entity):
   555             entity.has_eid() or '%s_format' % field.name in entity):
   531             return self.edited_entity.attr_metadata(field.name, 'format')
   556             return self.edited_entity.attr_metadata(field.name, 'format')
   532         return self.req.property_value('ui.default-text-format')
   557         return self.req.property_value('ui.default-text-format')
   533 
   558 
   534     def form_field_encoding(self, field):
   559     def form_field_encoding(self, field):
       
   560         """return encoding used for the given (text) field"""
   535         entity = self.edited_entity
   561         entity = self.edited_entity
   536         if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
   562         if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
   537             entity.has_eid() or '%s_encoding' % field.name in entity):
   563             entity.has_eid() or '%s_encoding' % field.name in entity):
   538             return self.edited_entity.attr_metadata(field.name, 'encoding')
   564             return self.edited_entity.attr_metadata(field.name, 'encoding')
   539         return super(EntityFieldsForm, self).form_field_encoding(field)
   565         return super(EntityFieldsForm, self).form_field_encoding(field)    
   540     
       
   541     
       
   542     def _errex_match_field(self, errex, field):
       
   543         """return true if the field has some error in given validation exception
       
   544         """
       
   545         return errex.eid == self.edited_entity.eid and field.name in errex.errors
       
   546     
   566     
   547     def form_field_name(self, field):
   567     def form_field_name(self, field):
       
   568         """return qualified name for the given field"""
   548         if field.eidparam:
   569         if field.eidparam:
   549             return eid_param(field.name, self.edited_entity.eid)
   570             return eid_param(field.name, self.edited_entity.eid)
   550         return field.name
   571         return field.name
   551 
   572 
   552     def form_field_id(self, field):
   573     def form_field_id(self, field):
       
   574         """return dom id for the given field"""
   553         if field.eidparam:
   575         if field.eidparam:
   554             return eid_param(field.id, self.edited_entity.eid)
   576             return eid_param(field.id, self.edited_entity.eid)
   555         return field.id
   577         return field.id
   556         
   578         
   557     def form_field_vocabulary(self, field, limit=None):
   579     def form_field_vocabulary(self, field, limit=None):
       
   580         """return vocabulary for the given field"""
   558         role, rtype = field.role, field.name
   581         role, rtype = field.role, field.name
   559         method = '%s_%s_vocabulary' % (role, rtype)
   582         method = '%s_%s_vocabulary' % (role, rtype)
   560         try:
   583         try:
   561             vocabfunc = getattr(self, method)
   584             vocabfunc = getattr(self, method)
   562         except AttributeError:
   585         except AttributeError:
   589         result = []
   612         result = []
   590         rsetsize = None
   613         rsetsize = None
   591         for objtype in rtype.objects(entity.e_schema):
   614         for objtype in rtype.objects(entity.e_schema):
   592             if limit is not None:
   615             if limit is not None:
   593                 rsetsize = limit - len(result)
   616                 rsetsize = limit - len(result)
   594             result += self.relation_vocabulary(rtype, objtype, 'subject',
   617             result += self._relation_vocabulary(rtype, objtype, 'subject',
   595                                                rsetsize, done)
   618                                                 rsetsize, done)
   596             if limit is not None and len(result) >= limit:
   619             if limit is not None and len(result) >= limit:
   597                 break
   620                 break
   598         return result
   621         return result
   599 
   622 
   600     def object_relation_vocabulary(self, rtype, limit=None):
   623     def object_relation_vocabulary(self, rtype, limit=None):
   610         result = []
   633         result = []
   611         rsetsize = None
   634         rsetsize = None
   612         for subjtype in rtype.subjects(entity.e_schema):
   635         for subjtype in rtype.subjects(entity.e_schema):
   613             if limit is not None:
   636             if limit is not None:
   614                 rsetsize = limit - len(result)
   637                 rsetsize = limit - len(result)
   615             result += self.relation_vocabulary(rtype, subjtype, 'object',
   638             result += self._relation_vocabulary(rtype, subjtype, 'object',
   616                                                rsetsize, done)
   639                                                 rsetsize, done)
   617             if limit is not None and len(result) >= limit:
   640             if limit is not None and len(result) >= limit:
   618                 break
   641                 break
   619         return result
   642         return result
   620 
   643 
   621     def relation_vocabulary(self, rtype, targettype, role,
       
   622                             limit=None, done=None):
       
   623         if done is None:
       
   624             done = set()
       
   625         rset = self.edited_entity.unrelated(rtype, targettype, role, limit)
       
   626         res = []
       
   627         for entity in rset.entities():
       
   628             if entity.eid in done:
       
   629                 continue
       
   630             done.add(entity.eid)
       
   631             res.append((entity.view('combobox'), entity.eid))
       
   632         return res
       
   633 
       
   634     def subject_in_state_vocabulary(self, rschema, limit=None):
   644     def subject_in_state_vocabulary(self, rschema, limit=None):
   635         """vocabulary method for the in_state relation, looking for
   645         """vocabulary method for the in_state relation, looking for relation's
   636         relation's object entities (i.e. self is the subject) according
   646         object entities (i.e. self is the subject) according to initial_state,
   637         to initial_state, state_of and next_state relation
   647         state_of and next_state relation
   638         """
   648         """
   639         entity = self.edited_entity
   649         entity = self.edited_entity
   640         if not entity.has_eid() or not entity.in_state:
   650         if not entity.has_eid() or not entity.in_state:
   641             # get the initial state
   651             # get the initial state
   642             rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
   652             rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
   657     def __init__(self, *args, **kwargs):
   667     def __init__(self, *args, **kwargs):
   658         super(CompositeForm, self).__init__(*args, **kwargs)
   668         super(CompositeForm, self).__init__(*args, **kwargs)
   659         self.forms = []
   669         self.forms = []
   660 
   670 
   661     def form_add_subform(self, subform):
   671     def form_add_subform(self, subform):
       
   672         """mark given form as a subform and append it"""
   662         subform.is_subform = True
   673         subform.is_subform = True
   663         self.forms.append(subform)
   674         self.forms.append(subform)