[form, entity] refactor '__linkto', now handled by the entity form, not the entity itself. Closes #1931543
--- 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
+
--- 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
--- 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)
--- 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):
--- 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 []