# HG changeset patch # User Sylvain Thénault # Date 1254232724 -7200 # Node ID a3431f4e2f40d42564a240de4407504cfb0f7ea3 # Parent c0c7a944c00d6e6e00815bb1718eb32f28fa867a# Parent 16880e7ee3fa5b9e70018b5cc36c600c2d5382d8 backport stable branch diff -r c0c7a944c00d -r a3431f4e2f40 selectors.py --- a/selectors.py Mon Sep 28 16:39:10 2009 +0200 +++ b/selectors.py Tue Sep 29 15:58:44 2009 +0200 @@ -150,8 +150,8 @@ class ImplementsMixIn(object): """mix-in class for selectors checking implemented interfaces of something """ - def __init__(self, *expected_ifaces): - super(ImplementsMixIn, self).__init__() + def __init__(self, *expected_ifaces, **kwargs): + super(ImplementsMixIn, self).__init__(**kwargs) self.expected_ifaces = expected_ifaces def __str__(self): @@ -186,8 +186,9 @@ - `once_is_enough` is False, in which case if score_class return 0, 0 is returned """ - def __init__(self, once_is_enough=False): + def __init__(self, once_is_enough=False, accept_none=True): self.once_is_enough = once_is_enough + self.accept_none = accept_none @lltrace def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): @@ -195,9 +196,12 @@ return 0 score = 0 if row is None: + if not self.accept_none: + if any(rset[i][col] is None for i in xrange(len(rset))): + return 0 for etype in rset.column_types(col): if etype is None: # outer join - continue + return 0 escore = self.score(cls, req, etype) if not escore and not self.once_is_enough: return 0 diff -r c0c7a944c00d -r a3431f4e2f40 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Mon Sep 28 16:39:10 2009 +0200 +++ b/server/test/unittest_rql2sql.py Tue Sep 29 15:58:44 2009 +0200 @@ -583,17 +583,13 @@ FROM entities AS X WHERE X.type='Note' AND X.eid>12"""), - ('Any X, T WHERE X eid > 12, X title T', + ('Any X, T WHERE X eid > 12, X title T, X is IN (Bookmark, Card)', """SELECT X.cw_eid, X.cw_title FROM cw_Bookmark AS X WHERE X.cw_eid>12 UNION ALL SELECT X.cw_eid, X.cw_title FROM cw_Card AS X -WHERE X.cw_eid>12 -UNION ALL -SELECT X.cw_eid, X.cw_title -FROM cw_EmailThread AS X WHERE X.cw_eid>12"""), ('Any X', @@ -754,12 +750,10 @@ FROM cw_Note AS X WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,cw_CWUser AS Y WHERE rel_evaluee0.eid_from=Y.cw_eid AND rel_evaluee0.eid_to=X.cw_eid)'''), - ('Any X,T WHERE X title T, NOT X is Bookmark', - '''SELECT X.cw_eid, X.cw_title -FROM cw_Card AS X -UNION ALL -SELECT X.cw_eid, X.cw_title -FROM cw_EmailThread AS X'''), + ('Any X,RT WHERE X relation_type RT, NOT X is CWAttribute', + '''SELECT X.cw_eid, X.cw_relation_type +FROM cw_CWRelation AS X +WHERE X.cw_relation_type IS NOT NULL'''), ('Any K,V WHERE P is CWProperty, P pkey K, P value V, NOT P for_user U', '''SELECT P.cw_pkey, P.cw_value @@ -774,7 +768,17 @@ SELECT S.cw_eid FROM cw_State AS S WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid)'''), - ] + + ('Any S WHERE NOT(X in_state S, S name "somename"), X is CWUser', + '''SELECT S.cw_eid +FROM cw_State AS S +WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid AND S.cw_name=somename)'''), + +# XXXFIXME fail +# ('Any X,RT WHERE X relation_type RT?, NOT X is CWAttribute', +# '''SELECT X.cw_eid, X.cw_relation_type +# FROM cw_CWRelation AS X'''), +] OUTER_JOIN = [ ('Any X,S WHERE X travaille S?', diff -r c0c7a944c00d -r a3431f4e2f40 web/form.py --- a/web/form.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/form.py Tue Sep 29 15:58:44 2009 +0200 @@ -7,6 +7,8 @@ """ __docformat__ = "restructuredtext en" +from logilab.common.decorators import iclassmethod + from cubicweb.appobject import AppObject from cubicweb.view import NOINDEX, NOFOLLOW from cubicweb.common import tags @@ -62,10 +64,65 @@ __metaclass__ = metafieldsform __registry__ = 'forms' + parent_form = None + def __init__(self, req, rset, **kwargs): super(Form, self).__init__(req, rset=rset, **kwargs) self.restore_previous_post(self.session_key()) + @property + def root_form(self): + """return the root form""" + if self.parent_form is None: + return self + return self.parent_form.root_form + + @iclassmethod + def _fieldsattr(cls_or_self): + if isinstance(cls_or_self, type): + fields = cls_or_self._fields_ + else: + fields = cls_or_self.fields + return fields + + @iclassmethod + def field_by_name(cls_or_self, name, role='subject'): + """return field with the given name and role. + Raise FieldNotFound if the field can't be found. + """ + for field in cls_or_self._fieldsattr(): + if field.name == name and field.role == role: + return field + raise FieldNotFound(name) + + @iclassmethod + def fields_by_name(cls_or_self, name, role='subject'): + """return a list of fields with the given name and role""" + return [field for field in cls_or_self._fieldsattr() + if field.name == name and field.role == role] + + @iclassmethod + def remove_field(cls_or_self, field): + """remove a field from form class or instance""" + cls_or_self._fieldsattr().remove(field) + + @iclassmethod + def append_field(cls_or_self, field): + """append a field to form class or instance""" + cls_or_self._fieldsattr().append(field) + + @iclassmethod + def insert_field_before(cls_or_self, new_field, name, role='subject'): + field = cls_or_self.field_by_name(name, role) + fields = cls_or_self._fieldsattr() + fields.insert(fields.index(field), new_field) + + @iclassmethod + def insert_field_after(cls_or_self, new_field, name, role='subject'): + field = cls_or_self.field_by_name(name, role) + fields = cls_or_self._fieldsattr() + fields.insert(fields.index(field)+1, new_field) + def session_key(self): """return the key that may be used to store / retreive data about a previous post which failed because of a validation error diff -r c0c7a944c00d -r a3431f4e2f40 web/formfields.py --- a/web/formfields.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/formfields.py Tue Sep 29 15:58:44 2009 +0200 @@ -209,7 +209,7 @@ return vocab def form_init(self, form): - """method called before by form_build_context to trigger potential field + """method called before by build_context to trigger potential field initialization requiring the form instance """ pass diff -r c0c7a944c00d -r a3431f4e2f40 web/test/unittest_application.py --- a/web/test/unittest_application.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/test/unittest_application.py Tue Sep 29 15:58:44 2009 +0200 @@ -138,6 +138,11 @@ class ApplicationTC(CubicWebTC): + def setUp(self): + super(ApplicationTC, self).setUp() + def raise_hdlr(*args, **kwargs): + raise + self.app.error_handler = raise_hdlr def publish(self, req, path='view'): return self.app.publish(path, req) @@ -411,6 +416,12 @@ self.assertRaises(AuthenticationError, self.publish, req, 'logout') self.assertEquals(len(self.open_sessions), 0) + def test_non_regr_optional_first_var(self): + req = self.request() + # expect a rset with None in [0][0] + req.form['rql'] = 'rql:Any OV1, X WHERE X custom_workflow OV1?' + self.publish(req) + print 'yuea' if __name__ == '__main__': unittest_main() diff -r c0c7a944c00d -r a3431f4e2f40 web/test/unittest_form.py --- a/web/test/unittest_form.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/test/unittest_form.py Tue Sep 29 15:58:44 2009 +0200 @@ -82,7 +82,7 @@ form = EntityFieldsForm(self.request(login=u'toto'), None, entity=e) field = StringField(name='login', eidparam=True) form.append_field(field) - form.form_build_context({}) + form.build_context({}) self.assertEquals(form.form_field_display_value(field, {}), 'toto') @@ -137,7 +137,7 @@ # fields tests ############################################################ def _render_entity_field(self, name, form): - form.form_build_context({}) + form.build_context({}) renderer = FormRenderer(self.req) return form.field_by_name(name).render(form, renderer) @@ -171,7 +171,7 @@ class FFForm(EntityFieldsForm): data = FileField(format_field=StringField(name='data_format', max_length=50), encoding_field=StringField(name='data_encoding', max_length=20)) - file = self.add_entity('File', name=u"pouet.txt", data_encoding=u'UTF-8', + file = self.add_entity('File', data_name=u"pouet.txt", data_encoding=u'UTF-8', data=Binary('new widgets system')) form = FFForm(self.req, redirect_path='perdu.com', entity=file) self.assertTextEquals(self._render_entity_field('data', form), @@ -195,7 +195,7 @@ return 'ascii' def form_field_format(self, field): return 'text/plain' - file = self.add_entity('File', name=u"pouet.txt", data_encoding=u'UTF-8', + file = self.add_entity('File', data_name=u"pouet.txt", data_encoding=u'UTF-8', data=Binary('new widgets system')) form = EFFForm(self.req, redirect_path='perdu.com', entity=file) self.assertTextEquals(self._render_entity_field('data', form), diff -r c0c7a944c00d -r a3431f4e2f40 web/views/autoform.py --- a/web/views/autoform.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/autoform.py Tue Sep 29 15:58:44 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.decorators import iclassmethod +from logilab.common.decorators import iclassmethod, cached from cubicweb import typed_eid from cubicweb.web import stdmsgs, uicfg @@ -113,6 +113,11 @@ """true if the form needs enctype=multipart/form-data""" return self._subform_needs_multipart() + def build_context(self, rendervalues=None): + super(AutomaticEntityForm, self).build_context(rendervalues) + for form in self.inlined_forms(): + form.build_context(rendervalues) + def _subform_needs_multipart(self, _tested=None): if _tested is None: _tested = set() @@ -260,6 +265,46 @@ # inlined forms support #################################################### + @cached + def inlined_form_views(self): + """compute and return list of inlined form views (hosting the inlined form object) + """ + formviews = [] + entity = self.edited_entity + for rschema, ttypes, role in self.inlined_relations(): + # show inline forms only if there's one possible target type + # for rschema + if len(ttypes) != 1: + self.warning('entity related by the %s relation should have ' + 'inlined form but there is multiple target types, ' + 'dunno what to do', rschema) + continue + ttype = ttypes[0].type + if self.should_inline_relation_form(rschema, ttype, role): + formviews += self.inline_edition_form_view(rschema, ttype, role) + if role == 'subject': + card = rschema.rproperty(entity.e_schema, ttype, 'cardinality')[0] + else: + card = rschema.rproperty(ttype, entity.e_schema, 'cardinality')[1] + # there is no related entity and we need at least one: we need to + # display one explicit inline-creation view + if self.should_display_inline_creation_form(rschema, formviews, card): + formviews += self.inline_creation_form_view(rschema, ttype, role) + # we can create more than one related entity, we thus display a link + # to add new related entities + if self.should_display_add_new_relation_link(rschema, formviews, card): + addnewlink = self.vreg['views'].select( + 'inline-addnew-link', self.req, + etype=ttype, rtype=rschema, role=role, + peid=self.edited_entity.eid, pform=self, card=card) + formviews.append(addnewlink) + return formviews + + def inlined_forms(self): + for formview in self.inlined_form_views(): + if formview.form: # may be None for the addnew_link artefact form + yield formview.form + def should_inline_relation_form(self, rschema, targettype, role): """return true if the given relation with entity has role and a targettype target should be inlined @@ -269,25 +314,6 @@ """ return True - def display_inline_edition_form(self, w, rschema, targettype, role, - i18nctx): - """display inline forms for already related entities. - - Return True if some inlined form are actually displayed - """ - existant = False - entity = self.edited_entity - related = entity.has_eid() and entity.related(rschema, role) - if related: - # display inline-edition view for all existing related entities - for i, relentity in enumerate(related.entities()): - if relentity.has_perm('update'): - w(self._cw.view('inline-edition', related, row=i, col=0, - rtype=rschema, role=role, ptype=entity.e_schema, - peid=entity.eid, i18nctx=i18nctx)) - existant = True - return existant - def should_display_inline_creation_form(self, rschema, existant, card): """return true if a creation form should be inlined @@ -295,17 +321,6 @@ """ return not existant and card in '1+' or self._cw.form.has_key('force_%s_display' % rschema) - def display_inline_creation_form(self, w, rschema, targettype, role, - i18nctx): - """display inline forms to a newly related (hence created) entity. - - Return True if some inlined form are actually displayed - """ - entity = self.edited_entity - w(self._cw.view('inline-creation', None, etype=targettype, - peid=entity.eid, ptype=entity.e_schema, - rtype=rschema, role=role, i18nctx=i18nctx)) - def should_display_add_new_relation_link(self, rschema, existant, card): """return true if we should add a link to add a new creation form (through ajax call) @@ -323,6 +338,29 @@ """ return card in '1?' + def inline_edition_form_view(self, rschema, ttype, role): + """yield inline form views for already related entities through the + given relation + """ + entity = self.edited_entity + related = entity.has_eid() and entity.related(rschema, role) + if related: + vvreg = self._cw.vreg['views'] + # display inline-edition view for all existing related entities + for i, relentity in enumerate(related.entities()): + if relentity.has_perm('update'): + yield vvreg.select('inline-edition', self._cw, related, + row=i, col=0, rtype=rschema, role=role, + peid=entity.eid, pform=self) + + def inline_creation_form_view(self, rschema, ttype, role): + """yield inline form views to a newly related (hence created) entity + through the given relation + """ + yield self._cw.vreg['views'].select('inline-creation', self._cw, + etype=ttype, rtype=rschema, role=role, + peid=self.edited_entity.eid, pform=self) + def etype_relation_field(etype, rtype, role='subject'): eschema = AutomaticEntityForm.schema.eschema(etype) @@ -337,7 +375,8 @@ uicfg.autoform_section.tag_attribute(('*', 'creation_date'), 'metadata') uicfg.autoform_section.tag_attribute(('*', 'modification_date'), 'metadata') uicfg.autoform_section.tag_attribute(('*', 'cwuri'), 'metadata') -uicfg.autoform_section.tag_subject_of(('*', 'in_state', '*'), 'primary') +uicfg.autoform_section.tag_attribute(('*', 'has_text'), 'generated') +uicfg.autoform_section.tag_subject_of(('*', 'in_state', '*'), 'generated') uicfg.autoform_section.tag_subject_of(('*', 'owned_by', '*'), 'metadata') uicfg.autoform_section.tag_subject_of(('*', 'created_by', '*'), 'metadata') uicfg.autoform_section.tag_subject_of(('*', 'require_permission', '*'), 'generated') diff -r c0c7a944c00d -r a3431f4e2f40 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/basecontrollers.py Tue Sep 29 15:58:44 2009 +0200 @@ -354,7 +354,7 @@ entity.eid = varname entity['pkey'] = propkey form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity) - form.form_build_context() + form.build_context() vfield = form.field_by_name('value') renderer = FormRenderer(self._cw) return vfield.render(form, renderer, tabindex=tabindex) \ @@ -381,8 +381,7 @@ view = self._cw.vreg['views'].select('inline-creation', self._cw, etype=ttype, peid=peid, rtype=rtype, role=role) - return self._call_view(view, etype=ttype, peid=peid, - rtype=rtype, role=role, i18nctx=i18nctx) + return self._call_view(view, i18nctx=i18nctx) @jsonize def js_validate_form(self, action, names, values): diff -r c0c7a944c00d -r a3431f4e2f40 web/views/boxes.py --- a/web/views/boxes.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/boxes.py Tue Sep 29 15:58:44 2009 +0200 @@ -60,8 +60,8 @@ ('addrelated', None)): for action in actions.get(category, ()): if category == 'addrelated': - warn('"addrelated" category is deprecated, use "moreaction"' - ' category w/ "addrelated" submenu', + warn('[3.5] "addrelated" category is deprecated, use ' + '"moreactions" category w/ "addrelated" submenu', DeprecationWarning) defaultmenu = self._get_menu('addrelated', _('add'), _('add')) if action.submenu: diff -r c0c7a944c00d -r a3431f4e2f40 web/views/cwproperties.py --- a/web/views/cwproperties.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/cwproperties.py Tue Sep 29 15:58:44 2009 +0200 @@ -216,7 +216,7 @@ eidparam=True)) #subform.vreg = self._cw.vreg subform.form_add_hidden('pkey', key, eidparam=True) - form.form_add_subform(subform) + form.add_subform(subform) return subform diff -r c0c7a944c00d -r a3431f4e2f40 web/views/editforms.py --- a/web/views/editforms.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/editforms.py Tue Sep 29 15:58:44 2009 +0200 @@ -14,6 +14,7 @@ from simplejson import dumps from logilab.mtconverter import xml_escape +from logilab.common.decorators import cached from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity, specified_etype_implements, yes) @@ -66,7 +67,7 @@ subform = self._cw.vreg['forms'].select('base', self._cw, entity=entity, mainform=False) - self.form_add_subform(subform) + self.add_subform(subform) class DeleteConfFormView(FormViewMixIn, EntityView): @@ -418,7 +419,7 @@ mainform=False) # XXX rely on the EntityCompositeFormRenderer to put the eid input form.remove_field(form.field_by_name('eid')) - self.form_add_subform(form) + self.add_subform(form) class TableEditFormView(FormViewMixIn, EntityView): @@ -436,62 +437,83 @@ 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') + + _select_attrs = ('peid', 'rtype', 'role', 'pform') removejs = "removeInlinedEntity('%s', '%s', '%s')" - def call(self, **kwargs): - """redefine default call() method to avoid automatic - insertions of
between each row of - the resultset - """ - rset = self.cw_rset - for i in xrange(len(rset)): - self.wview(self.__regid__, rset, row=i, **kwargs) + 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) - def cell_call(self, row, col, peid, rtype, role, i18nctx, **kwargs): + @property + @cached + def form(self): + entity = self._entity() + form = self.vreg['forms'].select('edition', self._cw, + entity=entity, + form_renderer_id='inline', + mainform=False, copy_nav_params=False, + **self.extra_kwargs) + form.parent_form = self.pform + 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.cw_rset.get_entity(row, col) - divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (peid, rtype, - entity.eid) - self.render_form(entity, peid, rtype, role, i18nctx, - divonclick=divonclick) + 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, entity, peid, rtype, role, i18nctx, **kwargs): + def render_form(self, i18nctx, **kwargs): """fetch and render the form""" - form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity, - form_renderer_id='inline', - formtype='inlined', - mainform=False, - copy_nav_params=False) - self.add_hiddens(form, entity, peid, rtype, role) - divid = '%s-%s-%s' % (peid, rtype, entity.eid) - title = self._cw.pgettext(i18nctx, 'This %s' % entity.e_schema) - removejs = self.removejs % (peid, rtype, entity.eid) - countkey = '%s_count' % rtype + entity = self._entity() + divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid) + title = self.req.pgettext(i18nctx, 'This %s' % entity.e_schema) + removejs = self.removejs % (self.peid, self.rtype, entity.eid) + countkey = '%s_count' % self.rtype try: self._cw.data[countkey] += 1 except: self._cw.data[countkey] = 1 - self.w(form.form_render(divid=divid, title=title, removejs=removejs, - i18nctx=i18nctx, - counter=self._cw.data[countkey], **kwargs)) + self.w(self.form.form_render( + divid=divid, title=title, removejs=removejs, i18nctx=i18nctx, + counter=self.req.data[countkey], **kwargs)) - def add_hiddens(self, form, entity, peid, rtype, role): + def add_hiddens(self, form, entity): # to ease overriding (see cubes.vcsfile.views.forms for instance) - form.form_add_hidden(name='%s:%s' % (rtype, peid), value=entity.eid, - id='rel-%s-%s-%s' % (peid, rtype, entity.eid)) + if self.keep_entity(form, entity): + if entity.has_eid(): + rval = entity.eid + else: + rval = INTERNAL_FIELD_VALUE + form.form_add_hidden('edit%s-%s:%s' % (self.role[0], self.rtype, self.peid), rval) + form.form_add_hidden(name='%s:%s' % (self.rtype, self.peid), value=entity.eid, + id='rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)) - def keep_entity(self, form, entity, peid, rtype): + 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(rtype, peid), + 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 @@ -499,23 +521,52 @@ 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')) + _select_attrs = InlineEntityEditionFormView._select_attrs + ('etype',) removejs = "removeInlineForm('%s', '%s', '%s')" - def call(self, etype, peid, rtype, role, i18nctx, **kwargs): - """ - :param etype: the entity type being created in the inline form - :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 - """ + @cached + def _entity(self): try: - cls = self._cw.vreg['etypes'].etype_class(etype) + 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() - self.render_form(entity, peid, rtype, role, i18nctx, **kwargs) + self.initialize_varmaker() + entity = cls(self.req) + entity.eid = self.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): + 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.req.pgettext + self.w(u'+ %s.' + % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype))) + self.w(u'
') + self.w(u'
 
') diff -r c0c7a944c00d -r a3431f4e2f40 web/views/formrenderers.py --- a/web/views/formrenderers.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/formrenderers.py Tue Sep 29 15:58:44 2009 +0200 @@ -169,7 +169,6 @@ return tag + '>' def render_fields(self, w, form, values): - form.form_build_context(values) fields = self._render_hidden_fields(w, form) if fields: self._render_fields(fields, w, form) @@ -292,7 +291,7 @@ _main_display_fields = None def render_fields(self, w, form, values): - if not form.is_subform: + if form.parent_form is None: w(u'') subfields = [field for field in form.forms[0].fields if self.display_field(form, field) @@ -308,14 +307,14 @@ w(u'' % self._cw._(field.label)) w(u'') super(EntityCompositeFormRenderer, self).render_fields(w, form, values) - if not form.is_subform: + if form.parent_form is None: w(u'
%s
') if self._main_display_fields: super(EntityCompositeFormRenderer, self)._render_fields( self._main_display_fields, w, form) def _render_fields(self, fields, w, form): - if form.is_subform: + if form.parent_form is not None: entity = form.edited_entity values = form.form_previous_values qeid = eid_param('eid', entity.eid) @@ -351,16 +350,19 @@ # needs some additional points in some case (XXX explain cases) __select__ = entity_implements('Any') & yes() - _options = FormRenderer._options + ('display_relations_form',) + _options = FormRenderer._options + ('display_relations_form', 'main_form_title') display_relations_form = True + main_form_title = _('main information') def render(self, form, values): rendered = super(EntityFormRenderer, self).render(form, values) return rendered + u'
' # close extra div introducted by open_form def open_form(self, form, values): - attrs_fs_label = ('
%s
' - % self._cw._('main informations')) + attrs_fs_label = '' + if self.main_form_title: + attrs_fs_label += ('
%s
' + % self.req._(self.main_form_title)) attrs_fs_label += '
' return attrs_fs_label + super(EntityFormRenderer, self).open_form(form, values) @@ -460,51 +462,23 @@ def inline_entities_form(self, w, form): """create a form to edit entity's inlined relations""" - if not hasattr(form, 'inlined_relations'): + if not hasattr(form, 'inlined_form_views'): return - for rschema, targettypes, role in form.inlined_relations(): - # show inline forms only if there's one possible target type - # for rschema - if len(targettypes) != 1: - self.warning('entity related by the %s relation should have ' - 'inlined form but there is multiple target types, ' - 'dunno what to do', rschema) - continue - targettype = targettypes[0].type - if form.should_inline_relation_form(rschema, targettype, role): - self.inline_relation_form(w, form, rschema, targettype, role) + keysinorder = [] + formviews = form.inlined_form_views() + for formview in formviews: + if not (formview.rtype, formview.role) in keysinorder: + keysinorder.append( (formview.rtype, formview.role) ) + for key in keysinorder: + self.inline_relation_form(w, form, [fv for fv in formviews + if (fv.rtype, fv.role) == key]) - def inline_relation_form(self, w, form, rschema, targettype, role): - entity = form.edited_entity - __ = self._cw.pgettext - i18nctx = 'inlined:%s.%s.%s' % (entity.e_schema, rschema, role) - w(u'
' % rschema) - existant = form.display_inline_edition_form(w, rschema, targettype, - role, i18nctx) - if role == 'subject': - card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0] - else: - card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1] - # there is no related entity and we need at least one: we need to - # display one explicit inline-creation view - if form.should_display_inline_creation_form(rschema, existant, card): - form.display_inline_creation_form(w, rschema, targettype, - role, i18nctx) - existant = True - # we can create more than one related entity, we thus display a link - # to add new related entities - if form.should_display_add_new_relation_link(rschema, existant, card): - divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid) - w(u'
' - % divid) - js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % ( - entity.eid, targettype, rschema, role, i18nctx) - if form.should_hide_add_new_relation_link(rschema, card): - js = "toggleVisibility('%s'); %s" % (divid, js) - w(u'+ %s.' - % (rschema, entity.eid, js, __(i18nctx, 'add a %s' % targettype))) - w(u'
') - w(u'
 
') + def inline_relation_form(self, w, form, formviews): + i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema, + formviews[0].rtype, formviews[0].role) + w(u'
' % formviews[0].rtype) + for formview in formviews: + w(formview.render(i18nctx=i18nctx, row=formview.row, col=formview.col)) w(u'
') @@ -541,7 +515,6 @@ return '\n'.join(data) def render_fields(self, w, form, values): - form.form_build_context(values) w(u'
' % values) fields = self._render_hidden_fields(w, form) w(u'
') diff -r c0c7a944c00d -r a3431f4e2f40 web/views/forms.py --- a/web/views/forms.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/forms.py Tue Sep 29 15:58:44 2009 +0200 @@ -10,7 +10,6 @@ from warnings import warn from logilab.common.compat import any -from logilab.common.decorators import iclassmethod from cubicweb.selectors import non_final_entity, match_kwargs, one_line_rset from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param @@ -56,7 +55,6 @@ """ __regid__ = 'base' - is_subform = False internal_fields = ('__errorurl',) + NAV_FORM_PARAMETERS # attributes overrideable by subclasses or through __init__ @@ -84,6 +82,8 @@ self.form_add_hidden(key, val) elif hasattr(self.__class__, key) and not key[0] == '_': setattr(self, key, val) + else: + self.extra_kwargs[key] = val # skip other parameters, usually given for selection # (else write a custom class to handle them) if mainform: @@ -101,52 +101,6 @@ if 'domid' in kwargs:# session key changed self.restore_previous_post(self.session_key()) - @iclassmethod - def _fieldsattr(cls_or_self): - if isinstance(cls_or_self, type): - fields = cls_or_self._fields_ - else: - fields = cls_or_self.fields - return fields - - @iclassmethod - def field_by_name(cls_or_self, name, role='subject'): - """return field with the given name and role. - Raise FieldNotFound if the field can't be found. - """ - for field in cls_or_self._fieldsattr(): - if field.name == name and field.role == role: - return field - raise form.FieldNotFound(name) - - @iclassmethod - def fields_by_name(cls_or_self, name, role='subject'): - """return a list of fields with the given name and role""" - return [field for field in cls_or_self._fieldsattr() - if field.name == name and field.role == role] - - @iclassmethod - def remove_field(cls_or_self, field): - """remove a field from form class or instance""" - cls_or_self._fieldsattr().remove(field) - - @iclassmethod - def append_field(cls_or_self, field): - """append a field to form class or instance""" - cls_or_self._fieldsattr().append(field) - - @iclassmethod - def insert_field_before(cls_or_self, new_field, name, role='subject'): - field = cls_or_self.field_by_name(name, role) - fields = cls_or_self._fieldsattr() - fields.insert(fields.index(field), new_field) - - @iclassmethod - def insert_field_after(cls_or_self, new_field, name, role='subject'): - field = cls_or_self.field_by_name(name, role) - fields = cls_or_self._fieldsattr() - fields.insert(fields.index(field)+1, new_field) - @property def form_needs_multipart(self): """true if the form needs enctype=multipart/form-data""" @@ -174,6 +128,7 @@ """render this form, using the renderer given in args or the default FormRenderer() """ + self.build_context(values) renderer = values.pop('renderer', None) if renderer is None: renderer = self.form_default_renderer() @@ -184,7 +139,7 @@ self._cw, rset=self.cw_rset, row=self.cw_row, col=self.cw_col) - def form_build_context(self, rendervalues=None): + def build_context(self, rendervalues=None): """build form context values (the .context attribute which is a dictionary with field instance as key associated to a dictionary containing field 'name' (qualified), 'id', 'value' (for display, always @@ -193,6 +148,8 @@ rendervalues is an optional dictionary containing extra kwargs given to form_render() """ + if self.context is not None: + return # already built self.context = context = {} # ensure rendervalues is a dict if rendervalues is None: @@ -224,6 +181,8 @@ if value is None: if field.name in rendervalues: value = rendervalues[field.name] + elif field.name in self.extra_kwargs: + value = self.extra_kwargs[field.name] else: value = self.form_field_value(field, load_bytes) if callable(value): @@ -349,9 +308,9 @@ searchedvalues = ['%s:%s:%s' % (field.name, eid, field.role) for eid in value] # remove associated __linkto hidden fields - for field in self.fields_by_name('__linkto'): + for field in self.root_form.fields_by_name('__linkto'): if field.initial in searchedvalues: - self.remove_field(field) + self.root_form.remove_field(field) else: value = None return value @@ -375,21 +334,6 @@ self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row, col=self.cw_col, entity=self.edited_entity) -## def form_build_context(self, values=None): -## """overriden to add edit[s|o] hidden fields and to ensure schema fields -## have eidparam set to True -## """ -## eschema = self.edited_entity.e_schema -## for field in self.fields[:]: -## for field in field.actual_fields(self): -## fieldname = field.name -## if fieldname != 'eid' and ( -## (eschema.has_subject_relation(fieldname) or -## eschema.has_object_relation(fieldname))): -## # XXX why do we need to do this here ? -## field.eidparam = True -## return super(EntityFieldsForm, self).form_build_context(values) - def form_field_value(self, field, load_bytes=False): """return field's *typed* value @@ -554,31 +498,28 @@ return False -class CompositeForm(FieldsForm): +class CompositeFormMixIn(object): """form composed of sub-forms""" __regid__ = 'composite' form_renderer_id = __regid__ def __init__(self, *args, **kwargs): - super(CompositeForm, self).__init__(*args, **kwargs) + super(CompositeFormMixIn, self).__init__(*args, **kwargs) self.forms = [] - def form_add_subform(self, subform): + def add_subform(self, subform): """mark given form as a subform and append it""" - subform.is_subform = True + subform.parent_form = self self.forms.append(subform) + def build_context(self, rendervalues=None): + super(CompositeFormMixIn, self).build_context(rendervalues) + for form in self.forms: + form.build_context(rendervalues) + -class CompositeEntityForm(EntityFieldsForm): - """form composed of sub-forms""" - __regid__ = 'composite' - form_renderer_id = __regid__ +class CompositeForm(CompositeFormMixIn, FieldsForm): + pass - def __init__(self, *args, **kwargs): - super(CompositeEntityForm, self).__init__(*args, **kwargs) - self.forms = [] - - def form_add_subform(self, subform): - """mark given form as a subform and append it""" - subform.is_subform = True - self.forms.append(subform) +class CompositeEntityForm(CompositeFormMixIn, EntityFieldsForm): + pass # XXX why is this class necessary? diff -r c0c7a944c00d -r a3431f4e2f40 web/views/ibreadcrumbs.py --- a/web/views/ibreadcrumbs.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/ibreadcrumbs.py Tue Sep 29 15:58:44 2009 +0200 @@ -21,7 +21,7 @@ class BreadCrumbEntityVComponent(Component): __regid__ = 'breadcrumbs' - __select__ = one_line_rset() & implements(IBreadCrumbs) + __select__ = one_line_rset() & implements(IBreadCrumbs, accept_none=False) cw_property_defs = { _('visible'): dict(type='Boolean', default=True, @@ -70,7 +70,8 @@ class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent): - __select__ = two_lines_rset() & one_etype_rset() & implements(IBreadCrumbs) + __select__ = two_lines_rset() & one_etype_rset() & \ + implements(IBreadCrumbs, accept_none=False) def render_breadcrumbs(self, contextentity, path): # XXX hack: only display etype name or first non entity path part diff -r c0c7a944c00d -r a3431f4e2f40 web/views/massmailing.py --- a/web/views/massmailing.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/massmailing.py Tue Sep 29 15:58:44 2009 +0200 @@ -13,18 +13,16 @@ from cubicweb.interfaces import IEmailable from cubicweb.selectors import implements, match_user_groups from cubicweb.view import EntityView -from cubicweb.web import stdmsgs -from cubicweb.web.action import Action -from cubicweb.web.form import FormViewMixIn -from cubicweb.web.formfields import StringField +from cubicweb.web import stdmsgs, action, form, formfields as ff from cubicweb.web.formwidgets import CheckBox, TextInput, AjaxWidget, ImgButton from cubicweb.web.views import forms, formrenderers -class SendEmailAction(Action): +class SendEmailAction(action.Action): __regid__ = 'sendemail' # XXX should check email is set as well - __select__ = implements(IEmailable) & match_user_groups('managers', 'users') + __select__ = (action.Action.__select__ & implements(IEmailable) + & match_user_groups('managers', 'users')) title = _('send email') category = 'mainactions' @@ -40,11 +38,12 @@ class MassMailingForm(forms.FieldsForm): __regid__ = 'massmailing' - sender = StringField(widget=TextInput({'disabled': 'disabled'}), label=_('From:')) - recipient = StringField(widget=CheckBox(), label=_('Recipients:')) - subject = StringField(label=_('Subject:'), max_length=256) - mailbody = StringField(widget=AjaxWidget(wdgtype='TemplateTextField', - inputid='mailbody')) + sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}), + label=_('From:')) + recipient = ff.StringField(widget=CheckBox(), label=_('Recipients:')) + subject = ff.StringField(label=_('Subject:'), max_length=256) + mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField', + inputid='mailbody')) form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()", _('send email'), 'SEND_EMAIL_ICON'), @@ -117,7 +116,7 @@ def render_buttons(self, w, form): pass -class MassMailingFormView(FormViewMixIn, EntityView): +class MassMailingFormView(form.FormViewMixIn, EntityView): __regid__ = 'massmailing' __select__ = implements(IEmailable) & match_user_groups('managers', 'users') diff -r c0c7a944c00d -r a3431f4e2f40 web/views/workflow.py --- a/web/views/workflow.py Mon Sep 28 16:39:10 2009 +0200 +++ b/web/views/workflow.py Tue Sep 29 15:58:44 2009 +0200 @@ -83,7 +83,7 @@ subform = self._cw.vreg['forms'].select('edition', self._cw, entity=trinfo, mainform=False) subform.field_by_name('by_transition').widget = fwdgs.HiddenInput() - form.form_add_subform(subform) + form.add_subform(subform) self.w(form.form_render(wf_info_for=entity.eid, by_transition=transition.eid))