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 = ' %s\n' |
194 templstr = ' %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): |