web/form.py
branchtls-sprint
changeset 1437 ea75dfe32317
parent 1406 133476216f4a
child 1451 982e8616d9a2
equal deleted inserted replaced
1436:97774eb6443b 1437:ea75dfe32317
    27     """abstract form view mix-in"""
    27     """abstract form view mix-in"""
    28     category = 'form'
    28     category = 'form'
    29     controller = 'edit'
    29     controller = 'edit'
    30     http_cache_manager = NoHTTPCacheManager
    30     http_cache_manager = NoHTTPCacheManager
    31     add_to_breadcrumbs = False
    31     add_to_breadcrumbs = False
    32     
    32 
    33     def __init__(self, req, rset, **kwargs):
    33     def __init__(self, req, rset, **kwargs):
    34         super(FormViewMixIn, self).__init__(req, rset, **kwargs)
    34         super(FormViewMixIn, self).__init__(req, rset, **kwargs)
    35         # get validation session data which may have been previously set.
    35         # get validation session data which may have been previously set.
    36         # deleting validation errors here breaks form reloading (errors are
    36         # deleting validation errors here breaks form reloading (errors are
    37         # no more available), they have to be deleted by application's publish
    37         # no more available), they have to be deleted by application's publish
    49                 if foreid == eid:
    49                 if foreid == eid:
    50                     errex.eid = var
    50                     errex.eid = var
    51                     break
    51                     break
    52             else:
    52             else:
    53                 errex.eid = foreid
    53                 errex.eid = foreid
    54         
    54 
    55     def html_headers(self):
    55     def html_headers(self):
    56         """return a list of html headers (eg something to be inserted between
    56         """return a list of html headers (eg something to be inserted between
    57         <head> and </head> of the returned page
    57         <head> and </head> of the returned page
    58 
    58 
    59         by default forms are neither indexed nor followed
    59         by default forms are neither indexed nor followed
    60         """
    60         """
    61         return [NOINDEX, NOFOLLOW]
    61         return [NOINDEX, NOFOLLOW]
    62         
    62 
    63     def linkable(self):
    63     def linkable(self):
    64         """override since forms are usually linked by an action,
    64         """override since forms are usually linked by an action,
    65         so we don't want them to be listed by appli.possible_views
    65         so we don't want them to be listed by appli.possible_views
    66         """
    66         """
    67         return False
    67         return False
    68 
    68 
    69 
    69 
    70 # XXX should disappear 
    70 # XXX should disappear
    71 class FormMixIn(object):
    71 class FormMixIn(object):
    72     """abstract form mix-in
    72     """abstract form mix-in
    73     XXX: you should inherit from this FIRST (obscure pb with super call)
    73     XXX: you should inherit from this FIRST (obscure pb with super call)
    74     """
    74     """
    75 
    75 
    79             varmaker = self.req.varmaker
    79             varmaker = self.req.varmaker
    80             self.req.set_page_data('rql_varmaker', varmaker)
    80             self.req.set_page_data('rql_varmaker', varmaker)
    81         self.varmaker = varmaker
    81         self.varmaker = varmaker
    82 
    82 
    83     # XXX deprecated with new form system. Should disappear
    83     # XXX deprecated with new form system. Should disappear
    84     
    84 
    85     domid = 'entityForm'
    85     domid = 'entityForm'
    86     category = 'form'
    86     category = 'form'
    87     controller = 'edit'
    87     controller = 'edit'
    88     http_cache_manager = NoHTTPCacheManager
    88     http_cache_manager = NoHTTPCacheManager
    89     add_to_breadcrumbs = False
    89     add_to_breadcrumbs = False
    90     
    90 
    91     def __init__(self, req, rset, **kwargs):
    91     def __init__(self, req, rset, **kwargs):
    92         super(FormMixIn, self).__init__(req, rset, **kwargs)
    92         super(FormMixIn, self).__init__(req, rset, **kwargs)
    93         # get validation session data which may have been previously set.
    93         # get validation session data which may have been previously set.
    94         # deleting validation errors here breaks form reloading (errors are
    94         # deleting validation errors here breaks form reloading (errors are
    95         # no more available), they have to be deleted by application's publish
    95         # no more available), they have to be deleted by application's publish
   106             for var, eid in forminfo['eidmap'].items():
   106             for var, eid in forminfo['eidmap'].items():
   107                 if foreid == eid:
   107                 if foreid == eid:
   108                     errex.eid = var
   108                     errex.eid = var
   109                     break
   109                     break
   110             else:
   110             else:
   111                 errex.eid = foreid    
   111                 errex.eid = foreid
   112         
   112 
   113     def html_headers(self):
   113     def html_headers(self):
   114         """return a list of html headers (eg something to be inserted between
   114         """return a list of html headers (eg something to be inserted between
   115         <head> and </head> of the returned page
   115         <head> and </head> of the returned page
   116 
   116 
   117         by default forms are neither indexed nor followed
   117         by default forms are neither indexed nor followed
   118         """
   118         """
   119         return [NOINDEX, NOFOLLOW]
   119         return [NOINDEX, NOFOLLOW]
   120         
   120 
   121     def linkable(self):
   121     def linkable(self):
   122         """override since forms are usually linked by an action,
   122         """override since forms are usually linked by an action,
   123         so we don't want them to be listed by appli.possible_views
   123         so we don't want them to be listed by appli.possible_views
   124         """
   124         """
   125         return False
   125         return False
   138 
   138 
   139     def button_ok(self, label=None, type='submit', name='defaultsubmit',
   139     def button_ok(self, label=None, type='submit', name='defaultsubmit',
   140                   **kwargs):
   140                   **kwargs):
   141         label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
   141         label = self.req._(label or stdmsgs.BUTTON_OK).capitalize()
   142         return self.button(label, name=name, type=type, **kwargs)
   142         return self.button(label, name=name, type=type, **kwargs)
   143     
   143 
   144     def button_apply(self, label=None, type='button', **kwargs):
   144     def button_apply(self, label=None, type='button', **kwargs):
   145         label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
   145         label = self.req._(label or stdmsgs.BUTTON_APPLY).capitalize()
   146         return self.action_button(label, __action='apply', type=type, **kwargs)
   146         return self.action_button(label, __action='apply', type=type, **kwargs)
   147 
   147 
   148     def button_delete(self, label=None, type='button', **kwargs):
   148     def button_delete(self, label=None, type='button', **kwargs):
   149         label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
   149         label = self.req._(label or stdmsgs.BUTTON_DELETE).capitalize()
   150         return self.action_button(label, __action='delete', type=type, **kwargs)
   150         return self.action_button(label, __action='delete', type=type, **kwargs)
   151     
   151 
   152     def button_cancel(self, label=None, type='button', **kwargs):
   152     def button_cancel(self, label=None, type='button', **kwargs):
   153         label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
   153         label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
   154         return self.action_button(label, __action='cancel', type=type, **kwargs)
   154         return self.action_button(label, __action='cancel', type=type, **kwargs)
   155     
   155 
   156     def button_reset(self, label=None, type='reset', name='__action_cancel',
   156     def button_reset(self, label=None, type='reset', name='__action_cancel',
   157                      **kwargs):
   157                      **kwargs):
   158         label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
   158         label = self.req._(label or stdmsgs.BUTTON_CANCEL).capitalize()
   159         return self.button(label, type=type, **kwargs)
   159         return self.button(label, type=type, **kwargs)
   160 
   160 
   172             inlined_entity = self.vreg.etype_class(ttype)(self.req, None, None)
   172             inlined_entity = self.vreg.etype_class(ttype)(self.req, None, None)
   173             for irschema, _, x in inlined_entity.relations_by_category(categories):
   173             for irschema, _, x in inlined_entity.relations_by_category(categories):
   174                 if inlined_entity.get_widget(irschema, x).need_multipart:
   174                 if inlined_entity.get_widget(irschema, x).need_multipart:
   175                     return True
   175                     return True
   176         return False
   176         return False
   177     
   177 
   178     def error_message(self):
   178     def error_message(self):
   179         """return formatted error message
   179         """return formatted error message
   180 
   180 
   181         This method should be called once inlined field errors has been consumed
   181         This method should be called once inlined field errors has been consumed
   182         """
   182         """
   187             displayed = self.req.data['displayederrors']
   187             displayed = self.req.data['displayederrors']
   188             errors = sorted((field, err) for field, err in errex.errors.items()
   188             errors = sorted((field, err) for field, err in errex.errors.items()
   189                             if not field in displayed)
   189                             if not field in displayed)
   190             if errors:
   190             if errors:
   191                 if len(errors) > 1:
   191                 if len(errors) > 1:
   192                     templstr = '<li>%s</li>\n' 
   192                     templstr = '<li>%s</li>\n'
   193                 else:
   193                 else:
   194                     templstr = '&nbsp;%s\n'
   194                     templstr = '&nbsp;%s\n'
   195                 for field, err in errors:
   195                 for field, err in errors:
   196                     if field is None:
   196                     if field is None:
   197                         errormsg += templstr % err
   197                         errormsg += templstr % err
   221                 field.set_name(fieldname)
   221                 field.set_name(fieldname)
   222             allfields.append(field)
   222             allfields.append(field)
   223         classdict['_fields_'] = allfields
   223         classdict['_fields_'] = allfields
   224         return super(metafieldsform, mcs).__new__(mcs, name, bases, classdict)
   224         return super(metafieldsform, mcs).__new__(mcs, name, bases, classdict)
   225 
   225 
   226     
   226 
   227 class FieldNotFound(Exception):
   227 class FieldNotFound(Exception):
   228     """raised by field_by_name when a field with the given name has not been
   228     """raised by field_by_name when a field with the given name has not been
   229     found
   229     found
   230     """
   230     """
   231     
   231 
   232 class FieldsForm(FormMixIn, AppRsetObject):
   232 class FieldsForm(FormMixIn, AppRsetObject):
   233     __metaclass__ = metafieldsform
   233     __metaclass__ = metafieldsform
   234     __registry__ = 'forms'
   234     __registry__ = 'forms'
   235     __select__ = yes()
   235     __select__ = yes()
   236     
   236 
   237     is_subform = False
   237     is_subform = False
   238     
   238 
   239     # attributes overrideable through __init__
   239     # attributes overrideable through __init__
   240     internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
   240     internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
   241     needs_js = ('cubicweb.ajax.js', 'cubicweb.edition.js',)
   241     needs_js = ('cubicweb.ajax.js', 'cubicweb.edition.js',)
   242     needs_css = ('cubicweb.form.css',)
   242     needs_css = ('cubicweb.form.css',)
   243     domid = 'form'
   243     domid = 'form'
   249     cwtarget = None
   249     cwtarget = None
   250     redirect_path = None
   250     redirect_path = None
   251     set_error_url = True
   251     set_error_url = True
   252     copy_nav_params = False
   252     copy_nav_params = False
   253     form_buttons = None # form buttons (button widgets instances)
   253     form_buttons = None # form buttons (button widgets instances)
   254                  
   254 
   255     def __init__(self, req, rset=None, row=None, col=None, submitmsg=None,
   255     def __init__(self, req, rset=None, row=None, col=None, submitmsg=None,
   256                  **kwargs):
   256                  **kwargs):
   257         super(FieldsForm, self).__init__(req, rset, row=row, col=col)
   257         super(FieldsForm, self).__init__(req, rset, row=row, col=col)
   258         self.fields = list(self.__class__._fields_)
   258         self.fields = list(self.__class__._fields_)
   259         for key, val in kwargs.items():
   259         for key, val in kwargs.items():
   283             fields = cls_or_self.fields
   283             fields = cls_or_self.fields
   284         for field in fields:
   284         for field in fields:
   285             if field.name == name and field.role == role:
   285             if field.name == name and field.role == role:
   286                 return field
   286                 return field
   287         raise FieldNotFound(name)
   287         raise FieldNotFound(name)
   288     
   288 
   289     @iclassmethod
   289     @iclassmethod
   290     def remove_field(cls_or_self, field):
   290     def remove_field(cls_or_self, field):
   291         """remove a field from form class or instance"""
   291         """remove a field from form class or instance"""
   292         if isinstance(cls_or_self, type):
   292         if isinstance(cls_or_self, type):
   293             fields = cls_or_self._fields_
   293             fields = cls_or_self._fields_
   294         else:
   294         else:
   295             fields = cls_or_self.fields
   295             fields = cls_or_self.fields
   296         fields.remove(field)
   296         fields.remove(field)
   297     
   297 
   298     @iclassmethod
   298     @iclassmethod
   299     def append_field(cls_or_self, field):
   299     def append_field(cls_or_self, field):
   300         """append a field to form class or instance"""
   300         """append a field to form class or instance"""
   301         if isinstance(cls_or_self, type):
   301         if isinstance(cls_or_self, type):
   302             fields = cls_or_self._fields_
   302             fields = cls_or_self._fields_
   303         else:
   303         else:
   304             fields = cls_or_self.fields
   304             fields = cls_or_self.fields
   305         fields.append(field)
   305         fields.append(field)
   306     
   306 
   307     @property
   307     @property
   308     def form_needs_multipart(self):
   308     def form_needs_multipart(self):
   309         """true if the form needs enctype=multipart/form-data"""
   309         """true if the form needs enctype=multipart/form-data"""
   310         return any(field.needs_multipart for field in self.fields) 
   310         return any(field.needs_multipart for field in self.fields)
   311 
   311 
   312     def form_add_hidden(self, name, value=None, **kwargs):
   312     def form_add_hidden(self, name, value=None, **kwargs):
   313         """add an hidden field to the form"""
   313         """add an hidden field to the form"""
   314         field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
   314         field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
   315                             **kwargs)
   315                             **kwargs)
   317             # by default, hidden input don't set id attribute. If one is
   317             # by default, hidden input don't set id attribute. If one is
   318             # explicitly specified, ensure it will be set
   318             # explicitly specified, ensure it will be set
   319             field.widget.setdomid = True
   319             field.widget.setdomid = True
   320         self.append_field(field)
   320         self.append_field(field)
   321         return field
   321         return field
   322     
   322 
   323     def add_media(self):
   323     def add_media(self):
   324         """adds media (CSS & JS) required by this widget"""
   324         """adds media (CSS & JS) required by this widget"""
   325         if self.needs_js:
   325         if self.needs_js:
   326             self.req.add_js(self.needs_js)
   326             self.req.add_js(self.needs_js)
   327         if self.needs_css:
   327         if self.needs_css:
   359                                   'id': self.form_field_id(field),
   359                                   'id': self.form_field_id(field),
   360                                   }
   360                                   }
   361 
   361 
   362     def form_field_display_value(self, field, rendervalues, load_bytes=False):
   362     def form_field_display_value(self, field, rendervalues, load_bytes=False):
   363         """return field's *string* value to use for display
   363         """return field's *string* value to use for display
   364         
   364 
   365         looks in
   365         looks in
   366         1. previously submitted form values if any (eg on validation error)
   366         1. previously submitted form values if any (eg on validation error)
   367         2. req.form
   367         2. req.form
   368         3. extra kw args given to render_form
   368         3. extra kw args given to render_form
   369         4. field's typed value
   369         4. field's typed value
   380                 value = rendervalues[field.name]
   380                 value = rendervalues[field.name]
   381             else:
   381             else:
   382                 value = self.form_field_value(field, load_bytes)
   382                 value = self.form_field_value(field, load_bytes)
   383                 if callable(value):
   383                 if callable(value):
   384                     value = value(self)
   384                     value = value(self)
   385             if value != INTERNAL_FIELD_VALUE: 
   385             if value != INTERNAL_FIELD_VALUE:
   386                 value = field.format_value(self.req, value)
   386                 value = field.format_value(self.req, value)
   387         return value
   387         return value
   388 
   388 
   389     def form_field_value(self, field, load_bytes=False):
   389     def form_field_value(self, field, load_bytes=False):
   390         """return field's *typed* value"""
   390         """return field's *typed* value"""
   391         value = field.initial
   391         value = field.initial
   392         if callable(value):
   392         if callable(value):
   393             value = value(self)
   393             value = value(self)
   394         return value
   394         return value
   395     
   395 
   396     def form_field_error(self, field):
   396     def form_field_error(self, field):
   397         """return validation error for widget's field, if any"""
   397         """return validation error for widget's field, if any"""
   398         errex = self.req.data.get('formerrors')
   398         errex = self.req.data.get('formerrors')
   399         if errex and self._errex_match_field(errex, field):
   399         if errex and self._errex_match_field(errex, field):
   400             self.req.data['displayederrors'].add(field.name)
   400             self.req.data['displayederrors'].add(field.name)
   402         return u''
   402         return u''
   403 
   403 
   404     def form_field_format(self, field):
   404     def form_field_format(self, field):
   405         """return MIME type used for the given (text or bytes) field"""
   405         """return MIME type used for the given (text or bytes) field"""
   406         return self.req.property_value('ui.default-text-format')
   406         return self.req.property_value('ui.default-text-format')
   407     
   407 
   408     def form_field_encoding(self, field):
   408     def form_field_encoding(self, field):
   409         """return encoding used for the given (text) field"""
   409         """return encoding used for the given (text) field"""
   410         return self.req.encoding
   410         return self.req.encoding
   411     
   411 
   412     def form_field_name(self, field):
   412     def form_field_name(self, field):
   413         """return qualified name for the given field"""
   413         """return qualified name for the given field"""
   414         return field.name
   414         return field.name
   415 
   415 
   416     def form_field_id(self, field):
   416     def form_field_id(self, field):
   417         """return dom id for the given field"""
   417         """return dom id for the given field"""
   418         return field.id
   418         return field.id
   419    
   419 
   420     def form_field_vocabulary(self, field, limit=None):
   420     def form_field_vocabulary(self, field, limit=None):
   421         """return vocabulary for the given field. Should be overriden in
   421         """return vocabulary for the given field. Should be overriden in
   422         specific forms using fields which requires some vocabulary
   422         specific forms using fields which requires some vocabulary
   423         """
   423         """
   424         raise NotImplementedError
   424         raise NotImplementedError
   426     def _errex_match_field(self, errex, field):
   426     def _errex_match_field(self, errex, field):
   427         """return true if the field has some error in given validation exception
   427         """return true if the field has some error in given validation exception
   428         """
   428         """
   429         return field.name in errex.errors
   429         return field.name in errex.errors
   430 
   430 
   431    
   431 
   432 class EntityFieldsForm(FieldsForm):
   432 class EntityFieldsForm(FieldsForm):
   433     __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
   433     __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
   434     
   434 
   435     internal_fields = FieldsForm.internal_fields + ('__type', 'eid')
   435     internal_fields = FieldsForm.internal_fields + ('__type', 'eid')
   436     domid = 'entityForm'
   436     domid = 'entityForm'
   437     
   437 
   438     def __init__(self, *args, **kwargs):
   438     def __init__(self, *args, **kwargs):
   439         self.edited_entity = kwargs.pop('entity', None)
   439         self.edited_entity = kwargs.pop('entity', None)
   440         msg = kwargs.pop('submitmsg', None)
   440         msg = kwargs.pop('submitmsg', None)
   441         super(EntityFieldsForm, self).__init__(*args, **kwargs)
   441         super(EntityFieldsForm, self).__init__(*args, **kwargs)
   442         if self.edited_entity is None:
   442         if self.edited_entity is None:
   447             # If we need to directly attach the new object to another one
   447             # If we need to directly attach the new object to another one
   448             for linkto in self.req.list_form_param('__linkto'):
   448             for linkto in self.req.list_form_param('__linkto'):
   449                 self.form_add_hidden('__linkto', linkto)
   449                 self.form_add_hidden('__linkto', linkto)
   450                 msg = '%s %s' % (msg, self.req._('and linked'))
   450                 msg = '%s %s' % (msg, self.req._('and linked'))
   451             self.form_add_hidden('__message', msg)
   451             self.form_add_hidden('__message', msg)
   452     
   452 
   453     def _errex_match_field(self, errex, field):
   453     def _errex_match_field(self, errex, field):
   454         """return true if the field has some error in given validation exception
   454         """return true if the field has some error in given validation exception
   455         """
   455         """
   456         return errex.eid == self.edited_entity.eid and field.name in errex.errors
   456         return errex.eid == self.edited_entity.eid and field.name in errex.errors
   457 
   457 
   482                 value = value()
   482                 value = value()
   483         else:
   483         else:
   484             value = super(EntityFieldsForm, self).form_field_value(field,
   484             value = super(EntityFieldsForm, self).form_field_value(field,
   485                                                                    load_bytes)
   485                                                                    load_bytes)
   486         return value
   486         return value
   487         
   487 
   488     def form_build_context(self, values=None):
   488     def form_build_context(self, values=None):
   489         """overriden to add edit[s|o] hidden fields and to ensure schema fields
   489         """overriden to add edit[s|o] hidden fields and to ensure schema fields
   490         have eidparam set to True
   490         have eidparam set to True
   491 
   491 
   492         edit[s|o] hidden fields are used t o indicate the value for the
   492         edit[s|o] hidden fields are used t o indicate the value for the
   501                     (eschema.has_subject_relation(fieldname) or
   501                     (eschema.has_subject_relation(fieldname) or
   502                      eschema.has_object_relation(fieldname))):
   502                      eschema.has_object_relation(fieldname))):
   503                     field.eidparam = True
   503                     field.eidparam = True
   504                     self.fields.append(HiddenInitialValueField(field))
   504                     self.fields.append(HiddenInitialValueField(field))
   505         return super(EntityFieldsForm, self).form_build_context(values)
   505         return super(EntityFieldsForm, self).form_build_context(values)
   506         
   506 
   507     def form_field_value(self, field, load_bytes=False):
   507     def form_field_value(self, field, load_bytes=False):
   508         """return field's *typed* value
   508         """return field's *typed* value
   509 
   509 
   510         overriden to deal with
   510         overriden to deal with
   511         * special eid / __type / edits- / edito- fields
   511         * special eid / __type / edits- / edito- fields
   549         if entity.has_eid():
   549         if entity.has_eid():
   550             value = [ent.eid for ent in getattr(entity, attr)]
   550             value = [ent.eid for ent in getattr(entity, attr)]
   551         else:
   551         else:
   552             value = self._form_field_default_value(field, load_bytes)
   552             value = self._form_field_default_value(field, load_bytes)
   553         return value
   553         return value
   554         
   554 
   555     def form_field_format(self, field):
   555     def form_field_format(self, field):
   556         """return MIME type used for the given (text or bytes) field"""
   556         """return MIME type used for the given (text or bytes) field"""
   557         entity = self.edited_entity
   557         entity = self.edited_entity
   558         if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
   558         if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
   559             entity.has_eid() or '%s_format' % field.name in entity):
   559             entity.has_eid() or '%s_format' % field.name in entity):
   564         """return encoding used for the given (text) field"""
   564         """return encoding used for the given (text) field"""
   565         entity = self.edited_entity
   565         entity = self.edited_entity
   566         if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
   566         if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
   567             entity.has_eid() or '%s_encoding' % field.name in entity):
   567             entity.has_eid() or '%s_encoding' % field.name in entity):
   568             return self.edited_entity.attr_metadata(field.name, 'encoding')
   568             return self.edited_entity.attr_metadata(field.name, 'encoding')
   569         return super(EntityFieldsForm, self).form_field_encoding(field)    
   569         return super(EntityFieldsForm, self).form_field_encoding(field)
   570     
   570 
   571     def form_field_name(self, field):
   571     def form_field_name(self, field):
   572         """return qualified name for the given field"""
   572         """return qualified name for the given field"""
   573         if field.eidparam:
   573         if field.eidparam:
   574             return eid_param(field.name, self.edited_entity.eid)
   574             return eid_param(field.name, self.edited_entity.eid)
   575         return field.name
   575         return field.name
   577     def form_field_id(self, field):
   577     def form_field_id(self, field):
   578         """return dom id for the given field"""
   578         """return dom id for the given field"""
   579         if field.eidparam:
   579         if field.eidparam:
   580             return eid_param(field.id, self.edited_entity.eid)
   580             return eid_param(field.id, self.edited_entity.eid)
   581         return field.id
   581         return field.id
   582         
   582 
   583     def form_field_vocabulary(self, field, limit=None):
   583     def form_field_vocabulary(self, field, limit=None):
   584         """return vocabulary for the given field"""
   584         """return vocabulary for the given field"""
   585         role, rtype = field.role, field.name
   585         role, rtype = field.role, field.name
   586         method = '%s_%s_vocabulary' % (role, rtype)
   586         method = '%s_%s_vocabulary' % (role, rtype)
   587         try:
   587         try:
   665         return sorted(results)
   665         return sorted(results)
   666 
   666 
   667 
   667 
   668 class CompositeForm(FieldsForm):
   668 class CompositeForm(FieldsForm):
   669     """form composed for sub-forms"""
   669     """form composed for sub-forms"""
   670     
   670 
   671     def __init__(self, *args, **kwargs):
   671     def __init__(self, *args, **kwargs):
   672         super(CompositeForm, self).__init__(*args, **kwargs)
   672         super(CompositeForm, self).__init__(*args, **kwargs)
   673         self.forms = []
   673         self.forms = []
   674 
   674 
   675     def form_add_subform(self, subform):
   675     def form_add_subform(self, subform):