refactor vocabulary handling to avoid having to define methods
on form objects, use .choices instead even for non final relations.
Details:
* deprecated support of <role>_<relation>_vocabulary methods on forms
* new utility functions for relations vocabulary
* most logic now on the RelationField class
--- a/web/box.py Mon Dec 21 19:52:21 2009 +0100
+++ b/web/box.py Mon Dec 21 20:00:18 2009 +0100
@@ -221,7 +221,7 @@
form = self.vreg['forms'].select('edition', self._cw, rset=self.cw_rset,
row=self.cw_row or 0)
field = form.field_by_name(self.rtype, get_role(self), entity.e_schema)
- for _, eid in form.form_field_vocabulary(field):
+ for _, eid in field.choices(form):
if eid is not None:
rset = self._cw.eid_rset(eid)
entities.append(rset.get_entity(0, 0))
--- a/web/formfields.py Mon Dec 21 19:52:21 2009 +0100
+++ b/web/formfields.py Mon Dec 21 20:00:18 2009 +0100
@@ -277,19 +277,16 @@
def vocabulary(self, form):
"""return vocabulary for this field. This method will be called by
- widgets which desire it."""
- if self.choices is not None:
- if callable(self.choices):
- try:
- vocab = self.choices(form=form)
- except TypeError:
- warn('[3.3] vocabulary method (eg field.choices) should now take '
- 'the form instance as argument', DeprecationWarning)
- vocab = self.choices(req=form._cw)
- else:
- vocab = self.choices
- if vocab and not isinstance(vocab[0], (list, tuple)):
- vocab = [(x, x) for x in vocab]
+ widgets which requires a vocabulary.
+ """
+ assert self.choices is not None
+ if callable(self.choices):
+ try:
+ vocab = self.choices(form=form)
+ except TypeError:
+ warn('[3.3] vocabulary method (eg field.choices) should now take '
+ 'the form instance as argument', DeprecationWarning)
+ vocab = self.choices(req=form._cw)
else:
vocab = form.form_field_vocabulary(self)
if self.internationalizable:
@@ -644,6 +641,60 @@
time = form.parse_time(wdgdate, 'Time')
return time
+
+# relation vocabulary helper functions #########################################
+
+def relvoc_linkedto(entity, rtype, role):
+ # first see if its specified by __linkto form parameters
+ linkedto = entity.linked_to(rtype, role)
+ if linkedto:
+ buildent = entity._cw.entity_from_eid
+ return [(buildent(eid).view('combobox'), eid) for eid in linkedto]
+ return []
+
+def relvoc_init(entity, rtype, role, required=False):
+ # it isn't, check if the entity provides a method to get correct values
+ vocab = []
+ if not required:
+ vocab.append(('', INTERNAL_FIELD_VALUE))
+ # vocabulary doesn't include current values, add them
+ if entity.has_eid():
+ rset = entity.related(rtype, role)
+ vocab += [(e.view('combobox'), e.eid) for e in rset.entities()]
+ return vocab
+
+def relvoc_unrelated(entity, rtype, role, limit=None):
+ if isinstance(rtype, basestring):
+ rtype = entity._cw.vreg.schema.rschema(rtype)
+ if entity.has_eid():
+ done = set(row[0] for row in entity.related(rtype, role))
+ else:
+ done = None
+ result = []
+ rsetsize = None
+ for objtype in rtype.targets(entity.e_schema, role):
+ if limit is not None:
+ rsetsize = limit - len(result)
+ result += _relvoc_unrelated(entity, rtype, objtype, role, rsetsize, done)
+ if limit is not None and len(result) >= limit:
+ break
+ return result
+
+def _relvoc_unrelated(entity, rtype, targettype, role, limit, done):
+ """return unrelated entities for a given relation and target entity type
+ for use in vocabulary
+ """
+ if done is None:
+ done = set()
+ res = []
+ for entity in entity.unrelated(rtype, targettype, role, limit).entities():
+ if entity.eid in done:
+ continue
+ done.add(entity.eid)
+ res.append((entity.view('combobox'), entity.eid))
+ return res
+
+
class RelationField(Field):
@staticmethod
@@ -651,24 +702,21 @@
kwargs.setdefault('widget', Select(multiple=card in '*+'))
return RelationField(**kwargs)
- def vocabulary(self, form):
+ def choices(self, form, limit=None):
entity = form.edited_entity
# first see if its specified by __linkto form parameters
- linkedto = entity.linked_to(self.name, self.role)
+ linkedto = relvoc_linkedto(entity, self.name, self.role)
if linkedto:
- entities = (req.entity_from_eid(eid) for eid in linkedto)
- return [(entity.view('combobox'), entity.eid) for entity in entities]
+ return linkedto
# it isn't, check if the entity provides a method to get correct values
- res = []
- if not self.required:
- res.append(('', INTERNAL_FIELD_VALUE))
- # vocabulary doesn't include current values, add them
- if entity.has_eid():
- rset = entity.related(self.name, self.role)
- relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()]
- else:
- relatedvocab = []
- vocab = res + form.form_field_vocabulary(self) + relatedvocab
+ vocab = relvoc_init(entity, self.name, self.role, self.required)
+ method = '%s_%s_vocabulary' % (self.role, self.name)
+ try:
+ vocab += getattr(form, method)(rtype, limit)
+ warn('[3.6] found %s on %s, should override field.choices instead (need tweaks)'
+ % (method, form), DeprecationWarning)
+ except AttributeError:
+ vocab += relvoc_unrelated(entity, self.name, self.role, limit)
if self.sort:
vocab = vocab_sort(vocab)
return vocab
--- a/web/views/editviews.py Mon Dec 21 19:52:21 2009 +0100
+++ b/web/views/editviews.py Mon Dec 21 20:00:18 2009 +0100
@@ -117,18 +117,17 @@
def _get_select_options(self, entity, rschema, target):
"""add options to search among all entities of each possible type"""
options = []
- eid = entity.eid
- pending_inserts = self._cw.get_pending_inserts(eid)
+ pending_inserts = self._cw.get_pending_inserts(entity.eid)
rtype = rschema.type
form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
field = form.field_by_name(rschema, target, entity.e_schema)
limit = self._cw.property_value('navigation.combobox-limit')
- for eview, reid in form.form_field_vocabulary(field, limit):
+ for eview, reid in field.choices(form, limit): # XXX expect 'limit' arg on choices
if reid is None:
options.append('<option class="separator">-- %s --</option>'
% xml_escape(eview))
else:
- optionid = relation_id(eid, rtype, target, reid)
+ optionid = relation_id(entity.eid, rtype, target, reid)
if optionid not in pending_inserts:
# prefix option's id with letters to make valid XHTML wise
options.append('<option id="id%s" value="%s">%s</option>' %
--- a/web/views/forms.py Mon Dec 21 19:52:21 2009 +0100
+++ b/web/views/forms.py Mon Dec 21 20:00:18 2009 +0100
@@ -17,7 +17,7 @@
from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
from cubicweb.web import form, formwidgets as fwdgs
from cubicweb.web.controller import NAV_FORM_PARAMETERS
-from cubicweb.web.formfields import StringField
+from cubicweb.web.formfields import StringField, relvoc_unrelated
class FieldsForm(form.Form):
@@ -174,12 +174,6 @@
"""return encoding used for the given (text) field"""
return self._cw.encoding
- 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 form_field_modified(self, field):
return field.is_visible()
@@ -257,52 +251,6 @@
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.root_form.fields_by_name('__linkto'):
- if field.initial in searchedvalues:
- self.root_form.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.__regid__), 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._cw.vreg['formrenderers'].select(
self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row,
col=self.cw_col, entity=self.edited_entity)
@@ -325,34 +273,14 @@
return self.edited_entity.attr_metadata(field.name, 'encoding')
return super(EntityFieldsForm, self).form_field_encoding(field)
# XXX all this vocabulary handling should be on the field, no?
-
- 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)
def actual_eid(self, eid):
# should be either an int (existant entity) or a variable (to be
# created entity)
assert eid or eid == 0, repr(eid) # 0 is a valid eid
try:
- vocabfunc = getattr(self, method)
- except AttributeError:
return typed_eid(eid)
except ValueError:
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.__regid__), 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 form_field_modified(self, field):
if field.is_visible():
@@ -379,49 +307,6 @@
return False # not modified
return True
return False
-
- 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 = self._cw.vreg.schema.rschema(rtype)
- done = None
- assert not rtype.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 = self._cw.vreg.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
return self._cw.data['eidmap'][eid]
except KeyError:
self._cw.data['eidmap'][eid] = None
@@ -433,6 +318,17 @@
def should_display_add_new_relation_link(self, rschema, existant, card):
return False
+ @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function')
+ 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)
+ """
+ return relvoc_unrelated(self.edited_entity, rtype, 'subject', limit=None)
+
+ @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function')
+ def object_relation_vocabulary(self, rtype, limit=None):
+ return relvoc_unrelated(self.edited_entity, rtype, 'object', limit=None)
+
class CompositeFormMixIn(object):
"""form composed of sub-forms"""
--- a/web/views/massmailing.py Mon Dec 21 19:52:21 2009 +0100
+++ b/web/views/massmailing.py Mon Dec 21 20:00:18 2009 +0100
@@ -35,6 +35,10 @@
**params)
+def recipient_vocabulary(form):
+ vocab = [(entity.get_email(), entity.eid) for entity in form.cw_rset.entities()]
+ return [(label, value) for label, value in vocab if label]
+
class MassMailingForm(forms.FieldsForm):
__regid__ = 'massmailing'
@@ -54,12 +58,6 @@
stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
form_renderer_id = __regid__
- def form_field_vocabulary(self, field):
- if field.name == 'recipient':
- vocab = [(entity.get_email(), entity.eid) for entity in self.cw_rset.entities()]
- return [(label, value) for label, value in vocab if label]
- return super(MassMailingForm, self).form_field_vocabulary(field)
-
def __init__(self, *args, **kwargs):
super(MassMailingForm, self).__init__(*args, **kwargs)
field = self.field_by_name('mailbody')