# HG changeset patch # User Sylvain Thénault # Date 1263978854 -3600 # Node ID 149b33d22d8775ae904ab38c8d5dbe32e21598f4 # Parent 74c1597f8a8234eb84b08d6ab2f9cf7bdac8975e# Parent 1084aaa53a25041db864a6f67eaa000ef4983fd5 merge diff -r 1084aaa53a25 -r 149b33d22d87 doc/book/en/development/devweb/internationalization.rst --- a/doc/book/en/development/devweb/internationalization.rst Wed Jan 20 10:05:10 2010 +0100 +++ b/doc/book/en/development/devweb/internationalization.rst Wed Jan 20 10:14:14 2010 +0100 @@ -87,15 +87,20 @@ May generate the following message :: - creating EntityB (EntityA %(linkto)s relation_a2b EntityB) + add Execution has_export File subject This message will be used in views of ``EntityA`` for creation of a new ``EntityB`` with a preset relation ``relation_a2b`` between the current ``EntityA`` and the new ``EntityB``. The opposite message :: - creating EntityA (EntityA relation_a2b %(linkto)s EntityA) + add Execution has_export File object -Is used for similar creation of an ``EntityA`` from a view of ``EntityB``. +Is used for similar creation of an ``EntityA`` from a view of ``EntityB``. The +title of they respective creation form will be :: + + creating EntityB (EntityA %(linkto)s relation_a2b EntityB) + + creating EntityA (EntityA relation_a2b %(linkto)s EntityA) In the translated string you can use ``%(linkto)s`` for reference to the source ``entity``. diff -r 1084aaa53a25 -r 149b33d22d87 doc/book/en/development/webstdlib/autoform.rst --- a/doc/book/en/development/webstdlib/autoform.rst Wed Jan 20 10:05:10 2010 +0100 +++ b/doc/book/en/development/webstdlib/autoform.rst Wed Jan 20 10:14:14 2010 +0100 @@ -1,8 +1,25 @@ The automatic entity form (:mod:`cubicweb.web.views.autoform`) --------------------------------------------------------------- +Tags declaration +~~~~~~~~~~~~~~~~~~~~ + It is possible to manage attributes/relations in the simple or multiple -editing form thanks to the following *rtags*: +editing form thanks of the methods bellow :: + + uicfg.autoform_section.tag_subject_of(, tag) + uicfg.autoform_section.tag_object_of(, tag) + uicfg.autoform_field.tag_attribute(, tag) + +Where ```` is a three elements tuple ``(Subject Entity Type, +relation_type, Object Entity Type)``. ```` is a two elements tuple +``(Entity Type, Attribut Name)``. Wildcard ``*`` could be used in place of +``Entity Type`` + +Possible tags are detailled below + +Simple Tags +~~~~~~~~~~~~~~~~~~~~ * `primary`, indicates that an attribute or a relation has to be inserted **in the simple or multiple editing forms**. In the case of @@ -29,3 +46,24 @@ If necessary, it is possible to overwrite the method `relation_category(rtype, x='subject')` to dynamically compute a relation editing category. + + +Advanced Tags +~~~~~~~~~~~~~~~~~~~~ + +Tag can also reference a custom Field crafted with the help of +``cubicweb.web.formfields`` and ``cubicweb.web.formwidget``. In the example +bellow, the field ``path`` of ``ExecData`` entities will be done with a standard +file input dialogue :: + + from cubicweb.web import uicfg, formfields, formwidgets + + uicfg.autoform_field.tag_attribute(('Execdata', 'path'), + formfields.FileField(name='path', widget=formwidgets.FileInput())) + + + + + + + diff -r 1084aaa53a25 -r 149b33d22d87 web/controller.py --- a/web/controller.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/controller.py Wed Jan 20 10:14:14 2010 +0100 @@ -35,19 +35,6 @@ params[navparam] = form[redirectparam] return params -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 append_url_params(url, params): """append raw parameters to the url. Given parameters, if any, are expected to be already url-quoted. @@ -137,22 +124,6 @@ else: self._cw.set_message(self._cw._('entity deleted')) - def delete_relations(self, rdefs): - """delete relations from the repository""" - # FIXME convert to using the syntax subject:relation:eids - execute = self._cw.execute - for subj, rtype, obj in rdefs: - rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype - execute(rql, {'x': subj, 'y': obj}, ('x', 'y')) - self._cw.set_message(self._cw._('relations deleted')) - - def insert_relations(self, rdefs): - """insert relations into the repository""" - execute = self._cw.execute - for subj, rtype, obj in rdefs: - rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype - execute(rql, {'x': subj, 'y': obj}, ('x', 'y')) - def reset(self): """reset form parameters and redirect to a view determinated by given diff -r 1084aaa53a25 -r 149b33d22d87 web/data/cubicweb.css --- a/web/data/cubicweb.css Wed Jan 20 10:05:10 2010 +0100 +++ b/web/data/cubicweb.css Wed Jan 20 10:14:14 2010 +0100 @@ -841,7 +841,7 @@ font-weight: bold; } -input.validateButton { +.validateButton { margin: 1em 1em 0px 0px; border: 1px solid #edecd2; border-color:#edecd2 #cfceb7 #cfceb7 #edecd2; diff -r 1084aaa53a25 -r 149b33d22d87 web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Wed Jan 20 10:05:10 2010 +0100 +++ b/web/data/cubicweb.edition.js Wed Jan 20 10:14:14 2010 +0100 @@ -399,14 +399,14 @@ /* unfreeze form buttons when the validation process is over*/ function unfreezeFormButtons(formid) { jQuery('#progress').hide(); - jQuery('#' + formid + ' input.validateButton').removeAttr('disabled'); + jQuery('#' + formid + ' .validateButton').removeAttr('disabled'); return true; } /* disable form buttons while the validation is being done */ function freezeFormButtons(formid) { jQuery('#progress').show(); - jQuery('#' + formid + ' input.validateButton').attr('disabled', 'disabled'); + jQuery('#' + formid + ' .validateButton').attr('disabled', 'disabled'); return true; } diff -r 1084aaa53a25 -r 149b33d22d87 web/data/cubicweb.form.css --- a/web/data/cubicweb.form.css Wed Jan 20 10:05:10 2010 +0100 +++ b/web/data/cubicweb.form.css Wed Jan 20 10:14:14 2010 +0100 @@ -108,7 +108,7 @@ background: url("required.png") 100% 50% no-repeat; } -.entityForm input.validateButton { +.entityForm .validateButton { margin: 5px 10px 5px 0px; } @@ -226,7 +226,7 @@ cursor: default; } -input.validateButton { +.validateButton { margin: 1em 1em 0px 0px; border: 1px solid #edecd2; border-color:#edecd2 #cfceb7 #cfceb7 #edecd2; diff -r 1084aaa53a25 -r 149b33d22d87 web/data/cubicweb.preferences.js --- a/web/data/cubicweb.preferences.js Wed Jan 20 10:05:10 2010 +0100 +++ b/web/data/cubicweb.preferences.js Wed Jan 20 10:14:14 2010 +0100 @@ -134,7 +134,7 @@ jQuery('form').each(function() { var form = jQuery(this); //freezeFormButtons(form.attr('id')); - form.find('input.validateButton').attr('disabled', 'disabled'); + form.find('.validateButton').attr('disabled', 'disabled'); form.find('input[type=text]').keyup(function(){ checkValues(form); }); diff -r 1084aaa53a25 -r 149b33d22d87 web/request.py --- a/web/request.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/request.py Wed Jan 20 10:14:14 2010 +0100 @@ -386,37 +386,7 @@ raise RequestError(self._('missing parameters for entity %s') % eid) return params - def get_pending_operations(self, entity, relname, role): - operations = {'insert' : [], 'delete' : []} - for optype in ('insert', 'delete'): - data = self.get_session_data('pending_%s' % optype) or () - for eidfrom, rel, eidto in data: - if relname == rel: - if role == 'subject' and entity.eid == eidfrom: - operations[optype].append(eidto) - if role == 'object' and entity.eid == eidto: - operations[optype].append(eidfrom) - return operations - - def get_pending_inserts(self, 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 = self.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(self, 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 = self.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)] + # XXX this should go to the GenericRelationsField. missing edition cancel protocol. def remove_pending_operations(self): """shortcut to clear req's pending_{delete,insert} entries diff -r 1084aaa53a25 -r 149b33d22d87 web/test/unittest_application.py --- a/web/test/unittest_application.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/test/unittest_application.py Wed Jan 20 10:14:14 2010 +0100 @@ -184,9 +184,9 @@ values = forminfo['values'] self.assertEquals(values['login-subject:'+eid], '') self.assertEquals(values['eid'], eid) - errors = forminfo['errors'] - self.assertEquals(errors.entity, user.eid) - self.assertEquals(errors.errors['login'], 'required attribute') + error = forminfo['error'] + self.assertEquals(error.entity, user.eid) + self.assertEquals(error.errors['login'], 'required attribute') def test_validation_error_dont_loose_subentity_data(self): @@ -212,9 +212,9 @@ path, params = self.expect_redirect(lambda x: self.app_publish(x, 'edit'), req) forminfo = req.get_session_data('view?vid=edition...') self.assertEquals(set(forminfo['eidmap']), set('XY')) - self.assertEquals(forminfo['errors'].entity, forminfo['eidmap']['X']) - self.assertEquals(forminfo['errors'].errors, {'login': 'required attribute', - 'upassword': 'required attribute'}) + self.assertEquals(forminfo['error'].entity, forminfo['eidmap']['X']) + self.assertEquals(forminfo['error'].errors, {'login': 'required attribute', + 'upassword': 'required attribute'}) self.assertEquals(forminfo['values'], form) def _test_cleaned(self, kwargs, injected, cleaned): diff -r 1084aaa53a25 -r 149b33d22d87 web/views/autoform.py --- a/web/views/autoform.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/views/autoform.py Wed Jan 20 10:14:14 2010 +0100 @@ -9,12 +9,12 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.decorators import cached +from logilab.common.decorators import cached, iclassmethod from cubicweb import typed_eid from cubicweb.web import stdmsgs, uicfg from cubicweb.web import form, formwidgets as fwdgs -from cubicweb.web.views import forms, editforms +from cubicweb.web.views import forms, editforms, editviews _afs = uicfg.autoform_section @@ -44,6 +44,31 @@ # which relations should be edited display_fields = None + def _generic_relations_field(self): + try: + srels_by_cat = self.srelations_by_category('generic', 'add', strict=True) + warn('[3.6] %s: srelations_by_category is deprecated, use uicfg or ' + 'override editable_relations instead' % classid(form), + DeprecationWarning) + except AttributeError: + srels_by_cat = self.editable_relations() + if not srels_by_cat: + raise form.FieldNotFound('_cw_generic_field') + return editviews.GenericRelationsField(self.editable_relations()) + + @iclassmethod + def field_by_name(cls_or_self, name, role=None, eschema=None): + """return field with the given name and role. If field is not explicitly + defined for the form but `eclass` is specified, guess_field will be + called. + """ + try: + return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role, eschema) + except form.FieldNotFound: + if name == '_cw_generic_field' and not isinstance(cls_or_self, type): + return cls_or_self._generic_relations_field() + raise + # base automatic entity form methods ####################################### def __init__(self, *args, **kwargs): @@ -64,6 +89,12 @@ except form.FieldNotFound: # meta attribute such as _format continue + if self.formtype == 'main' and entity.has_eid(): + try: + self.fields.append(self.field_by_name('_cw_generic_field')) + except form.FieldNotFound: + # no editable relation + pass self.maxrelitems = self._cw.property_value('navigation.related-limit') self.force_display = bool(self._cw.form.get('__force_display')) fnum = len(self.fields) @@ -163,63 +194,6 @@ # generic relations modifier ############################################### - def relations_table(self): - """yiels 3-tuples (rtype, target, 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 = self.edited_entity - pending_deletes = self._cw.get_pending_deletes(entity.eid) - for label, rschema, role in self.editable_relations(): - relatedrset = entity.related(rschema, role, limit=self.related_limit) - if rschema.has_perm(self._cw, 'delete'): - toggleable_rel_link_func = editforms.toggleable_relation_link - else: - toggleable_rel_link_func = lambda x, y, z: u'' - related = [] - for row in xrange(relatedrset.rowcount): - nodeid = editforms.relation_id(entity.eid, rschema, role, - relatedrset[row][0]) - if nodeid in pending_deletes: - status = u'pendingDelete' - label = '+' - else: - status = u'' - label = 'x' - dellink = toggleable_rel_link_func(entity.eid, nodeid, label) - eview = self._cw.view('oneline', relatedrset, row=row) - related.append((nodeid, dellink, status, eview)) - yield (rschema, role, related) - - def restore_pending_inserts(self, cell=False): - """used to restore edition page as it was before clicking on - 'search for ' - """ - eid = self.edited_entity.eid - cell = cell and "div_insert_" or "tr" - pending_inserts = set(self._cw.get_pending_inserts(eid)) - for pendingid in pending_inserts: - eidfrom, rtype, eidto = pendingid.split(':') - if typed_eid(eidfrom) == eid: # subject - label = display_name(self._cw, rtype, 'subject', - self.edited_entity.__regid__) - reid = eidto - else: - label = display_name(self._cw, rtype, 'object', - self.edited_entity.__regid__) - reid = eidfrom - jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \ - % (pendingid, cell, eid) - rset = self._cw.eid_rset(reid) - eview = self._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(self._cw, 'Basket')) - yield rtype, pendingid, jscall, label, reid, eview - # inlined forms support #################################################### @cached diff -r 1084aaa53a25 -r 149b33d22d87 web/views/basetemplates.py --- a/web/views/basetemplates.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/views/basetemplates.py Wed Jan 20 10:14:14 2010 +0100 @@ -266,7 +266,10 @@ self.w(u'\n') if can_do_pdf_conversion(): - from xml.etree.cElementTree import ElementTree + try: + from xml.etree.cElementTree import ElementTree + except ImportError: #python2.4 + from elementtree import ElementTree from subprocess import Popen as sub from StringIO import StringIO from tempfile import NamedTemporaryFile diff -r 1084aaa53a25 -r 149b33d22d87 web/views/editcontroller.py --- a/web/views/editcontroller.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/views/editcontroller.py Wed Jan 20 10:14:14 2010 +0100 @@ -13,7 +13,7 @@ from cubicweb import Binary, ValidationError, typed_eid from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError -from cubicweb.web.controller import parse_relations_descr +from cubicweb.web.views.editviews import delete_relations, insert_relations from cubicweb.web.views.basecontrollers import ViewController @@ -73,8 +73,6 @@ # no specific action, generic edition self._to_create = req.data['eidmap'] = {} self._pending_fields = req.data['pendingfields'] = set() - todelete = self._cw.get_pending_deletes() - toinsert = self._cw.get_pending_inserts() try: methodname = req.form.pop('__method', None) for eid in req.edited_eids(): @@ -88,7 +86,7 @@ except (RequestError, NothingToEdit), ex: if '__linkto' in req.form and 'eid' in req.form: self.execute_linkto() - elif not ('__delete' in req.form or '__insert' in req.form or todelete or toinsert): + elif not ('__delete' in req.form or '__insert' in req.form): raise ValidationError(None, {None: unicode(ex)}) # handle relations in newly created entities if self._pending_fields: @@ -99,13 +97,15 @@ self._cw.execute(*querydef) # XXX this processes *all* pending operations of *all* entities if req.form.has_key('__delete'): - todelete += req.list_form_param('__delete', req.form, pop=True) - if todelete: - self.delete_relations(parse_relations_descr(todelete)) + todelete = req.list_form_param('__delete', req.form, pop=True) + if todelete: + 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: - self.insert_relations(parse_relations_descr(toinsert)) + if toinsert: + 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) @@ -171,13 +171,11 @@ if is_main_entity: self.notify_edited(entity) if formparams.has_key('__delete'): + # XXX deprecate? todelete = self._cw.list_form_param('__delete', formparams, pop=True) - self.delete_relations(parse_relations_descr(todelete)) + delete_relations(self._cw, todelete) if formparams.has_key('__cloned_eid'): entity.copy_relations(typed_eid(formparams['__cloned_eid'])) - if formparams.has_key('__insert'): - toinsert = self._cw.list_form_param('__insert', formparams, pop=True) - self.insert_relations(parse_relations_descr(toinsert)) if is_main_entity: # only execute linkto for the main entity self.execute_linkto(entity.eid) return eid diff -r 1084aaa53a25 -r 149b33d22d87 web/views/editforms.py --- a/web/views/editforms.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/views/editforms.py Wed Jan 20 10:14:14 2010 +0100 @@ -21,8 +21,7 @@ specified_etype_implements, yes) from cubicweb.view import EntityView from cubicweb import tags -from cubicweb.web import stdmsgs, eid_param -from cubicweb.web import uicfg +from cubicweb.web import uicfg, stdmsgs, eid_param from cubicweb.web.form import FormViewMixIn, FieldNotFound from cubicweb.web.formfields import guess_field from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton @@ -30,21 +29,6 @@ _pvdc = uicfg.primaryview_display_ctrl -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 DeleteConfForm(forms.CompositeForm): __regid__ = 'deleteconf' diff -r 1084aaa53a25 -r 149b33d22d87 web/views/editviews.py --- a/web/views/editviews.py Wed Jan 20 10:05:10 2010 +0100 +++ b/web/views/editviews.py Wed Jan 20 10:14:14 2010 +0100 @@ -13,14 +13,29 @@ from logilab.common.decorators import cached from logilab.mtconverter import xml_escape -from cubicweb import typed_eid +from cubicweb import typed_eid, uilib +from cubicweb.schema import display_name from cubicweb.view import EntityView from cubicweb.selectors import (one_line_rset, non_final_entity, match_search_state, match_form_params) -from cubicweb.uilib import cut -from cubicweb.web.views import linksearch_select_url -from cubicweb.web.views.editforms import relation_id -from cubicweb.web.views.baseviews import FinalView +from cubicweb.web import formwidgets as fw, formfields as ff +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): @@ -73,37 +88,226 @@ 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._ + __ = _ + label = u'%s :' % __('This %s' % form.edited_entity.e_schema).capitalize() + eid = form.edited_entity.eid + w(u'
') + w(u'%s' % label) + 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'' % self._cw._('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'
') + 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 + self.label = None + + 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, target = self._cw.form.get('relation').rsplit('_', 1) + 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, target, + self.w(self.build_unrelated_select_div(entity, rschema, role, is_cell=is_cell, hidden=hidden)) - def build_unrelated_select_div(self, entity, rschema, target, + def build_unrelated_select_div(self, entity, rschema, role, is_cell=False, hidden=True): options = [] - divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid) - selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid) - if rschema.symetric or target == 'subject': + 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 = cut(etypes, self._cw.property_value('navigation.short-line-size')) + 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, target) - options += self._get_search_options(entity, rschema, target, targettypes) + 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, target, targettypes) - relname, target = self._cw.form.get('relation').rsplit('_', 1) + options += self._get_basket_options(entity, rschema, role, targettypes) + relname, role = self._cw.form.get('relation').rsplit('_', 1) return u"""\
' - % (eid, req.next_tabindex(), xml_escape(dumps(eid)))) - w(u'' % _('select a relation')) - for i18nrtype, rschema, target in srels_by_cat: - # more entities to link to - w(u'' % (rschema, target, i18nrtype)) - w(u'') - w(u'') - w(u'' % eid) - w(u'') - w(u'') - w(u'') - # NOTE: should_* and display_* method extracted and moved to the form to # ease overriding