reorganize code:
move everything related to inlined forms and generic relation from
editviews/editforms where there are used, eg in autoforms
--- a/web/views/autoform.py Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/autoform.py Wed Jan 27 09:53:48 2010 +0100
@@ -9,17 +9,538 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from logilab.common.decorators import iclassmethod
+from simplejson import dumps
+
+from logilab.mtconverter import xml_escape
+from logilab.common.decorators import iclassmethod, cached
-from cubicweb import typed_eid
-from cubicweb.web import stdmsgs, uicfg, form, \
- formwidgets as fw, formfields as ff
-from cubicweb.web.views import forms, editforms, editviews
+from cubicweb import typed_eid, neg_role, uilib
+from cubicweb.schema import display_name
+from cubicweb.view import EntityView
+from cubicweb.selectors import (
+ match_kwargs, match_form_params, non_final_entity,
+ specified_etype_implements)
+from cubicweb.web import stdmsgs, uicfg, eid_param, \
+ form as f, formwidgets as fw, formfields as ff
+from cubicweb.web.views import forms
_AFS = uicfg.autoform_section
_AFFK = uicfg.autoform_field_kwargs
+# inlined form handling ########################################################
+
+class InlinedFormField(ff.Field):
+ def __init__(self, view=None, **kwargs):
+ if view.role == 'object':
+ fieldset = u'%s_object%s' % view.rtype
+ else:
+ fieldset = view.rtype
+ #kwargs.setdefault('fieldset', fieldset)
+ kwargs.setdefault('label', None)
+ super(InlinedFormField, self).__init__(name=view.rtype, role=view.role,
+ eidparam=True, **kwargs)
+ self.view = view
+
+ def render(self, form, renderer):
+ """render this field, which is part of form, using the given form
+ renderer
+ """
+ view = self.view
+ i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
+ view.rtype, view.role)
+ return u'<div class="inline-%s-%s-slot">%s</div>' % (
+ view.rtype, view.role,
+ view.render(i18nctx=i18nctx, row=view.cw_row, col=view.cw_col))
+
+ def form_init(self, form):
+ """method called before by build_context to trigger potential field
+ initialization requiring the form instance
+ """
+ if self.view.form:
+ self.view.form.build_context(form.formvalues)
+
+ @property
+ def needs_multipart(self):
+ if self.view.form:
+ # take a look at inlined forms to check (recursively) if they need
+ # multipart handling.
+ return self.view.form.needs_multipart
+ return False
+
+ def has_been_modified(self, form):
+ return False
+
+ def process_posted(self, form):
+ pass # handled by the subform
+
+
+class InlineEntityEditionFormView(f.FormViewMixIn, EntityView):
+ """
+ :attr peid: the parent entity's eid hosting the inline form
+ :attr rtype: the relation bridging `etype` and `peid`
+ :attr role: the role played by the `peid` in the relation
+ :attr pform: the parent form where this inlined form is being displayed
+ """
+ __regid__ = 'inline-edition'
+ __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+
+ _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype')
+ removejs = "removeInlinedEntity('%s', '%s', '%s')"
+
+ def __init__(self, *args, **kwargs):
+ for attr in self._select_attrs:
+ setattr(self, attr, kwargs.pop(attr, None))
+ super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
+
+ def _entity(self):
+ assert self.cw_row is not None, self
+ return self.cw_rset.get_entity(self.cw_row, self.cw_col)
+
+ @property
+ @cached
+ def form(self):
+ entity = self._entity()
+ form = self._cw.vreg['forms'].select('edition', self._cw,
+ entity=entity,
+ formtype='inlined',
+ form_renderer_id='inline',
+ copy_nav_params=False,
+ mainform=False,
+ parent_form=self.pform,
+ **self.cw_extra_kwargs)
+ if self.pform is None:
+ form.restore_previous_post(form.session_key())
+ #assert form.parent_form
+ self.add_hiddens(form, entity)
+ return form
+
+ def cell_call(self, row, col, i18nctx, **kwargs):
+ """
+ :param peid: the parent entity's eid hosting the inline form
+ :param rtype: the relation bridging `etype` and `peid`
+ :param role: the role played by the `peid` in the relation
+ """
+ entity = self._entity()
+ divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (
+ self.peid, self.rtype, entity.eid)
+ self.render_form(i18nctx, divonclick=divonclick, **kwargs)
+
+ def render_form(self, i18nctx, **kwargs):
+ """fetch and render the form"""
+ entity = self._entity()
+ divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
+ title = self.form_title(entity, i18nctx)
+ removejs = self.removejs and self.removejs % (
+ self.peid, self.rtype, entity.eid)
+ countkey = '%s_count' % self.rtype
+ try:
+ self._cw.data[countkey] += 1
+ except KeyError:
+ self._cw.data[countkey] = 1
+ self.w(self.form.render(
+ divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
+ counter=self._cw.data[countkey] , **kwargs))
+
+ def form_title(self, entity, i18nctx):
+ return self._cw.pgettext(i18nctx, entity.__regid__)
+
+ def add_hiddens(self, form, entity):
+ """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
+ iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)
+ # * str(self.rtype) in case it's a schema object
+ # * neged_role() since role is the for parent entity, we want the role
+ # of the inlined entity
+ form.add_hidden(name=str(self.rtype), value=self.peid,
+ role=neg_role(self.role), eidparam=True, id=iid)
+
+ def keep_entity(self, form, entity):
+ if not entity.has_eid():
+ return True
+ # are we regenerating form because of a validation error ?
+ if form.form_previous_values:
+ cdvalues = self._cw.list_form_param(eid_param(self.rtype, self.peid),
+ form.form_previous_values)
+ if unicode(entity.eid) not in cdvalues:
+ return False
+ return True
+
+
+class InlineEntityCreationFormView(InlineEntityEditionFormView):
+ """
+ :attr etype: the entity type being created in the inline form
+ """
+ __regid__ = 'inline-creation'
+ __select__ = (match_kwargs('peid', 'rtype')
+ & specified_etype_implements('Any'))
+
+ @property
+ def removejs(self):
+ entity = self._entity()
+ card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role)
+ # when one is adding an inline entity for a relation of a single card,
+ # the 'add a new xxx' link disappears. If the user then cancel the addition,
+ # we have to make this link appears back. This is done by giving add new link
+ # id to removeInlineForm.
+ if card not in '?1':
+ return "removeInlineForm('%%s', '%%s', '%s', '%%s')" % self.role
+ divid = "addNew%s%s%s:%s" % (
+ self.etype, self.rtype, self.role, self.peid)
+ return "removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')" % (
+ self.role, divid)
+
+ @cached
+ def _entity(self):
+ try:
+ cls = self._cw.vreg['etypes'].etype_class(self.etype)
+ except:
+ self.w(self._cw._('no such entity type %s') % etype)
+ return
+ entity = cls(self._cw)
+ entity.eid = self._cw.varmaker.next()
+ return entity
+
+ def call(self, i18nctx, **kwargs):
+ self.render_form(i18nctx, **kwargs)
+
+
+class InlineAddNewLinkView(InlineEntityCreationFormView):
+ """
+ :attr card: the cardinality of the relation according to role of `peid`
+ """
+ __regid__ = 'inline-addnew-link'
+ __select__ = (match_kwargs('peid', 'rtype')
+ & specified_etype_implements('Any'))
+
+ _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
+ form = None # no actual form wrapped
+
+ def call(self, i18nctx, **kwargs):
+ self._cw.set_varmaker()
+ divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
+ self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
+ % divid)
+ js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
+ self.peid, self.etype, self.rtype, self.role, i18nctx)
+ if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
+ js = "toggleVisibility('%s'); %s" % (divid, js)
+ __ = self._cw.pgettext
+ self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
+ % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
+ self.w(u'</div>')
+
+
+# generic relations handling ##################################################
+
+def relation_id(eid, rtype, role, reid):
+ """return an identifier for a relation between two entities"""
+ if role == 'subject':
+ return u'%s:%s:%s' % (eid, rtype, reid)
+ return u'%s:%s:%s' % (reid, rtype, eid)
+
+def toggleable_relation_link(eid, nodeid, label='x'):
+ """return javascript snippet to delete/undelete a relation between two
+ entities
+ """
+ js = u"javascript: togglePendingDelete('%s', %s);" % (
+ nodeid, xml_escape(dumps(eid)))
+ return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
+ js, nodeid, label)
+
+
+def get_pending_inserts(req, eid=None):
+ """shortcut to access req's pending_insert entry
+
+ This is where are stored relations being added while editing
+ an entity. This used to be stored in a temporary cookie.
+ """
+ pending = req.get_session_data('pending_insert') or ()
+ return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
+ if eid is None or eid in (subj, obj)]
+
+def get_pending_deletes(req, eid=None):
+ """shortcut to access req's pending_delete entry
+
+ This is where are stored relations being removed while editing
+ an entity. This used to be stored in a temporary cookie.
+ """
+ pending = req.get_session_data('pending_delete') or ()
+ return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
+ if eid is None or eid in (subj, obj)]
+
+def parse_relations_descr(rdescr):
+ """parse a string describing some relations, in the form
+ subjeids:rtype:objeids
+ where subjeids and objeids are eids separeted by a underscore
+
+ return an iterator on (subject eid, relation type, object eid) found
+ """
+ for rstr in rdescr:
+ subjs, rtype, objs = rstr.split(':')
+ for subj in subjs.split('_'):
+ for obj in objs.split('_'):
+ yield typed_eid(subj), rtype, typed_eid(obj)
+
+def delete_relations(req, rdefs):
+ """delete relations from the repository"""
+ # FIXME convert to using the syntax subject:relation:eids
+ execute = req.execute
+ for subj, rtype, obj in parse_relations_descr(rdefs):
+ rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
+ execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+ req.set_message(req._('relations deleted'))
+
+def insert_relations(req, rdefs):
+ """insert relations into the repository"""
+ execute = req.execute
+ for subj, rtype, obj in parse_relations_descr(rdefs):
+ rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
+ execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+
+
+class GenericRelationsWidget(fw.FieldWidget):
+
+ def render(self, form, field, renderer):
+ stream = []
+ w = stream.append
+ req = form._cw
+ _ = req._
+ __ = _
+ eid = form.edited_entity.eid
+ w(u'<table id="relatedEntities">')
+ for rschema, role, related in field.relations_table(form):
+ # already linked entities
+ if related:
+ w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, role))
+ w(u'<td>')
+ w(u'<ul>')
+ for viewparams in related:
+ w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
+ % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
+ if not form.force_display and form.maxrelitems < len(related):
+ link = (u'<span class="invisible">'
+ '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
+ '</span>' % _('view all'))
+ w(u'<li class="invisible">%s</li>' % link)
+ w(u'</ul>')
+ w(u'</td>')
+ w(u'</tr>')
+ pendings = list(field.restore_pending_inserts(form))
+ if not pendings:
+ w(u'<tr><th> </th><td> </td></tr>')
+ else:
+ for row in pendings:
+ # soon to be linked to entities
+ w(u'<tr id="tr%s">' % row[1])
+ w(u'<th>%s</th>' % row[3])
+ w(u'<td>')
+ w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
+ (_('cancel this insert'), row[2]))
+ w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
+ % (row[1], row[4], xml_escape(row[5])))
+ w(u'</td>')
+ w(u'</tr>')
+ w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
+ w(u'<th class="labelCol">')
+ w(u'<select id="relationSelector_%s" tabindex="%s" '
+ 'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
+ % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
+ w(u'<option value="">%s</option>' % _('select a relation'))
+ for i18nrtype, rschema, role in field.relations:
+ # more entities to link to
+ w(u'<option value="%s_%s">%s</option>' % (rschema, role, i18nrtype))
+ w(u'</select>')
+ w(u'</th>')
+ w(u'<td id="unrelatedDivs_%s"></td>' % eid)
+ w(u'</tr>')
+ w(u'</table>')
+ return '\n'.join(stream)
+
+
+class GenericRelationsField(ff.Field):
+ widget = GenericRelationsWidget
+
+ def __init__(self, relations, name='_cw_generic_field', **kwargs):
+ assert relations
+ kwargs['eidparam'] = True
+ super(GenericRelationsField, self).__init__(name, **kwargs)
+ self.relations = relations
+
+ def process_posted(self, form):
+ todelete = get_pending_deletes(form._cw)
+ if todelete:
+ delete_relations(form._cw, todelete)
+ toinsert = get_pending_inserts(form._cw)
+ if toinsert:
+ insert_relations(form._cw, toinsert)
+ return ()
+
+ def relations_table(self, form):
+ """yiels 3-tuples (rtype, role, related_list)
+ where <related_list> itself a list of :
+ - node_id (will be the entity element's DOM id)
+ - appropriate javascript's togglePendingDelete() function call
+ - status 'pendingdelete' or ''
+ - oneline view of related entity
+ """
+ entity = form.edited_entity
+ pending_deletes = get_pending_deletes(form._cw, entity.eid)
+ for label, rschema, role in self.relations:
+ related = []
+ if entity.has_eid():
+ rset = entity.related(rschema, role, limit=form.related_limit)
+ if rschema.has_perm(form._cw, 'delete'):
+ toggleable_rel_link_func = toggleable_relation_link
+ else:
+ toggleable_rel_link_func = lambda x, y, z: u''
+ for row in xrange(rset.rowcount):
+ nodeid = relation_id(entity.eid, rschema, role,
+ rset[row][0])
+ if nodeid in pending_deletes:
+ status, label = u'pendingDelete', '+'
+ else:
+ status, label = u'', 'x'
+ dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
+ eview = form._cw.view('oneline', rset, row=row)
+ related.append((nodeid, dellink, status, eview))
+ yield (rschema, role, related)
+
+ def restore_pending_inserts(self, form):
+ """used to restore edition page as it was before clicking on
+ 'search for <some entity type>'
+ """
+ entity = form.edited_entity
+ pending_inserts = set(get_pending_inserts(form._cw, form.edited_entity.eid))
+ for pendingid in pending_inserts:
+ eidfrom, rtype, eidto = pendingid.split(':')
+ if typed_eid(eidfrom) == entity.eid: # subject
+ label = display_name(form._cw, rtype, 'subject',
+ entity.__regid__)
+ reid = eidto
+ else:
+ label = display_name(form._cw, rtype, 'object',
+ entity.__regid__)
+ reid = eidfrom
+ jscall = "javascript: cancelPendingInsert('%s', 'tr', null, %s);" \
+ % (pendingid, entity.eid)
+ rset = form._cw.eid_rset(reid)
+ eview = form._cw.view('text', rset, row=0)
+ # XXX find a clean way to handle baskets
+ if rset.description[0][0] == 'Basket':
+ eview = '%s (%s)' % (eview, display_name(form._cw, 'Basket'))
+ yield rtype, pendingid, jscall, label, reid, eview
+
+
+class UnrelatedDivs(EntityView):
+ __regid__ = 'unrelateddivs'
+ __select__ = match_form_params('relation')
+
+ def cell_call(self, row, col):
+ entity = self.cw_rset.get_entity(row, col)
+ relname, role = self._cw.form.get('relation').rsplit('_', 1)
+ rschema = self._cw.vreg.schema.rschema(relname)
+ hidden = 'hidden' in self._cw.form
+ is_cell = 'is_cell' in self._cw.form
+ self.w(self.build_unrelated_select_div(entity, rschema, role,
+ is_cell=is_cell, hidden=hidden))
+
+ def build_unrelated_select_div(self, entity, rschema, role,
+ is_cell=False, hidden=True):
+ options = []
+ divid = 'div%s_%s_%s' % (rschema.type, role, entity.eid)
+ selectid = 'select%s_%s_%s' % (rschema.type, role, entity.eid)
+ if rschema.symetric or role == 'subject':
+ targettypes = rschema.objects(entity.e_schema)
+ etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+ else:
+ targettypes = rschema.subjects(entity.e_schema)
+ etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+ etypes = uilib.cut(etypes, self._cw.property_value('navigation.short-line-size'))
+ options.append('<option>%s %s</option>' % (self._cw._('select a'), etypes))
+ options += self._get_select_options(entity, rschema, role)
+ options += self._get_search_options(entity, rschema, role, targettypes)
+ if 'Basket' in self._cw.vreg.schema: # XXX
+ options += self._get_basket_options(entity, rschema, role, targettypes)
+ relname, role = self._cw.form.get('relation').rsplit('_', 1)
+ return u"""\
+<div class="%s" id="%s">
+ <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
+ %s
+ </select>
+</div>
+""" % (hidden and 'hidden' or '', divid, selectid,
+ xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+ '\n'.join(options))
+
+ def _get_select_options(self, entity, rschema, role):
+ """add options to search among all entities of each possible type"""
+ options = []
+ pending_inserts = get_pending_inserts(self._cw, entity.eid)
+ rtype = rschema.type
+ form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
+ field = form.field_by_name(rschema, role, entity.e_schema)
+ limit = self._cw.property_value('navigation.combobox-limit')
+ # NOTE: expect 'limit' arg on choices method of relation field
+ for eview, reid in field.vocabulary(form, limit=limit):
+ if reid is None:
+ if eview: # skip blank value
+ options.append('<option class="separator">-- %s --</option>'
+ % xml_escape(eview))
+ elif reid != ff.INTERNAL_FIELD_VALUE:
+ optionid = relation_id(entity.eid, rtype, role, 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>' %
+ (optionid, reid, xml_escape(eview)))
+ return options
+
+ def _get_search_options(self, entity, rschema, role, targettypes):
+ """add options to search among all entities of each possible type"""
+ options = []
+ _ = self._cw._
+ for eschema in targettypes:
+ mode = '%s:%s:%s:%s' % (role, entity.eid, rschema.type, eschema)
+ url = self._cw.build_url(entity.rest_path(), vid='search-associate',
+ __mode=mode)
+ options.append((eschema.display_name(self._cw),
+ '<option value="%s">%s %s</option>' % (
+ xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
+ return [o for l, o in sorted(options)]
+
+ # XXX move this out
+ def _get_basket_options(self, entity, rschema, role, targettypes):
+ options = []
+ rtype = rschema.type
+ _ = self._cw._
+ for basketeid, basketname in self._get_basket_links(self._cw.user.eid,
+ role, targettypes):
+ optionid = relation_id(entity.eid, rtype, role, basketeid)
+ options.append('<option id="%s" value="%s">%s %s</option>' % (
+ optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
+ return options
+
+ def _get_basket_links(self, ueid, role, targettypes):
+ targettypes = set(targettypes)
+ for basketeid, basketname, elements in self._get_basket_info(ueid):
+ baskettypes = elements.column_types(0)
+ # if every elements in the basket can be attached to the
+ # edited entity
+ if baskettypes & targettypes:
+ yield basketeid, basketname
+
+ def _get_basket_info(self, ueid):
+ basketref = []
+ basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
+ basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x')
+ for result in basketresultset:
+ basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
+ rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x')
+ basketref.append((result[0], result[1], rset))
+ return basketref
+
+
+# The automatic entity form ####################################################
+
class AutomaticEntityForm(forms.EntityFieldsForm):
"""base automatic form to edit any entity.
@@ -54,7 +575,7 @@
"""
try:
return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role, eschema)
- except form.FieldNotFound:
+ except f.FieldNotFound:
if name == '_cw_generic_field' and not isinstance(cls_or_self, type):
return cls_or_self._generic_relations_field()
raise
@@ -70,13 +591,13 @@
try:
self.field_by_name(str(rtype), role)
continue # explicitly specified
- except form.FieldNotFound:
+ except f.FieldNotFound:
# has to be guessed
try:
field = self.field_by_name(str(rtype), role,
eschema=entity.e_schema)
self.fields.append(field)
- except form.FieldNotFound:
+ except f.FieldNotFound:
# meta attribute such as <attr>_format
continue
if self.formtype == 'main':
@@ -97,7 +618,7 @@
'_cw_generic_field' in self.display_fields):
try:
field = self.field_by_name('_cw_generic_field')
- except form.FieldNotFound:
+ except f.FieldNotFound:
# no editable relation
pass
else:
@@ -141,11 +662,11 @@
except AttributeError:
srels_by_cat = self.editable_relations()
if not srels_by_cat:
- raise form.FieldNotFound('_cw_generic_field')
+ raise f.FieldNotFound('_cw_generic_field')
fieldset = u'%s :' % self._cw.__('This %s' % self.edited_entity.e_schema)
fieldset = fieldset.capitalize()
- return editviews.GenericRelationsField(self.editable_relations(),
- fieldset=fieldset, label=None)
+ return GenericRelationsField(self.editable_relations(),
+ fieldset=fieldset, label=None)
def _inlined_form_view_field(self, view):
# XXX allow more customization
@@ -153,7 +674,7 @@
view.role, view.etype)
if kwargs is None:
kwargs = {}
- return editforms.InlinedFormField(view=view, **kwargs)
+ return InlinedFormField(view=view, **kwargs)
# methods mapping edited entity relations to fields in the form ############
--- a/web/views/editcontroller.py Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/editcontroller.py Wed Jan 27 09:53:48 2010 +0100
@@ -13,8 +13,7 @@
from cubicweb import Binary, ValidationError, typed_eid
from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
-from cubicweb.web.views.editviews import delete_relations, insert_relations
-from cubicweb.web.views.basecontrollers import ViewController
+from cubicweb.web.views import basecontrollers, autoform
class RqlQuery(object):
@@ -44,7 +43,7 @@
return rql
-class EditController(ViewController):
+class EditController(basecontrollers.ViewController):
__regid__ = 'edit'
def publish(self, rset=None):
@@ -99,13 +98,13 @@
if req.form.has_key('__delete'):
todelete = req.list_form_param('__delete', req.form, pop=True)
if todelete:
- delete_relations(self._cw, todelete)
+ autoform.delete_relations(self._cw, todelete)
if req.form.has_key('__insert'):
warn('[3.6] stop using __insert, support will be removed',
DeprecationWarning)
toinsert = req.list_form_param('__insert', req.form, pop=True)
if toinsert:
- insert_relations(self._cw, toinsert)
+ autoform.insert_relations(self._cw, toinsert)
self._cw.remove_pending_operations()
if self.errors:
errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
@@ -174,7 +173,7 @@
if formparams.has_key('__delete'):
# XXX deprecate?
todelete = self._cw.list_form_param('__delete', formparams, pop=True)
- delete_relations(self._cw, todelete)
+ autoform.delete_relations(self._cw, todelete)
if formparams.has_key('__cloned_eid'):
entity.copy_relations(typed_eid(formparams['__cloned_eid']))
if is_main_entity: # only execute linkto for the main entity
--- a/web/views/editforms.py Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/editforms.py Wed Jan 27 09:53:48 2010 +0100
@@ -16,7 +16,6 @@
from logilab.mtconverter import xml_escape
from logilab.common.decorators import cached
-from cubicweb import neg_role
from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
specified_etype_implements, yes)
from cubicweb.view import EntityView
@@ -83,204 +82,6 @@
w(form.render())
-class ClickAndEditFormView(FormViewMixIn, EntityView):
- """form used to permit ajax edition of a relation or attribute of an entity
- in a view, if logged user have the permission to edit it.
-
- (double-click on the field to see an appropriate edition widget).
- """
- __regid__ = 'doreledit'
- __select__ = non_final_entity() & match_kwargs('rtype')
- # FIXME editableField class could be toggleable from userprefs
-
- _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
- _onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
- "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
- _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
- _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
- 'alt="%(msg)s"/>')
- _landingzonemsg = _('click to edit this field')
- # default relation vids according to cardinality
- _one_rvid = 'incontext'
- _many_rvid = 'csv'
-
-
- def cell_call(self, row, col, rtype=None, role='subject',
- reload=False, # controls reloading the whole page after change
- rvid=None, # vid to be applied to other side of rtype (non final relations only)
- default=None, # default value
- landing_zone=None # prepend value with a separate html element to click onto
- # (esp. needed when values are links)
- ):
- """display field to edit entity's `rtype` relation on click"""
- assert rtype
- assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
- self._cw.add_js('cubicweb.edition.js')
- self._cw.add_css('cubicweb.form.css')
- if default is None:
- default = xml_escape(self._cw._('<no value>'))
- schema = self._cw.vreg.schema
- entity = self.cw_rset.get_entity(row, col)
- rschema = schema.rschema(rtype)
- lzone = self._build_landing_zone(landing_zone)
- # compute value, checking perms, build form
- if rschema.final:
- form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
- if not self.should_edit_attribute(entity, rschema, role, form):
- self.w(entity.printable_value(rtype))
- return
- value = entity.printable_value(rtype) or default
- else:
- rvid = self._compute_best_vid(entity.e_schema, rschema, role)
- rset = entity.related(rtype, role)
- if rset:
- value = self._cw.view(rvid, rset)
- else:
- value = default
- if not self.should_edit_relation(entity, rschema, role, rvid):
- if rset:
- self.w(value)
- return
- # XXX do we really have to give lzone twice?
- form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
- dict(vid=rvid, lzone=lzone))
- field = form.field_by_name(rtype, role, entity.e_schema)
- form.append_field(field)
- self.relation_form(lzone, value, form,
- self._build_renderer(entity, rtype, role))
-
- def should_edit_attribute(self, entity, rschema, role, form):
- rtype = str(rschema)
- ttype = rschema.targets(entity.__regid__, role)[0]
- afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
- if 'main_hidden' in afs or not entity.has_perm('update'):
- return False
- try:
- form.field_by_name(rtype, role, entity.e_schema)
- except FieldNotFound:
- return False
- return True
-
- def should_edit_relation(self, entity, rschema, role, rvid):
- if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
- fromeid=entity.eid))
- or
- (role == 'object' and not rschema.has_perm(self._cw, 'add',
- toeid=entity.eid))):
- return False
- return True
-
- def relation_form(self, lzone, value, form, renderer):
- """xxx-reledit div (class=field)
- +-xxx div (class="editableField")
- | +-landing zone
- +-xxx-value div
- +-xxx-form div
- """
- w = self.w
- divid = form.event_args['divid']
- w(u'<div id="%s-reledit" class="field" '
- u'onmouseout="addElementClass(jQuery(\'#%s\'), \'hidden\')" '
- u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
- % (divid, divid, divid))
- w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
- w(form.render(renderer=renderer))
- w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
- divid, xml_escape(self._onclick % form.event_args),
- self._cw._(self._landingzonemsg)))
- w(lzone)
- w(u'</div>')
- w(u'</div>')
-
- def _compute_best_vid(self, eschema, rschema, role):
- dispctrl = _pvdc.etype_get(eschema, rschema, role)
- if dispctrl.get('rvid'):
- return dispctrl['rvid']
- if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
- return self._many_rvid
- return self._one_rvid
-
- def _build_landing_zone(self, lzone):
- return lzone or self._defaultlandingzone % {
- 'msg': xml_escape(self._cw._(self._landingzonemsg))}
-
- def _build_renderer(self, entity, rtype, role):
- return self._cw.vreg['formrenderers'].select(
- 'base', self._cw, entity=entity, display_label=False,
- display_help=False, table_class='',
- button_bar_class='buttonbar', display_progress_div=False)
-
- def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
- extradata=None):
- divid = '%s-%s-%s' % (rtype, role, entity.eid)
- event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
- 'reload' : dumps(reload), 'default' : default, 'role' : role, 'vid' : u'',
- 'lzone' : lzone}
- if extradata:
- event_args.update(extradata)
- return divid, event_args
-
- def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
- extradata=None, **formargs):
- divid, event_args = self._build_args(entity, rtype, role, formid, default,
- reload, lzone, extradata)
- onsubmit = self._onsubmit % event_args
- cancelclick = self._cancelclick % (entity.eid, rtype, divid)
- form = self._cw.vreg['forms'].select(
- formid, self._cw, entity=entity, domid='%s-form' % divid,
- cssstyle='display: none', onsubmit=onsubmit, action='#',
- form_buttons=[fw.SubmitButton(),
- fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)],
- **formargs)
- form.event_args = event_args
- return form
-
-
-class DummyForm(object):
- __slots__ = ('event_args',)
- def form_render(self, **_args):
- return u''
- def render(self, **_args):
- return u''
- def append_field(self, *args):
- pass
- def field_by_name(self, rtype, role, eschema=None):
- return None
-
-
-class AutoClickAndEditFormView(ClickAndEditFormView):
- """same as ClickAndEditFormView but checking if the view *should* be applied
- by checking uicfg configuration and composite relation property.
- """
- __regid__ = 'reledit'
- _onclick = (u"loadInlineEditionForm(%(eid)s, '%(rtype)s', '%(role)s', "
- "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
-
- def should_edit_relation(self, entity, rschema, role, rvid):
- eschema = entity.e_schema
- rtype = str(rschema)
- # XXX check autoform_section. what if 'generic'?
- dispctrl = _pvdc.etype_get(eschema, rtype, role)
- vid = dispctrl.get('vid', 'reledit')
- if vid != 'reledit': # reledit explicitly disabled
- return False
- if eschema.rdef(rschema, role).composite == role:
- return False
- return super(AutoClickAndEditFormView, self).should_edit_relation(
- entity, rschema, role, rvid)
-
- def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
- extradata=None, **formargs):
- _divid, event_args = self._build_args(entity, rtype, role, formid, default,
- reload, lzone, extradata)
- form = DummyForm()
- form.event_args = event_args
- return form
-
- def _build_renderer(self, entity, rtype, role):
- pass
-
-
class EditionFormView(FormViewMixIn, EntityView):
"""display primary entity edition form"""
__regid__ = 'edition'
@@ -449,203 +250,202 @@
self.w(form.render(formvid='edition'))
-# inlined form handling ########################################################
+# click and edit handling ('reledit') ##########################################
-class InlinedFormField(ff.Field):
- def __init__(self, view=None, **kwargs):
- if view.role == 'object':
- fieldset = u'%s_object%s' % view.rtype
- else:
- fieldset = view.rtype
- #kwargs.setdefault('fieldset', fieldset)
- kwargs.setdefault('label', None)
- super(InlinedFormField, self).__init__(name=view.rtype, role=view.role,
- eidparam=True, **kwargs)
- self.view = view
+class DummyForm(object):
+ __slots__ = ('event_args',)
+ def form_render(self, **_args):
+ return u''
+ def render(self, **_args):
+ return u''
+ def append_field(self, *args):
+ pass
+ def field_by_name(self, rtype, role, eschema=None):
+ return None
+
- def render(self, form, renderer):
- """render this field, which is part of form, using the given form
- renderer
- """
- view = self.view
- i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
- view.rtype, view.role)
- return u'<div class="inline-%s-%s-slot">%s</div>' % (
- view.rtype, view.role,
- view.render(i18nctx=i18nctx, row=view.cw_row, col=view.cw_col))
+class ClickAndEditFormView(FormViewMixIn, EntityView):
+ """form used to permit ajax edition of a relation or attribute of an entity
+ in a view, if logged user have the permission to edit it.
+
+ (double-click on the field to see an appropriate edition widget).
+ """
+ __regid__ = 'doreledit'
+ __select__ = non_final_entity() & match_kwargs('rtype')
+ # FIXME editableField class could be toggleable from userprefs
- def form_init(self, form):
- """method called before by build_context to trigger potential field
- initialization requiring the form instance
- """
- if self.view.form:
- self.view.form.build_context(form.formvalues)
-
- @property
- def needs_multipart(self):
- if self.view.form:
- # take a look at inlined forms to check (recursively) if they need
- # multipart handling.
- return self.view.form.needs_multipart
- return False
-
- def has_been_modified(self, form):
- return False
-
- def process_posted(self, form):
- pass # handled by the subform
+ _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+ _onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
+ "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
+ _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
+ _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
+ 'alt="%(msg)s"/>')
+ _landingzonemsg = _('click to edit this field')
+ # default relation vids according to cardinality
+ _one_rvid = 'incontext'
+ _many_rvid = 'csv'
-class InlineEntityEditionFormView(FormViewMixIn, EntityView):
- """
- :attr peid: the parent entity's eid hosting the inline form
- :attr rtype: the relation bridging `etype` and `peid`
- :attr role: the role played by the `peid` in the relation
- :attr pform: the parent form where this inlined form is being displayed
- """
- __regid__ = 'inline-edition'
- __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+ def cell_call(self, row, col, rtype=None, role='subject',
+ reload=False, # controls reloading the whole page after change
+ rvid=None, # vid to be applied to other side of rtype (non final relations only)
+ default=None, # default value
+ landing_zone=None # prepend value with a separate html element to click onto
+ # (esp. needed when values are links)
+ ):
+ """display field to edit entity's `rtype` relation on click"""
+ assert rtype
+ assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
+ self._cw.add_js('cubicweb.edition.js')
+ self._cw.add_css('cubicweb.form.css')
+ if default is None:
+ default = xml_escape(self._cw._('<no value>'))
+ schema = self._cw.vreg.schema
+ entity = self.cw_rset.get_entity(row, col)
+ rschema = schema.rschema(rtype)
+ lzone = self._build_landing_zone(landing_zone)
+ # compute value, checking perms, build form
+ if rschema.final:
+ form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
+ if not self.should_edit_attribute(entity, rschema, role, form):
+ self.w(entity.printable_value(rtype))
+ return
+ value = entity.printable_value(rtype) or default
+ else:
+ rvid = self._compute_best_vid(entity.e_schema, rschema, role)
+ rset = entity.related(rtype, role)
+ if rset:
+ value = self._cw.view(rvid, rset)
+ else:
+ value = default
+ if not self.should_edit_relation(entity, rschema, role, rvid):
+ if rset:
+ self.w(value)
+ return
+ # XXX do we really have to give lzone twice?
+ form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
+ dict(vid=rvid, lzone=lzone))
+ field = form.field_by_name(rtype, role, entity.e_schema)
+ form.append_field(field)
+ self.relation_form(lzone, value, form,
+ self._build_renderer(entity, rtype, role))
- _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype')
- removejs = "removeInlinedEntity('%s', '%s', '%s')"
-
- def __init__(self, *args, **kwargs):
- for attr in self._select_attrs:
- setattr(self, attr, kwargs.pop(attr, None))
- super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
+ def should_edit_attribute(self, entity, rschema, role, form):
+ rtype = str(rschema)
+ ttype = rschema.targets(entity.__regid__, role)[0]
+ afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
+ if 'main_hidden' in afs or not entity.has_perm('update'):
+ return False
+ try:
+ form.field_by_name(rtype, role, entity.e_schema)
+ except FieldNotFound:
+ return False
+ return True
- def _entity(self):
- assert self.cw_row is not None, self
- return self.cw_rset.get_entity(self.cw_row, self.cw_col)
+ def should_edit_relation(self, entity, rschema, role, rvid):
+ if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
+ fromeid=entity.eid))
+ or
+ (role == 'object' and not rschema.has_perm(self._cw, 'add',
+ toeid=entity.eid))):
+ return False
+ return True
+
+ def relation_form(self, lzone, value, form, renderer):
+ """xxx-reledit div (class=field)
+ +-xxx div (class="editableField")
+ | +-landing zone
+ +-xxx-value div
+ +-xxx-form div
+ """
+ w = self.w
+ divid = form.event_args['divid']
+ w(u'<div id="%s-reledit" class="field" '
+ u'onmouseout="addElementClass(jQuery(\'#%s\'), \'hidden\')" '
+ u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
+ % (divid, divid, divid))
+ w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
+ w(form.render(renderer=renderer))
+ w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
+ divid, xml_escape(self._onclick % form.event_args),
+ self._cw._(self._landingzonemsg)))
+ w(lzone)
+ w(u'</div>')
+ w(u'</div>')
- @property
- @cached
- def form(self):
- entity = self._entity()
- form = self._cw.vreg['forms'].select('edition', self._cw,
- entity=entity,
- formtype='inlined',
- form_renderer_id='inline',
- copy_nav_params=False,
- mainform=False,
- parent_form=self.pform,
- **self.cw_extra_kwargs)
- if self.pform is None:
- form.restore_previous_post(form.session_key())
- #assert form.parent_form
- self.add_hiddens(form, entity)
+ def _compute_best_vid(self, eschema, rschema, role):
+ dispctrl = _pvdc.etype_get(eschema, rschema, role)
+ if dispctrl.get('rvid'):
+ return dispctrl['rvid']
+ if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
+ return self._many_rvid
+ return self._one_rvid
+
+ def _build_landing_zone(self, lzone):
+ return lzone or self._defaultlandingzone % {
+ 'msg': xml_escape(self._cw._(self._landingzonemsg))}
+
+ def _build_renderer(self, entity, rtype, role):
+ return self._cw.vreg['formrenderers'].select(
+ 'base', self._cw, entity=entity, display_label=False,
+ display_help=False, table_class='',
+ button_bar_class='buttonbar', display_progress_div=False)
+
+ def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
+ extradata=None):
+ divid = '%s-%s-%s' % (rtype, role, entity.eid)
+ event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
+ 'reload' : dumps(reload), 'default' : default, 'role' : role, 'vid' : u'',
+ 'lzone' : lzone}
+ if extradata:
+ event_args.update(extradata)
+ return divid, event_args
+
+ def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
+ extradata=None, **formargs):
+ divid, event_args = self._build_args(entity, rtype, role, formid, default,
+ reload, lzone, extradata)
+ onsubmit = self._onsubmit % event_args
+ cancelclick = self._cancelclick % (entity.eid, rtype, divid)
+ form = self._cw.vreg['forms'].select(
+ formid, self._cw, entity=entity, domid='%s-form' % divid,
+ cssstyle='display: none', onsubmit=onsubmit, action='#',
+ form_buttons=[fw.SubmitButton(),
+ fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)],
+ **formargs)
+ form.event_args = event_args
return form
- def cell_call(self, row, col, i18nctx, **kwargs):
- """
- :param peid: the parent entity's eid hosting the inline form
- :param rtype: the relation bridging `etype` and `peid`
- :param role: the role played by the `peid` in the relation
- """
- entity = self._entity()
- divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (
- self.peid, self.rtype, entity.eid)
- self.render_form(i18nctx, divonclick=divonclick, **kwargs)
- def render_form(self, i18nctx, **kwargs):
- """fetch and render the form"""
- entity = self._entity()
- divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
- title = self.form_title(entity, i18nctx)
- removejs = self.removejs and self.removejs % (
- self.peid, self.rtype, entity.eid)
- countkey = '%s_count' % self.rtype
- try:
- self._cw.data[countkey] += 1
- except KeyError:
- self._cw.data[countkey] = 1
- self.w(self.form.render(
- divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
- counter=self._cw.data[countkey] , **kwargs))
-
- def form_title(self, entity, i18nctx):
- return self._cw.pgettext(i18nctx, entity.__regid__)
-
- def add_hiddens(self, form, entity):
- """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
- iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)
- # * str(self.rtype) in case it's a schema object
- # * neged_role() since role is the for parent entity, we want the role
- # of the inlined entity
- form.add_hidden(name=str(self.rtype), value=self.peid,
- role=neg_role(self.role), eidparam=True, id=iid)
-
- def keep_entity(self, form, entity):
- if not entity.has_eid():
- return True
- # are we regenerating form because of a validation error ?
- if form.form_previous_values:
- cdvalues = self._cw.list_form_param(eid_param(self.rtype, self.peid),
- form.form_previous_values)
- if unicode(entity.eid) not in cdvalues:
- return False
- return True
-
-
-class InlineEntityCreationFormView(InlineEntityEditionFormView):
- """
- :attr etype: the entity type being created in the inline form
+class AutoClickAndEditFormView(ClickAndEditFormView):
+ """same as ClickAndEditFormView but checking if the view *should* be applied
+ by checking uicfg configuration and composite relation property.
"""
- __regid__ = 'inline-creation'
- __select__ = (match_kwargs('peid', 'rtype')
- & specified_etype_implements('Any'))
-
- @property
- def removejs(self):
- entity = self._entity()
- card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role)
- # when one is adding an inline entity for a relation of a single card,
- # the 'add a new xxx' link disappears. If the user then cancel the addition,
- # we have to make this link appears back. This is done by giving add new link
- # id to removeInlineForm.
- if card not in '?1':
- return "removeInlineForm('%%s', '%%s', '%s', '%%s')" % self.role
- divid = "addNew%s%s%s:%s" % (
- self.etype, self.rtype, self.role, self.peid)
- return "removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')" % (
- self.role, divid)
+ __regid__ = 'reledit'
+ _onclick = (u"loadInlineEditionForm(%(eid)s, '%(rtype)s', '%(role)s', "
+ "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
- @cached
- def _entity(self):
- try:
- cls = self._cw.vreg['etypes'].etype_class(self.etype)
- except:
- self.w(self._cw._('no such entity type %s') % etype)
- return
- entity = cls(self._cw)
- entity.eid = self._cw.varmaker.next()
- return entity
-
- def call(self, i18nctx, **kwargs):
- self.render_form(i18nctx, **kwargs)
-
+ def should_edit_relation(self, entity, rschema, role, rvid):
+ eschema = entity.e_schema
+ rtype = str(rschema)
+ # XXX check autoform_section. what if 'generic'?
+ dispctrl = _pvdc.etype_get(eschema, rtype, role)
+ vid = dispctrl.get('vid', 'reledit')
+ if vid != 'reledit': # reledit explicitly disabled
+ return False
+ if eschema.rdef(rschema, role).composite == role:
+ return False
+ return super(AutoClickAndEditFormView, self).should_edit_relation(
+ entity, rschema, role, rvid)
-class InlineAddNewLinkView(InlineEntityCreationFormView):
- """
- :attr card: the cardinality of the relation according to role of `peid`
- """
- __regid__ = 'inline-addnew-link'
- __select__ = (match_kwargs('peid', 'rtype')
- & specified_etype_implements('Any'))
-
- _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
- form = None # no actual form wrapped
+ def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
+ extradata=None, **formargs):
+ _divid, event_args = self._build_args(entity, rtype, role, formid, default,
+ reload, lzone, extradata)
+ form = DummyForm()
+ form.event_args = event_args
+ return form
- def call(self, i18nctx, **kwargs):
- self._cw.set_varmaker()
- divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
- self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
- % divid)
- js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
- self.peid, self.etype, self.rtype, self.role, i18nctx)
- if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
- js = "toggleVisibility('%s'); %s" % (divid, js)
- __ = self._cw.pgettext
- self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
- % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
- self.w(u'</div>')
+ def _build_renderer(self, entity, rtype, role):
+ pass
+
--- a/web/views/editviews.py Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/editviews.py Wed Jan 27 09:53:48 2010 +0100
@@ -8,36 +8,16 @@
__docformat__ = "restructuredtext en"
_ = unicode
-from simplejson import dumps
-
from logilab.common.decorators import cached
from logilab.mtconverter import xml_escape
-from cubicweb import typed_eid, uilib
-from cubicweb.schema import display_name
+from cubicweb import typed_eid
from cubicweb.view import EntityView
from cubicweb.selectors import (one_line_rset, non_final_entity,
- match_search_state, match_form_params)
-from cubicweb.web import formwidgets as fw, formfields as ff
+ match_search_state)
from cubicweb.web.views import baseviews, linksearch_select_url
-def relation_id(eid, rtype, role, reid):
- """return an identifier for a relation between two entities"""
- if role == 'subject':
- return u'%s:%s:%s' % (eid, rtype, reid)
- return u'%s:%s:%s' % (reid, rtype, eid)
-
-def toggleable_relation_link(eid, nodeid, label='x'):
- """return javascript snippet to delete/undelete a relation between two
- entities
- """
- js = u"javascript: togglePendingDelete('%s', %s);" % (
- nodeid, xml_escape(dumps(eid)))
- return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
- js, nodeid, label)
-
-
class SearchForAssociationView(EntityView):
"""view called by the edition view when the user asks to search for
something to link to the edited eid
@@ -88,296 +68,6 @@
entity.view('outofcontext', w=self.w)
-def get_pending_inserts(req, eid=None):
- """shortcut to access req's pending_insert entry
-
- This is where are stored relations being added while editing
- an entity. This used to be stored in a temporary cookie.
- """
- pending = req.get_session_data('pending_insert') or ()
- return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
- if eid is None or eid in (subj, obj)]
-
-def get_pending_deletes(req, eid=None):
- """shortcut to access req's pending_delete entry
-
- This is where are stored relations being removed while editing
- an entity. This used to be stored in a temporary cookie.
- """
- pending = req.get_session_data('pending_delete') or ()
- return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
- if eid is None or eid in (subj, obj)]
-
-def parse_relations_descr(rdescr):
- """parse a string describing some relations, in the form
- subjeids:rtype:objeids
- where subjeids and objeids are eids separeted by a underscore
-
- return an iterator on (subject eid, relation type, object eid) found
- """
- for rstr in rdescr:
- subjs, rtype, objs = rstr.split(':')
- for subj in subjs.split('_'):
- for obj in objs.split('_'):
- yield typed_eid(subj), rtype, typed_eid(obj)
-
-def delete_relations(req, rdefs):
- """delete relations from the repository"""
- # FIXME convert to using the syntax subject:relation:eids
- execute = req.execute
- for subj, rtype, obj in parse_relations_descr(rdefs):
- rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
- execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
- req.set_message(req._('relations deleted'))
-
-def insert_relations(req, rdefs):
- """insert relations into the repository"""
- execute = req.execute
- for subj, rtype, obj in parse_relations_descr(rdefs):
- rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
- execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
-
-
-class GenericRelationsWidget(fw.FieldWidget):
-
- def render(self, form, field, renderer):
- stream = []
- w = stream.append
- req = form._cw
- _ = req._
- __ = _
- eid = form.edited_entity.eid
- w(u'<table id="relatedEntities">')
- for rschema, role, related in field.relations_table(form):
- # already linked entities
- if related:
- w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, role))
- w(u'<td>')
- w(u'<ul>')
- for viewparams in related:
- w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
- % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
- if not form.force_display and form.maxrelitems < len(related):
- link = (u'<span class="invisible">'
- '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
- '</span>' % _('view all'))
- w(u'<li class="invisible">%s</li>' % link)
- w(u'</ul>')
- w(u'</td>')
- w(u'</tr>')
- pendings = list(field.restore_pending_inserts(form))
- if not pendings:
- w(u'<tr><th> </th><td> </td></tr>')
- else:
- for row in pendings:
- # soon to be linked to entities
- w(u'<tr id="tr%s">' % row[1])
- w(u'<th>%s</th>' % row[3])
- w(u'<td>')
- w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
- (_('cancel this insert'), row[2]))
- w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
- % (row[1], row[4], xml_escape(row[5])))
- w(u'</td>')
- w(u'</tr>')
- w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
- w(u'<th class="labelCol">')
- w(u'<select id="relationSelector_%s" tabindex="%s" '
- 'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
- % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
- w(u'<option value="">%s</option>' % _('select a relation'))
- for i18nrtype, rschema, role in field.relations:
- # more entities to link to
- w(u'<option value="%s_%s">%s</option>' % (rschema, role, i18nrtype))
- w(u'</select>')
- w(u'</th>')
- w(u'<td id="unrelatedDivs_%s"></td>' % eid)
- w(u'</tr>')
- w(u'</table>')
- return '\n'.join(stream)
-
-
-class GenericRelationsField(ff.Field):
- widget = GenericRelationsWidget
-
- def __init__(self, relations, name='_cw_generic_field', **kwargs):
- assert relations
- kwargs['eidparam'] = True
- super(GenericRelationsField, self).__init__(name, **kwargs)
- self.relations = relations
-
- def process_posted(self, form):
- todelete = get_pending_deletes(form._cw)
- if todelete:
- delete_relations(form._cw, todelete)
- toinsert = get_pending_inserts(form._cw)
- if toinsert:
- insert_relations(form._cw, toinsert)
- return ()
-
- def relations_table(self, form):
- """yiels 3-tuples (rtype, role, related_list)
- where <related_list> itself a list of :
- - node_id (will be the entity element's DOM id)
- - appropriate javascript's togglePendingDelete() function call
- - status 'pendingdelete' or ''
- - oneline view of related entity
- """
- entity = form.edited_entity
- pending_deletes = get_pending_deletes(form._cw, entity.eid)
- for label, rschema, role in self.relations:
- related = []
- if entity.has_eid():
- rset = entity.related(rschema, role, limit=form.related_limit)
- if rschema.has_perm(form._cw, 'delete'):
- toggleable_rel_link_func = toggleable_relation_link
- else:
- toggleable_rel_link_func = lambda x, y, z: u''
- for row in xrange(rset.rowcount):
- nodeid = relation_id(entity.eid, rschema, role,
- rset[row][0])
- if nodeid in pending_deletes:
- status, label = u'pendingDelete', '+'
- else:
- status, label = u'', 'x'
- dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
- eview = form._cw.view('oneline', rset, row=row)
- related.append((nodeid, dellink, status, eview))
- yield (rschema, role, related)
-
- def restore_pending_inserts(self, form):
- """used to restore edition page as it was before clicking on
- 'search for <some entity type>'
- """
- entity = form.edited_entity
- pending_inserts = set(get_pending_inserts(form._cw, form.edited_entity.eid))
- for pendingid in pending_inserts:
- eidfrom, rtype, eidto = pendingid.split(':')
- if typed_eid(eidfrom) == entity.eid: # subject
- label = display_name(form._cw, rtype, 'subject',
- entity.__regid__)
- reid = eidto
- else:
- label = display_name(form._cw, rtype, 'object',
- entity.__regid__)
- reid = eidfrom
- jscall = "javascript: cancelPendingInsert('%s', 'tr', null, %s);" \
- % (pendingid, entity.eid)
- rset = form._cw.eid_rset(reid)
- eview = form._cw.view('text', rset, row=0)
- # XXX find a clean way to handle baskets
- if rset.description[0][0] == 'Basket':
- eview = '%s (%s)' % (eview, display_name(form._cw, 'Basket'))
- yield rtype, pendingid, jscall, label, reid, eview
-
-
-class UnrelatedDivs(EntityView):
- __regid__ = 'unrelateddivs'
- __select__ = match_form_params('relation')
-
- def cell_call(self, row, col):
- entity = self.cw_rset.get_entity(row, col)
- relname, role = self._cw.form.get('relation').rsplit('_', 1)
- rschema = self._cw.vreg.schema.rschema(relname)
- hidden = 'hidden' in self._cw.form
- is_cell = 'is_cell' in self._cw.form
- self.w(self.build_unrelated_select_div(entity, rschema, role,
- is_cell=is_cell, hidden=hidden))
-
- def build_unrelated_select_div(self, entity, rschema, role,
- is_cell=False, hidden=True):
- options = []
- divid = 'div%s_%s_%s' % (rschema.type, role, entity.eid)
- selectid = 'select%s_%s_%s' % (rschema.type, role, entity.eid)
- if rschema.symetric or role == 'subject':
- targettypes = rschema.objects(entity.e_schema)
- etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
- else:
- targettypes = rschema.subjects(entity.e_schema)
- etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
- etypes = uilib.cut(etypes, self._cw.property_value('navigation.short-line-size'))
- options.append('<option>%s %s</option>' % (self._cw._('select a'), etypes))
- options += self._get_select_options(entity, rschema, role)
- options += self._get_search_options(entity, rschema, role, targettypes)
- if 'Basket' in self._cw.vreg.schema: # XXX
- options += self._get_basket_options(entity, rschema, role, targettypes)
- relname, role = self._cw.form.get('relation').rsplit('_', 1)
- return u"""\
-<div class="%s" id="%s">
- <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
- %s
- </select>
-</div>
-""" % (hidden and 'hidden' or '', divid, selectid,
- xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
- '\n'.join(options))
-
- def _get_select_options(self, entity, rschema, role):
- """add options to search among all entities of each possible type"""
- options = []
- pending_inserts = get_pending_inserts(self._cw, entity.eid)
- rtype = rschema.type
- form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
- field = form.field_by_name(rschema, role, entity.e_schema)
- limit = self._cw.property_value('navigation.combobox-limit')
- for eview, reid in field.choices(form, limit): # XXX expect 'limit' arg on choices
- if reid is None:
- if eview: # skip blank value
- options.append('<option class="separator">-- %s --</option>'
- % xml_escape(eview))
- elif reid != ff.INTERNAL_FIELD_VALUE:
- optionid = relation_id(entity.eid, rtype, role, 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>' %
- (optionid, reid, xml_escape(eview)))
- return options
-
- def _get_search_options(self, entity, rschema, role, targettypes):
- """add options to search among all entities of each possible type"""
- options = []
- _ = self._cw._
- for eschema in targettypes:
- mode = '%s:%s:%s:%s' % (role, entity.eid, rschema.type, eschema)
- url = self._cw.build_url(entity.rest_path(), vid='search-associate',
- __mode=mode)
- options.append((eschema.display_name(self._cw),
- '<option value="%s">%s %s</option>' % (
- xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
- return [o for l, o in sorted(options)]
-
- # XXX move this out
- def _get_basket_options(self, entity, rschema, role, targettypes):
- options = []
- rtype = rschema.type
- _ = self._cw._
- for basketeid, basketname in self._get_basket_links(self._cw.user.eid,
- role, targettypes):
- optionid = relation_id(entity.eid, rtype, role, basketeid)
- options.append('<option id="%s" value="%s">%s %s</option>' % (
- optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
- return options
-
- def _get_basket_links(self, ueid, role, targettypes):
- targettypes = set(targettypes)
- for basketeid, basketname, elements in self._get_basket_info(ueid):
- baskettypes = elements.column_types(0)
- # if every elements in the basket can be attached to the
- # edited entity
- if baskettypes & targettypes:
- yield basketeid, basketname
-
- def _get_basket_info(self, ueid):
- basketref = []
- basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
- basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x')
- for result in basketresultset:
- basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
- rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x')
- basketref.append((result[0], result[1], rset))
- return basketref
-
-
class ComboboxView(EntityView):
"""the view used in combobox (unrelated entities)