--- a/web/form.py Fri May 29 14:07:42 2009 +0200
+++ b/web/form.py Fri May 29 14:19:30 2009 +0200
@@ -7,20 +7,10 @@
"""
__docformat__ = "restructuredtext en"
-from warnings import warn
-
-from logilab.common.compat import any
-from logilab.common.decorators import iclassmethod
-
from cubicweb.appobject import AppRsetObject
-from cubicweb.selectors import yes, non_final_entity, match_kwargs, one_line_rset
from cubicweb.view import NOINDEX, NOFOLLOW
from cubicweb.common import tags
-from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param, stdmsgs
-from cubicweb.web import formwidgets as fwdgs, httpcache
-from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.formfields import (Field, StringField, RelationField,
- HiddenInitialValueField)
+from cubicweb.web import stdmsgs, httpcache, formfields
class FormViewMixIn(object):
@@ -198,7 +188,7 @@
if hasattr(base, '_fields_'):
allfields += base._fields_
clsfields = (item for item in classdict.items()
- if isinstance(item[1], Field))
+ if isinstance(item[1], formfields.Field))
for fieldname, field in sorted(clsfields, key=lambda x: x[1].creation_rank):
if not field.name:
field.set_name(fieldname)
@@ -212,509 +202,6 @@
found
"""
-class FieldsForm(FormMixIn, AppRsetObject):
+class Form(FormMixIn, AppRsetObject):
__metaclass__ = metafieldsform
__registry__ = 'forms'
- __select__ = yes()
-
- is_subform = False
-
- # attributes overrideable through __init__
- internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS
- needs_js = ('cubicweb.ajax.js', 'cubicweb.edition.js',)
- needs_css = ('cubicweb.form.css',)
- domid = 'form'
- title = None
- action = None
- onsubmit = "return freezeFormButtons('%(domid)s');"
- cssclass = None
- cssstyle = None
- cwtarget = None
- redirect_path = None
- set_error_url = True
- copy_nav_params = False
- form_buttons = None # form buttons (button widgets instances)
- form_renderer_id = 'default'
-
- def __init__(self, req, rset=None, row=None, col=None, submitmsg=None,
- **kwargs):
- super(FieldsForm, self).__init__(req, rset, row=row, col=col)
- self.fields = list(self.__class__._fields_)
- for key, val in kwargs.items():
- if key in NAV_FORM_PARAMETERS:
- self.form_add_hidden(key, val)
- else:
- assert hasattr(self.__class__, key) and not key[0] == '_', key
- setattr(self, key, val)
- if self.set_error_url:
- self.form_add_hidden('__errorurl', self.session_key())
- if self.copy_nav_params:
- for param in NAV_FORM_PARAMETERS:
- if not param in kwargs:
- value = req.form.get(param)
- if value:
- self.form_add_hidden(param, value)
- if submitmsg is not None:
- self.form_add_hidden('__message', submitmsg)
- self.context = None
- if 'domid' in kwargs:# session key changed
- self.restore_previous_post(self.session_key())
-
- @iclassmethod
- def _fieldsattr(cls_or_self):
- if isinstance(cls_or_self, type):
- fields = cls_or_self._fields_
- else:
- fields = cls_or_self.fields
- return fields
-
- @iclassmethod
- def field_by_name(cls_or_self, name, role='subject'):
- """return field with the given name and role.
- Raise FieldNotFound if the field can't be found.
- """
- for field in cls_or_self._fieldsattr():
- if field.name == name and field.role == role:
- return field
- raise FieldNotFound(name)
-
- @iclassmethod
- def fields_by_name(cls_or_self, name, role='subject'):
- """return a list of fields with the given name and role"""
- return [field for field in cls_or_self._fieldsattr()
- if field.name == name and field.role == role]
-
- @iclassmethod
- def remove_field(cls_or_self, field):
- """remove a field from form class or instance"""
- cls_or_self._fieldsattr().remove(field)
-
- @iclassmethod
- def append_field(cls_or_self, field):
- """append a field to form class or instance"""
- cls_or_self._fieldsattr().append(field)
-
- @iclassmethod
- def insert_field_before(cls_or_self, new_field, name, role='subject'):
- field = cls_or_self.field_by_name(name, role)
- fields = cls_or_self._fieldsattr()
- fields.insert(fields.index(field), new_field)
-
- @iclassmethod
- def insert_field_after(cls_or_self, new_field, name, role='subject'):
- field = cls_or_self.field_by_name(name, role)
- fields = cls_or_self._fieldsattr()
- fields.insert(fields.index(field)+1, new_field)
-
- @property
- def form_needs_multipart(self):
- """true if the form needs enctype=multipart/form-data"""
- return any(field.needs_multipart for field in self.fields)
-
- def form_add_hidden(self, name, value=None, **kwargs):
- """add an hidden field to the form"""
- field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value,
- **kwargs)
- if 'id' in kwargs:
- # by default, hidden input don't set id attribute. If one is
- # explicitly specified, ensure it will be set
- field.widget.setdomid = True
- self.append_field(field)
- return field
-
- def add_media(self):
- """adds media (CSS & JS) required by this widget"""
- if self.needs_js:
- self.req.add_js(self.needs_js)
- if self.needs_css:
- self.req.add_css(self.needs_css)
-
- def form_render(self, **values):
- """render this form, using the renderer given in args or the default
- FormRenderer()
- """
- renderer = values.pop('renderer', None)
- if renderer is None:
- renderer = self.form_default_renderer()
- return renderer.render(self, values)
-
- def form_default_renderer(self):
- return self.vreg.select_object('formrenderers', self.form_renderer_id,
- self.req, self.rset,
- row=self.row, col=self.col)
-
- def form_build_context(self, rendervalues=None):
- """build form context values (the .context attribute which is a
- dictionary with field instance as key associated to a dictionary
- containing field 'name' (qualified), 'id', 'value' (for display, always
- a string).
-
- rendervalues is an optional dictionary containing extra kwargs given to
- form_render()
- """
- self.context = context = {}
- # ensure rendervalues is a dict
- if rendervalues is None:
- rendervalues = {}
- # use a copy in case fields are modified while context is build (eg
- # __linkto handling for instance)
- for field in self.fields[:]:
- for field in field.actual_fields(self):
- field.form_init(self)
- value = self.form_field_display_value(field, rendervalues)
- context[field] = {'value': value,
- 'name': self.form_field_name(field),
- 'id': self.form_field_id(field),
- }
-
- def form_field_display_value(self, field, rendervalues, load_bytes=False):
- """return field's *string* value to use for display
-
- looks in
- 1. previously submitted form values if any (eg on validation error)
- 2. req.form
- 3. extra kw args given to render_form
- 4. field's typed value
-
- values found in 1. and 2. are expected te be already some 'display'
- value while those found in 3. and 4. are expected to be correctly typed.
- """
- value = self._req_display_value(field)
- if value is None:
- if field.name in rendervalues:
- value = rendervalues[field.name]
- else:
- value = self.form_field_value(field, load_bytes)
- if callable(value):
- value = value(self)
- if value != INTERNAL_FIELD_VALUE:
- value = field.format_value(self.req, value)
- return value
-
- def _req_display_value(self, field):
- qname = self.form_field_name(field)
- if qname in self.form_previous_values:
- return self.form_previous_values[qname]
- if qname in self.req.form:
- return self.req.form[qname]
- if field.name in self.req.form:
- return self.req.form[field.name]
- return None
-
- def form_field_value(self, field, load_bytes=False):
- """return field's *typed* value"""
- myattr = '%s_%s_default' % (field.role, field.name)
- if hasattr(self, myattr):
- return getattr(self, myattr)()
- value = field.initial
- if callable(value):
- value = value(self)
- return value
-
- def form_field_error(self, field):
- """return validation error for widget's field, if any"""
- if self._field_has_error(field):
- self.form_displayed_errors.add(field.name)
- return u'<span class="error">%s</span>' % self.form_valerror.errors[field.name]
- return u''
-
- def form_field_format(self, field):
- """return MIME type used for the given (text or bytes) field"""
- return self.req.property_value('ui.default-text-format')
-
- def form_field_encoding(self, field):
- """return encoding used for the given (text) field"""
- return self.req.encoding
-
- def form_field_name(self, field):
- """return qualified name for the given field"""
- return field.name
-
- def form_field_id(self, field):
- """return dom id for the given field"""
- return field.id
-
- def form_field_vocabulary(self, field, limit=None):
- """return vocabulary for the given field. Should be overriden in
- specific forms using fields which requires some vocabulary
- """
- raise NotImplementedError
-
- def _field_has_error(self, field):
- """return true if the field has some error in given validation exception
- """
- return self.form_valerror and field.name in self.form_valerror.errors
-
-
-class EntityFieldsForm(FieldsForm):
- __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity()))
-
- internal_fields = FieldsForm.internal_fields + ('__type', 'eid', '__maineid')
- domid = 'entityForm'
-
- def __init__(self, *args, **kwargs):
- self.edited_entity = kwargs.pop('entity', None)
- msg = kwargs.pop('submitmsg', None)
- super(EntityFieldsForm, self).__init__(*args, **kwargs)
- if self.edited_entity is None:
- self.edited_entity = self.complete_entity(self.row or 0, self.col or 0)
- self.form_add_hidden('__type', eidparam=True)
- self.form_add_hidden('eid')
- if msg:
- # If we need to directly attach the new object to another one
- self.form_add_hidden('__message', msg)
- if not self.is_subform:
- for linkto in self.req.list_form_param('__linkto'):
- self.form_add_hidden('__linkto', linkto)
- msg = '%s %s' % (msg, self.req._('and linked'))
- # in case of direct instanciation
- self.schema = self.edited_entity.schema
- self.vreg = self.edited_entity.vreg
-
- def _field_has_error(self, field):
- """return true if the field has some error in given validation exception
- """
- return super(EntityFieldsForm, self)._field_has_error(field) \
- and self.form_valerror.eid == self.edited_entity.eid
-
- def _relation_vocabulary(self, rtype, targettype, role,
- limit=None, done=None):
- """return unrelated entities for a given relation and target entity type
- for use in vocabulary
- """
- if done is None:
- done = set()
- rset = self.edited_entity.unrelated(rtype, targettype, role, limit)
- res = []
- for entity in rset.entities():
- if entity.eid in done:
- continue
- done.add(entity.eid)
- res.append((entity.view('combobox'), entity.eid))
- return res
-
- def _req_display_value(self, field):
- value = super(EntityFieldsForm, self)._req_display_value(field)
- if value is None:
- value = self.edited_entity.linked_to(field.name, field.role)
- if value:
- searchedvalues = ['%s:%s:%s' % (field.name, eid, field.role)
- for eid in value]
- # remove associated __linkto hidden fields
- for field in self.fields_by_name('__linkto'):
- if field.initial in searchedvalues:
- self.remove_field(field)
- else:
- value = None
- return value
-
- def _form_field_default_value(self, field, load_bytes):
- defaultattr = 'default_%s' % field.name
- if hasattr(self.edited_entity, defaultattr):
- # XXX bw compat, default_<field name> on the entity
- warn('found %s on %s, should be set on a specific form'
- % (defaultattr, self.edited_entity.id), DeprecationWarning)
- value = getattr(self.edited_entity, defaultattr)
- if callable(value):
- value = value()
- else:
- value = super(EntityFieldsForm, self).form_field_value(field,
- load_bytes)
- return value
-
- def form_default_renderer(self):
- return self.vreg.select_object('formrenderers', self.form_renderer_id,
- self.req, self.rset,
- row=self.row, col=self.col,
- entity=self.edited_entity)
-
- def form_build_context(self, values=None):
- """overriden to add edit[s|o] hidden fields and to ensure schema fields
- have eidparam set to True
-
- edit[s|o] hidden fields are used to indicate the value for the
- associated field before the (potential) modification made when
- submitting the form.
- """
- eschema = self.edited_entity.e_schema
- for field in self.fields[:]:
- for field in field.actual_fields(self):
- fieldname = field.name
- if fieldname != 'eid' and (
- (eschema.has_subject_relation(fieldname) or
- eschema.has_object_relation(fieldname))):
- field.eidparam = True
- self.fields.append(HiddenInitialValueField(field))
- return super(EntityFieldsForm, self).form_build_context(values)
-
- def form_field_value(self, field, load_bytes=False):
- """return field's *typed* value
-
- overriden to deal with
- * special eid / __type / edits- / edito- fields
- * lookup for values on edited entities
- """
- attr = field.name
- entity = self.edited_entity
- if attr == 'eid':
- return entity.eid
- if not field.eidparam:
- return super(EntityFieldsForm, self).form_field_value(field, load_bytes)
- if attr.startswith('edits-') or attr.startswith('edito-'):
- # edit[s|o]- fieds must have the actual value stored on the entity
- assert hasattr(field, 'visible_field')
- vfield = field.visible_field
- assert vfield.eidparam
- if entity.has_eid():
- return self.form_field_value(vfield)
- return INTERNAL_FIELD_VALUE
- if attr == '__type':
- return entity.id
- if self.schema.rschema(attr).is_final():
- attrtype = entity.e_schema.destination(attr)
- if attrtype == 'Password':
- return entity.has_eid() and INTERNAL_FIELD_VALUE or ''
- if attrtype == 'Bytes':
- if entity.has_eid():
- if load_bytes:
- return getattr(entity, attr)
- # XXX value should reflect if some file is already attached
- return True
- return False
- if entity.has_eid() or attr in entity:
- value = getattr(entity, attr)
- else:
- value = self._form_field_default_value(field, load_bytes)
- return value
- # non final relation field
- if entity.has_eid() or entity.relation_cached(attr, field.role):
- value = [r[0] for r in entity.related(attr, field.role)]
- else:
- value = self._form_field_default_value(field, load_bytes)
- return value
-
- def form_field_format(self, field):
- """return MIME type used for the given (text or bytes) field"""
- entity = self.edited_entity
- if field.eidparam and entity.e_schema.has_metadata(field.name, 'format') and (
- entity.has_eid() or '%s_format' % field.name in entity):
- return self.edited_entity.attr_metadata(field.name, 'format')
- return self.req.property_value('ui.default-text-format')
-
- def form_field_encoding(self, field):
- """return encoding used for the given (text) field"""
- entity = self.edited_entity
- if field.eidparam and entity.e_schema.has_metadata(field.name, 'encoding') and (
- entity.has_eid() or '%s_encoding' % field.name in entity):
- return self.edited_entity.attr_metadata(field.name, 'encoding')
- return super(EntityFieldsForm, self).form_field_encoding(field)
-
- def form_field_name(self, field):
- """return qualified name for the given field"""
- if field.eidparam:
- return eid_param(field.name, self.edited_entity.eid)
- return field.name
-
- def form_field_id(self, field):
- """return dom id for the given field"""
- if field.eidparam:
- return eid_param(field.id, self.edited_entity.eid)
- return field.id
-
- def form_field_vocabulary(self, field, limit=None):
- """return vocabulary for the given field"""
- role, rtype = field.role, field.name
- method = '%s_%s_vocabulary' % (role, rtype)
- try:
- vocabfunc = getattr(self, method)
- except AttributeError:
- try:
- # XXX bw compat, <role>_<rtype>_vocabulary on the entity
- vocabfunc = getattr(self.edited_entity, method)
- except AttributeError:
- vocabfunc = getattr(self, '%s_relation_vocabulary' % role)
- else:
- warn('found %s on %s, should be set on a specific form'
- % (method, self.edited_entity.id), DeprecationWarning)
- # NOTE: it is the responsibility of `vocabfunc` to sort the result
- # (direclty through RQL or via a python sort). This is also
- # important because `vocabfunc` might return a list with
- # couples (label, None) which act as separators. In these
- # cases, it doesn't make sense to sort results afterwards.
- return vocabfunc(rtype, limit)
-
- def subject_relation_vocabulary(self, rtype, limit=None):
- """defaut vocabulary method for the given relation, looking for
- relation's object entities (i.e. self is the subject)
- """
- entity = self.edited_entity
- if isinstance(rtype, basestring):
- rtype = entity.schema.rschema(rtype)
- done = None
- assert not rtype.is_final(), rtype
- if entity.has_eid():
- done = set(e.eid for e in getattr(entity, str(rtype)))
- result = []
- rsetsize = None
- for objtype in rtype.objects(entity.e_schema):
- if limit is not None:
- rsetsize = limit - len(result)
- result += self._relation_vocabulary(rtype, objtype, 'subject',
- rsetsize, done)
- if limit is not None and len(result) >= limit:
- break
- return result
-
- def object_relation_vocabulary(self, rtype, limit=None):
- """defaut vocabulary method for the given relation, looking for
- relation's subject entities (i.e. self is the object)
- """
- entity = self.edited_entity
- if isinstance(rtype, basestring):
- rtype = entity.schema.rschema(rtype)
- done = None
- if entity.has_eid():
- done = set(e.eid for e in getattr(entity, 'reverse_%s' % rtype))
- result = []
- rsetsize = None
- for subjtype in rtype.subjects(entity.e_schema):
- if limit is not None:
- rsetsize = limit - len(result)
- result += self._relation_vocabulary(rtype, subjtype, 'object',
- rsetsize, done)
- if limit is not None and len(result) >= limit:
- break
- return result
-
- def subject_in_state_vocabulary(self, rtype, limit=None):
- """vocabulary method for the in_state relation, looking for relation's
- object entities (i.e. self is the subject) according to initial_state,
- state_of and next_state relation
- """
- entity = self.edited_entity
- if not entity.has_eid() or not entity.in_state:
- # get the initial state
- rql = 'Any S where S state_of ET, ET name %(etype)s, ET initial_state S'
- rset = self.req.execute(rql, {'etype': str(entity.e_schema)})
- if rset:
- return [(rset.get_entity(0, 0).view('combobox'), rset[0][0])]
- return []
- results = []
- for tr in entity.in_state[0].transitions(entity):
- state = tr.destination_state[0]
- results.append((state.view('combobox'), state.eid))
- return sorted(results)
-
-
-class CompositeForm(FieldsForm):
- """form composed for sub-forms"""
- form_renderer_id = 'composite'
-
- def __init__(self, *args, **kwargs):
- super(CompositeForm, self).__init__(*args, **kwargs)
- self.forms = []
-
- def form_add_subform(self, subform):
- """mark given form as a subform and append it"""
- subform.is_subform = True
- self.forms.append(subform)