# HG changeset patch # User Sylvain Thénault # Date 1264582428 -3600 # Node ID 4aacd6492ef4af2e084eef7182e0f3e70e9085b3 # Parent cf8842b693799d2b726f482ae115328272fca8d9 reorganize code: move everything related to inlined forms and generic relation from editviews/editforms where there are used, eg in autoforms diff -r cf8842b69379 -r 4aacd6492ef4 web/views/autoform.py --- 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'
%s
' % ( + 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'
' + % 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'+ %s.' + % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype))) + self.w(u'
') + + +# 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'[%s]' % ( + 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'') + for rschema, role, related in field.relations_table(form): + # already linked entities + if related: + w(u'' % rschema.display_name(req, role)) + w(u'') + w(u'') + pendings = list(field.restore_pending_inserts(form)) + if not pendings: + w(u'') + else: + for row in pendings: + # soon to be linked to entities + w(u'' % row[1]) + w(u'' % row[3]) + w(u'') + w(u'') + w(u'' % eid) + w(u'') + w(u'' % eid) + w(u'') + w(u'
%s') + w(u'
    ') + for viewparams in related: + w(u'' + % (viewparams[1], viewparams[0], viewparams[2], viewparams[3])) + if not form.force_display and form.maxrelitems < len(related): + link = (u'' % _('view all')) + w(u'' % link) + w(u'
') + w(u'
  
%s') + w(u'[x]' % + (_('cancel this insert'), row[2])) + w(u'%s' + % (row[1], row[4], xml_escape(row[5]))) + w(u'
') + w(u'') + w(u'
') + 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 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 ' + """ + 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('' % (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"""\ +
+ +
+""" % (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('' + % 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('' % + (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), + '' % ( + 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('' % ( + 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 _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 ############ diff -r cf8842b69379 -r 4aacd6492ef4 web/views/editcontroller.py --- 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 diff -r cf8842b69379 -r 4aacd6492ef4 web/views/editforms.py --- 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'') - _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._('')) - 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'
' - % (divid, divid, divid)) - w(u'
%s
' % (divid, value)) - w(form.render(renderer=renderer)) - w(u'') - w(u'
') - - 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'
%s
' % ( - 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'') + _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._('')) + 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'
' + % (divid, divid, divid)) + w(u'
%s
' % (divid, value)) + w(form.render(renderer=renderer)) + w(u'') + w(u'
') - @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'
' - % 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'+ %s.' - % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype))) - self.w(u'
') + def _build_renderer(self, entity, rtype, role): + pass + diff -r cf8842b69379 -r 4aacd6492ef4 web/views/editviews.py --- 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'[%s]' % ( - 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'') - for rschema, role, related in field.relations_table(form): - # already linked entities - if related: - w(u'' % rschema.display_name(req, role)) - w(u'') - w(u'') - pendings = list(field.restore_pending_inserts(form)) - if not pendings: - w(u'') - else: - for row in pendings: - # soon to be linked to entities - w(u'' % row[1]) - w(u'' % row[3]) - w(u'') - w(u'') - w(u'' % eid) - w(u'') - w(u'' % eid) - w(u'') - w(u'
%s') - w(u'
    ') - for viewparams in related: - w(u'' - % (viewparams[1], viewparams[0], viewparams[2], viewparams[3])) - if not form.force_display and form.maxrelitems < len(related): - link = (u'' % _('view all')) - w(u'' % link) - w(u'
') - w(u'
  
%s') - w(u'[x]' % - (_('cancel this insert'), row[2])) - w(u'%s' - % (row[1], row[4], xml_escape(row[5]))) - w(u'
') - w(u'') - w(u'
') - 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 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 ' - """ - 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('' % (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"""\ -
- -
-""" % (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('' - % 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('' % - (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), - '' % ( - 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('' % ( - 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)