# HG changeset patch # User Florent Cayré # Date 1317141996 -7200 # Node ID 65e460690139ace95abdcc0ed57bd50390a4bd21 # Parent 29fc83fca803228fcb51d66a276e49b48f2e981c [form, entity] refactor '__linkto', now handled by the entity form, not the entity itself. Closes #1931543 diff -r 29fc83fca803 -r 65e460690139 doc/3.14.rst --- a/doc/3.14.rst Mon Sep 26 19:46:04 2011 +0200 +++ b/doc/3.14.rst Tue Sep 27 18:46:36 2011 +0200 @@ -36,3 +36,15 @@ No backward compat yet since this is a protected method an no code is known to use it outside cubicweb itself. +* `AnyEntity.linked_to` has been removed as part of a refactoring of this + functionality (link a entity to another one at creation step). It was replaced + by a `EntityFieldsForm.linked_to` property. + + In the same refactoring, `cubicweb.web.formfield.relvoc_linkedto`, + `cubicweb.web.formfield.relvoc_init` and + `cubicweb.web.formfield.relvoc_unrelated` were removed and replaced by + RelationField methods with the same names, that take a form as a parameter. + + **No backward compatibility yet**. It's still time to cry for it. + Cubes known to be affected: tracker, vcsfile, vcreview + diff -r 29fc83fca803 -r 65e460690139 entities/__init__.py --- a/entities/__init__.py Mon Sep 26 19:46:04 2011 +0200 +++ b/entities/__init__.py Tue Sep 27 18:46:36 2011 +0200 @@ -118,38 +118,6 @@ return self.printable_value(rtype, format='text/plain').lower() return value - # edition helper functions ################################################ - - def linked_to(self, rtype, role, remove=True): - """if entity should be linked to another using '__linkto' form param for - the given relation/role, return eids of related entities - - This method is consuming matching link-to information from form params - if `remove` is True (by default). Computed values are stored into a - `cw_linkto` attribute, a dictionary with (relation, role) as key and - linked eids as value. - """ - try: - return self.cw_linkto[(rtype, role)] - except AttributeError: - self.cw_linkto = {} - except KeyError: - pass - linktos = list(self._cw.list_form_param('__linkto')) - linkedto = [] - for linkto in linktos[:]: - ltrtype, eid, ltrole = linkto.split(':') - if rtype == ltrtype and role == ltrole: - # delete __linkto from form param to avoid it being added as - # hidden input - if remove: - linktos.remove(linkto) - self._cw.form['__linkto'] = linktos - linkedto.append(typed_eid(eid)) - self.cw_linkto[(rtype, role)] = linkedto - return linkedto - - # server side helpers ##################################################### def fetch_config(fetchattrs, mainattr=None, pclass=AnyEntity, order='ASC'): """function to ease basic configuration of an entity class ORM. Basic usage diff -r 29fc83fca803 -r 65e460690139 web/formfields.py --- a/web/formfields.py Mon Sep 26 19:46:04 2011 +0200 +++ b/web/formfields.py Tue Sep 27 18:46:36 2011 +0200 @@ -1018,59 +1018,6 @@ return list(self.fields) -# 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'), unicode(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'), unicode(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'), unicode(entity.eid))) - return res - - class RelationField(Field): """Use this field to edit a relation of an entity. @@ -1095,10 +1042,10 @@ entity = form.edited_entity # first see if its specified by __linkto form parameters if limit is None: - linkedto = relvoc_linkedto(entity, self.name, self.role) + linkedto = self.relvoc_linkedto(form) if linkedto: return linkedto - vocab = relvoc_init(entity, self.name, self.role, self.required) + vocab = self.relvoc_init(form) else: vocab = [] # it isn't, check if the entity provides a method to get correct values @@ -1108,22 +1055,63 @@ 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) + vocab += self.relvoc_unrelated(form, limit) if self.sort: vocab = vocab_sort(vocab) return vocab - def form_init(self, form): - #if not self.display_value(form): - value = form.edited_entity.linked_to(self.name, self.role) - if value: - searchedvalues = ['%s:%s:%s' % (self.name, eid, self.role) - for eid in value] - # remove associated __linkto hidden fields - for field in form.root_form.fields_by_name('__linkto'): - if field.value in searchedvalues: - form.root_form.remove_field(field) - form.formvalues[(self, form)] = value + def relvoc_linkedto(self, form): + linkedto = form.linked_to.get((self.name, self.role)) + if linkedto: + buildent = form._cw.entity_from_eid + return [(buildent(eid).view('combobox'), unicode(eid)) + for eid in linkedto] + return [] + + def relvoc_init(self, form): + entity, rtype, role = form.edited_entity, self.name, self.role + vocab = [] + if not self.required: + vocab.append(('', INTERNAL_FIELD_VALUE)) + # vocabulary doesn't include current values, add them + if form.edited_entity.has_eid(): + rset = form.edited_entity.related(self.name, self.role) + vocab += [(e.view('combobox'), unicode(e.eid)) + for e in rset.entities()] + return vocab + + def relvoc_unrelated(self, form, limit=None): + entity = form.edited_entity + rtype = entity._cw.vreg.schema.rschema(self.name) + if entity.has_eid(): + done = set(row[0] for row in entity.related(rtype, self.role)) + else: + done = None + result = [] + rsetsize = None + for objtype in rtype.targets(entity.e_schema, self.role): + if limit is not None: + rsetsize = limit - len(result) + result += self._relvoc_unrelated(form, objtype, rsetsize, done) + if limit is not None and len(result) >= limit: + break + return result + + def _relvoc_unrelated(self, form, targettype, limit, done): + """return unrelated entities for a given relation and target entity type + for use in vocabulary + """ + if done is None: + done = set() + res = [] + entity = form.edited_entity + for entity in entity.unrelated(self.name, targettype, + self.role, limit).entities(): + if entity.eid in done: + continue + done.add(entity.eid) + res.append((entity.view('combobox'), unicode(entity.eid))) + return res def format_single_value(self, req, value): return unicode(value) diff -r 29fc83fca803 -r 65e460690139 web/views/forms.py --- a/web/views/forms.py Mon Sep 26 19:46:04 2011 +0200 +++ b/web/views/forms.py Tue Sep 27 18:46:36 2011 +0200 @@ -47,7 +47,7 @@ from warnings import warn from logilab.common import dictattr, tempattr -from logilab.common.decorators import iclassmethod +from logilab.common.decorators import iclassmethod, cached from logilab.common.compat import any from logilab.common.textutils import splitstrip from logilab.common.deprecation import deprecated @@ -57,7 +57,7 @@ from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset from cubicweb.web import RequestError, ProcessFormError from cubicweb.web import uicfg, form, formwidgets as fwdgs -from cubicweb.web.formfields import relvoc_unrelated, guess_field +from cubicweb.web.formfields import guess_field class FieldsForm(form.Form): @@ -376,9 +376,7 @@ if kwargs.get('mainform', True) or kwargs.get('mainentity', False): self.add_hidden(u'__maineid', self.edited_entity.eid) # If we need to directly attach the new object to another one - if self._cw.list_form_param('__linkto'): - for linkto in self._cw.list_form_param('__linkto'): - self.add_hidden('__linkto', linkto) + if '__linkto' in self._cw.form: if msg: msg = '%s %s' % (msg, self._cw._('and linked')) else: @@ -387,6 +385,38 @@ msgid = self._cw.set_redirect_message(msg) self.add_hidden('_cwmsgid', msgid) + def add_linkto_hidden(self): + '''add the __linkto hidden field used to directly attach the new object + to an existing other one when the relation between those two is not + already present in the form. + Warning: this method must be called only when all form fields are setup''' + # if current form is not the main form, exit immediately + try: + self.field_by_name('__maineid') + except form.FieldNotFound: + return + for (rtype, role), eids in self.linked_to.iteritems(): + # if the relation is already setup by a form field, do not add it + # in a __linkto hidden to avoid setting it twice in the controller + try: + self.field_by_name(rtype, role) + except form.FieldNotFound: + for eid in eids: + self.add_hidden('__linkto', '%s:%s:%s' % (rtype, role, eid)) + + def render(self, *args, **kwargs): + self.add_linkto_hidden() + return super(EntityFieldsForm, self).render(*args, **kwargs) + + @property + @cached + def linked_to(self): + linked_to = {} + for linkto in self._cw.list_form_param('__linkto'): + ltrtype, eid, ltrole = linkto.split(':') + linked_to.setdefault((ltrtype, ltrole), []).append(typed_eid(eid)) + return linked_to + def session_key(self): """return the key that may be used to store / retreive data about a previous post which failed because of a validation error @@ -427,16 +457,18 @@ def editable_relations(self): return () - @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function') + @deprecated('[3.6] use cw.web.formfields.RelationField.relvoc_unrelated method') 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) + field = self.field_by_name(rtype, 'subject') + return field.relvoc_unrelated(form, limit=None) - @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated function') + @deprecated('[3.6] use cw.web.formfields.relvoc_unrelated method') def object_relation_vocabulary(self, rtype, limit=None): - return relvoc_unrelated(self.edited_entity, rtype, 'object', limit=None) + field = self.field_by_name(rtype, 'object') + return field.relvoc_unrelated(form, limit=None) class CompositeFormMixIn(object): diff -r 29fc83fca803 -r 65e460690139 web/views/workflow.py --- a/web/views/workflow.py Mon Sep 26 19:46:04 2011 +0200 +++ b/web/views/workflow.py Tue Sep 27 18:46:36 2011 +0200 @@ -340,7 +340,7 @@ def transition_states_vocabulary(form, field): entity = form.edited_entity if not entity.has_eid(): - eids = entity.linked_to('transition_of', 'subject') + eids = form.linked_to.get(('transition_of', 'subject')) if not eids: return [] return _wf_items_for_relation(form._cw, eids[0], 'state_of', field) @@ -358,7 +358,7 @@ def state_transitions_vocabulary(form, field): entity = form.edited_entity if not entity.has_eid(): - eids = entity.linked_to('state_of', 'subject') + eids = form.linked_to.get(('state_of', 'subject')) if eids: return _wf_items_for_relation(form._cw, eids[0], 'transition_of', field) return []