# HG changeset patch # User Sylvain Thénault # Date 1261422018 -3600 # Node ID 4273f509465108814f36208f93ba19e8273e75cd # Parent 3fbdeef9a61050984a90b590fac3a88c76ff1ebc refactor vocabulary handling to avoid having to define methods on form objects, use .choices instead even for non final relations. Details: * deprecated support of __vocabulary methods on forms * new utility functions for relations vocabulary * most logic now on the RelationField class diff -r 3fbdeef9a610 -r 4273f5094651 web/box.py --- 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)) diff -r 3fbdeef9a610 -r 4273f5094651 web/formfields.py --- 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 diff -r 3fbdeef9a610 -r 4273f5094651 web/views/editviews.py --- 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('' % 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('' % diff -r 3fbdeef9a610 -r 4273f5094651 web/views/forms.py --- 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_ 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, __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""" diff -r 3fbdeef9a610 -r 4273f5094651 web/views/massmailing.py --- 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')