--- 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``.
--- 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(<relation>, tag)
+ uicfg.autoform_section.tag_object_of(<relation>, tag)
+ uicfg.autoform_field.tag_attribute(<attribut_def>, tag)
+
+Where ``<relation>`` is a three elements tuple ``(Subject Entity Type,
+relation_type, Object Entity Type)``. ``<attribut_def>`` 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()))
+
+
+
+
+
+
+
--- 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
--- 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;
--- 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;
}
--- 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;
--- 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);
});
--- 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
--- 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):
--- 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 <attr>_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 <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 = 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 <some entity type>'
- """
- 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
--- 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'</tr></table>\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
--- 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
--- 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'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
- js, nodeid, label)
-
class DeleteConfForm(forms.CompositeForm):
__regid__ = 'deleteconf'
--- 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'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
+ 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'<fieldset class="subentity">')
+ w(u'<legend class="iformTitle">%s</legend>' % label)
+ 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>' % self._cw._('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>')
+ w(u'</fieldset>')
+ 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 <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, 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('<option>%s %s</option>' % (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"""\
<div class="%s" id="%s">
<select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
@@ -114,32 +318,33 @@
xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
'\n'.join(options))
- def _get_select_options(self, entity, rschema, target):
+ def _get_select_options(self, entity, rschema, role):
"""add options to search among all entities of each possible type"""
options = []
- pending_inserts = self._cw.get_pending_inserts(entity.eid)
+ 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, target, entity.e_schema)
+ 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:
- options.append('<option class="separator">-- %s --</option>'
- % xml_escape(eview))
+ if eview: # skip blank value
+ options.append('<option class="separator">-- %s --</option>'
+ % xml_escape(eview))
else:
- optionid = relation_id(entity.eid, rtype, target, reid)
+ 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, target, targettypes):
+ 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' % (target, entity.eid, rschema.type, eschema)
+ 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),
@@ -147,18 +352,18 @@
xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
return [o for l, o in sorted(options)]
- def _get_basket_options(self, entity, rschema, target, targettypes):
+ 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,
- target, targettypes):
- optionid = relation_id(entity.eid, rtype, target, basketeid)
+ 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, target, targettypes):
+ 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)
@@ -193,7 +398,7 @@
self.wview('textoutofcontext', self.cw_rset, row=row, col=col)
-class EditableFinalView(FinalView):
+class EditableFinalView(baseviews.FinalView):
"""same as FinalView but enables inplace-edition when possible"""
__regid__ = 'editable-final'
--- a/web/views/formrenderers.py Wed Jan 20 10:05:10 2010 +0100
+++ b/web/views/formrenderers.py Wed Jan 20 10:14:14 2010 +0100
@@ -1,7 +1,7 @@
"""form renderers, responsible to layout a form to html
:organization: Logilab
-:copyright: 2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
+:copyright: 2009-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
"""
@@ -205,14 +205,15 @@
w(u'<table class="%s">' % self.table_class)
for field in fields:
w(u'<tr class="%s_%s_row">' % (field.name, field.role))
- if self.display_label:
+ if self.display_label and field.label is not None:
w(u'<th class="labelCol">%s</th>' % self.render_label(form, field))
+ w('<td')
+ if field.label is None:
+ w(' colspan="2"')
error = form.field_error(field)
if error:
- w(u'<td class="error">')
- self.render_error(w, error)
- else:
- w(u'<td>')
+ w(u' class="error"')
+ w(u'>')
w(field.render(form, self))
if self.display_help:
w(self.render_help(form, field))
@@ -392,72 +393,6 @@
</table>""" % tuple(button.render(form) for button in form.form_buttons))
else:
super(EntityFormRenderer, self).render_buttons(w, form)
-
- def relations_form(self, w, form):
- try:
- srels_by_cat = form.srelations_by_category('generic', 'add', strict=True)
- warn('[3.6] %s: srelations_by_category is deprecated, override '
- 'editable_relations instead' % classid(form), DeprecationWarning)
- except AttributeError:
- srels_by_cat = form.editable_relations()
- if not srels_by_cat:
- return u''
- req = self._cw
- _ = req._
- __ = _
- label = u'%s :' % __('This %s' % form.edited_entity.e_schema).capitalize()
- eid = form.edited_entity.eid
- w(u'<fieldset class="subentity">')
- w(u'<legend class="iformTitle">%s</legend>' % label)
- w(u'<table id="relatedEntities">')
- for rschema, target, related in form.relations_table():
- # already linked entities
- if related:
- w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, target))
- 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>' % self._cw._('view all'))
- w(u'<li class="invisible">%s</li>' % link)
- w(u'</ul>')
- w(u'</td>')
- w(u'</tr>')
- pendings = list(form.restore_pending_inserts())
- 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, target in srels_by_cat:
- # more entities to link to
- w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
- w(u'</select>')
- w(u'</th>')
- w(u'<td id="unrelatedDivs_%s"></td>' % eid)
- w(u'</tr>')
- w(u'</table>')
- w(u'</fieldset>')
-
# NOTE: should_* and display_* method extracted and moved to the form to
# ease overriding