# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1240906204 -7200 # Node ID 742aff97dbf73026823e1f3dd1b7316548d66bdb # Parent 12bba5e13cf947a23f52bbdc52f28a21de8d19bc move AutomaticEntityForm and PrimaryView into their own module diff -r 12bba5e13cf9 -r 742aff97dbf7 web/views/autoform.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/autoform.py Tue Apr 28 10:10:04 2009 +0200 @@ -0,0 +1,327 @@ +"""The automatic entity form. + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +from logilab.common.decorators import iclassmethod + +from cubicweb import typed_eid +from cubicweb.web import stdmsgs, uicfg +from cubicweb.web.form import FieldNotFound, EntityFieldsForm +from cubicweb.web.formwidgets import Button, SubmitButton +_ = unicode + +class AutomaticEntityForm(EntityFieldsForm): + """base automatic form to edit any entity. + + Designed to be flly generated from schema but highly configurable through: + * rtags (rcategories, rfields, rwidgets, inlined, rpermissions) + * various standard form parameters + + You can also easily customise it by adding/removing fields in + AutomaticEntityForm instances. + """ + id = 'edition' + + cwtarget = 'eformframe' + cssclass = 'entityForm' + copy_nav_params = True + form_buttons = [SubmitButton(stdmsgs.BUTTON_OK), + Button(stdmsgs.BUTTON_APPLY, cwaction='apply'), + Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] + attrcategories = ('primary', 'secondary') + # class attributes below are actually stored in the uicfg module since we + # don't want them to be reloaded + rcategories = uicfg.rcategories + rfields = uicfg.rfields + rwidgets = uicfg.rwidgets + rinlined = uicfg.rinlined + rpermissions_overrides = uicfg.rpermissions_overrides + + @classmethod + def vreg_initialization_completed(cls): + """set default category tags for relations where it's not yet defined in + the category relation tags + """ + for eschema in cls.schema.entities(): + for rschema, tschemas, role in eschema.relation_definitions(True): + for tschema in tschemas: + if role == 'subject': + X, Y = eschema, tschema + card = rschema.rproperty(X, Y, 'cardinality')[0] + composed = rschema.rproperty(X, Y, 'composite') == 'object' + else: + X, Y = tschema, eschema + card = rschema.rproperty(X, Y, 'cardinality')[1] + composed = rschema.rproperty(X, Y, 'composite') == 'subject' + if not cls.rcategories.rtag(rschema, role, X, Y): + if card in '1+': + if not rschema.is_final() and composed: + category = 'generated' + else: + category = 'primary' + elif rschema.is_final(): + category = 'secondary' + else: + category = 'generic' + cls.rcategories.set_rtag(category, rschema, role, X, Y) + + @classmethod + def erelations_by_category(cls, entity, categories=None, permission=None, rtags=None): + """return a list of (relation schema, target schemas, role) matching + categories and permission + """ + if categories is not None: + if not isinstance(categories, (list, tuple, set, frozenset)): + categories = (categories,) + if not isinstance(categories, (set, frozenset)): + categories = frozenset(categories) + eschema = entity.e_schema + if rtags is None: + rtags = cls.rcategories + permsoverrides = cls.rpermissions_overrides + if entity.has_eid(): + eid = entity.eid + else: + eid = None + for rschema, targetschemas, role in eschema.relation_definitions(True): + # check category first, potentially lower cost than checking + # permission which may imply rql queries + if categories is not None: + targetschemas = [tschema for tschema in targetschemas + if rtags.etype_rtag(eschema, rschema, role, tschema) in categories] + if not targetschemas: + continue + if permission is not None: + # tag allowing to hijack the permission machinery when + # permission is not verifiable until the entity is actually + # created... + if eid is None and '%s_on_new' % permission in permsoverrides.etype_rtags(eschema, rschema, role): + yield (rschema, targetschemas, role) + continue + if rschema.is_final(): + if not rschema.has_perm(entity.req, permission, eid): + continue + elif role == 'subject': + if not ((eid is None and rschema.has_local_role(permission)) or + rschema.has_perm(entity.req, permission, fromeid=eid)): + continue + # on relation with cardinality 1 or ?, we need delete perm as well + # if the relation is already set + if (permission == 'add' + and rschema.cardinality(eschema, targetschemas[0], role) in '1?' + and eid and entity.related(rschema.type, role) + and not rschema.has_perm(entity.req, 'delete', fromeid=eid, + toeid=entity.related(rschema.type, role)[0][0])): + continue + elif role == 'object': + if not ((eid is None and rschema.has_local_role(permission)) or + rschema.has_perm(entity.req, permission, toeid=eid)): + continue + # on relation with cardinality 1 or ?, we need delete perm as well + # if the relation is already set + if (permission == 'add' + and rschema.cardinality(targetschemas[0], eschema, role) in '1?' + and eid and entity.related(rschema.type, role) + and not rschema.has_perm(entity.req, 'delete', toeid=eid, + fromeid=entity.related(rschema.type, role)[0][0])): + continue + yield (rschema, targetschemas, role) + + @classmethod + def esrelations_by_category(cls, entity, categories=None, permission=None): + """filter out result of relations_by_category(categories, permission) by + removing final relations + + return a sorted list of (relation's label, relation'schema, role) + """ + result = [] + for rschema, ttypes, role in cls.erelations_by_category( + entity, categories, permission): + if rschema.is_final(): + continue + result.append((rschema.display_name(entity.req, role), rschema, role)) + return sorted(result) + + @iclassmethod + def field_by_name(cls_or_self, name, role='subject', 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) + except FieldNotFound: # XXX should raise more explicit exception + if eschema is None or not name in cls_or_self.schema: + raise + rschema = cls_or_self.schema.rschema(name) + fieldcls = cls_or_self.rfields.etype_rtag(eschema, rschema, role) + if fieldcls: + return fieldcls(name=name, role=role, eidparam=True) + widget = cls_or_self.rwidgets.etype_rtag(eschema, rschema, role) + if widget: + field = guess_field(eschema, rschema, role, + eidparam=True, widget=widget) + else: + field = guess_field(eschema, rschema, role, eidparam=True) + if field is None: + raise + return field + + def __init__(self, *args, **kwargs): + super(AutomaticEntityForm, self).__init__(*args, **kwargs) + entity = self.edited_entity + if entity.has_eid(): + entity.complete() + for rschema, role in self.editable_attributes(): + try: + self.field_by_name(rschema.type, role) + continue # explicitly specified + except FieldNotFound: + # has to be guessed + try: + field = self.field_by_name(rschema.type, role, + eschema=entity.e_schema) + self.fields.append(field) + except FieldNotFound: + # meta attribute such as _format + continue + self.maxrelitems = self.req.property_value('navigation.related-limit') + self.force_display = bool(self.req.form.get('__force_display')) + + @property + def related_limit(self): + if self.force_display: + return None + return self.maxrelitems + 1 + + def relations_by_category(self, categories=None, permission=None): + """return a list of (relation schema, target schemas, role) matching + given category(ies) and permission + """ + return self.erelations_by_category(self.edited_entity, categories, + permission) + + def inlined_relations(self): + """return a list of (relation schema, target schemas, role) matching + given category(ies) and permission + """ + # we'll need an initialized varmaker if there are some inlined relation + self.initialize_varmaker() + return self.erelations_by_category(self.edited_entity, True, 'add', self.rinlined) + + def srelations_by_category(self, categories=None, permission=None): + """filter out result of relations_by_category(categories, permission) by + removing final relations + + return a sorted list of (relation's label, relation'schema, role) + """ + return self.esrelations_by_category(self.edited_entity, categories, + permission) + + def action(self): + """return the form's action attribute. Default to validateform if not + explicitly overriden. + """ + try: + return self._action + except AttributeError: + return self.build_url('validateform') + + def set_action(self, value): + """override default action""" + self._action = value + + action = property(action, set_action) + + def editable_attributes(self): + """return a list of (relation schema, role) to edit for the entity""" + return [(rschema, x) for rschema, _, x in self.relations_by_category( + self.attrcategories, 'add') if rschema != 'eid'] + + 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.req.get_pending_deletes(entity.eid) + for label, rschema, role in self.srelations_by_category('generic', 'add'): + relatedrset = entity.related(rschema, role, limit=self.related_limit) + if rschema.has_perm(self.req, 'delete'): + toggable_rel_link_func = toggable_relation_link + else: + toggable_rel_link_func = lambda x, y, z: u'' + related = [] + for row in xrange(relatedrset.rowcount): + nodeid = relation_id(entity.eid, rschema, role, + relatedrset[row][0]) + if nodeid in pending_deletes: + status = u'pendingDelete' + label = '+' + else: + status = u'' + label = 'x' + dellink = toggable_rel_link_func(entity.eid, nodeid, label) + eview = self.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.req.get_pending_inserts(eid)) + for pendingid in pending_inserts: + eidfrom, rtype, eidto = pendingid.split(':') + if typed_eid(eidfrom) == eid: # subject + label = display_name(self.req, rtype, 'subject') + reid = eidto + else: + label = display_name(self.req, rtype, 'object') + reid = eidfrom + jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \ + % (pendingid, cell, eid) + rset = self.req.eid_rset(reid) + eview = self.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.req, 'Basket')) + yield rtype, pendingid, jscall, label, reid, eview + + # should_* method extracted to allow overriding + + 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 + """ + return self.rinlined.etype_rtag(self.edited_entity.id, rschema, role, targettype) + + def should_display_inline_creation_form(self, rschema, existant, card): + """return true if a creation form should be inlined + + by default true if there is no related entity and we need at least one + """ + return not existant and card in '1+' + + 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) + + by default true if there is no related entity or if the relation has + multiple cardinality + """ + return not existant or card in '+*' + + +def etype_relation_field(etype, rtype, role='subject'): + eschema = AutomaticEntityForm.schema.eschema(etype) + return AutomaticEntityForm.field_by_name(rtype, role, eschema) diff -r 12bba5e13cf9 -r 742aff97dbf7 web/views/baseviews.py --- a/web/views/baseviews.py Fri Apr 24 19:49:47 2009 +0200 +++ b/web/views/baseviews.py Tue Apr 28 10:10:04 2009 +0200 @@ -14,13 +14,11 @@ __docformat__ = "restructuredtext en" -from warnings import warn - from rql import nodes from logilab.mtconverter import TransformError, html_escape -from cubicweb import Unauthorized, NoSelectableObject +from cubicweb import NoSelectableObject from cubicweb.selectors import yes, empty_rset from cubicweb.view import EntityView, AnyRsetView, View from cubicweb.common.uilib import cut, printable_value @@ -101,219 +99,6 @@ self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime)) -PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity', - 'owned_by', 'created_by', - 'in_state', 'wf_info_for', 'require_permission', - 'from_entity', 'to_entity', - 'see_also']) - -class PrimaryView(EntityView): - """the full view of an non final entity""" - id = 'primary' - title = _('primary') - show_attr_label = True - show_rel_label = True - skip_none = True - skip_attrs = ('eid', 'creation_date', 'modification_date') - skip_rels = () - main_related_section = True - - def html_headers(self): - """return a list of html headers (eg something to be inserted between - and of the returned page - - by default primary views are indexed - """ - return [] - - def cell_call(self, row, col): - self.row = row - # XXX move render_entity implementation here - self.render_entity(self.complete_entity(row, col)) - - def render_entity(self, entity): - """return html to display the given entity""" - siderelations = [] - self.render_entity_title(entity) - self.render_entity_metadata(entity) - # entity's attributes and relations, excluding meta data - # if the entity isn't meta itself - boxes = self._preinit_side_related(entity, siderelations) - if boxes: - self.w(u'
') - self.w(u'
') - self.w(u'
') - self.render_entity_attributes(entity, siderelations) - self.w(u'
') - self.content_navigation_components('navcontenttop') - if self.main_related_section: - self.render_entity_relations(entity, siderelations) - self.w(u'
') - if boxes: - self.w(u'
') - # side boxes - self.w(u'
') - self.render_side_related(entity, siderelations) - self.w(u'
') - self.w(u'
') - self.content_navigation_components('navcontentbottom') - - - def content_navigation_components(self, context): - self.w(u'
' % context) - for comp in self.vreg.possible_vobjects('contentnavigation', - self.req, self.rset, row=self.row, - view=self, context=context): - try: - comp.dispatch(w=self.w, row=self.row, view=self) - except NotImplementedError: - warn('component %s doesnt implement cell_call, please update' - % comp.__class__, DeprecationWarning) - comp.dispatch(w=self.w, view=self) - self.w(u'
') - - def iter_attributes(self, entity): - for rschema, targetschema in entity.e_schema.attribute_definitions(): - if rschema.type in self.skip_attrs: - continue - yield rschema, targetschema - - def iter_relations(self, entity): - skip = set(self.skip_rels) - skip.update(PRIMARY_SKIP_RELS) - for rschema, targetschemas, x in entity.e_schema.relation_definitions(): - if rschema.type in skip: - continue - yield rschema, targetschemas, x - - def render_entity_title(self, entity): - title = self.content_title(entity) # deprecate content_title? - if title: - self.w(u'

%s %s

' - % (entity.dc_type().capitalize(), title)) - - def content_title(self, entity): - """default implementation return an empty string""" - return u'' - - def render_entity_metadata(self, entity): - entity.view('metadata', w=self.w) - summary = self.summary(entity) # deprecate summary? - if summary: - self.w(u'
%s
' % summary) - - def summary(self, entity): - """default implementation return an empty string""" - return u'' - - def render_entity_attributes(self, entity, siderelations): - for rschema, targetschema in self.iter_attributes(entity): - attr = rschema.type - if targetschema.type in ('Password', 'Bytes'): - continue - try: - wdg = entity.get_widget(attr) - except Exception, ex: - value = entity.printable_value(attr, entity[attr], targetschema.type) - else: - value = wdg.render(entity) - if self.skip_none and (value is None or value == ''): - continue - if rschema.meta: - continue - self._render_related_entities(entity, rschema, value) - - def _preinit_side_related(self, entity, siderelations): - self._sideboxes = None - self._related_entities = [] - if hasattr(self, 'get_side_boxes_defs'): - self._sideboxes = [(label, rset) for label, rset in self.get_side_boxes_defs(entity) - if rset] - else: - eschema = entity.e_schema - maxrelated = self.req.property_value('navigation.related-limit') - for rschema, targetschemas, x in self.iter_relations(entity): - try: - related = entity.related(rschema.type, x, limit=maxrelated+1) - except Unauthorized: - continue - if not related: - continue - if self.is_side_related(rschema, eschema): - siderelations.append((rschema, related, x)) - continue - self._related_entities.append((rschema, related, x)) - self._boxes_in_context = list(self.vreg.possible_vobjects('boxes', self.req, self.rset, - row=self.row, view=self, - context='incontext')) - return self._sideboxes or self._boxes_in_context or self._related_entities or siderelations - - def render_entity_relations(self, entity, siderelations): - if self._related_entities: - for rschema, related, x in self._related_entities: - self._render_related_entities(entity, rschema, related, x) - - - def render_side_related(self, entity, siderelations): - """display side related relations: - non-meta in a first step, meta in a second step - """ - if self._sideboxes: - for label, rset in self._sideboxes: - self.w(u'
') - self.wview('sidebox', rset, title=label) - self.w(u'
') - elif siderelations: - self.w(u'
') - for relatedinfos in siderelations: - # if not relatedinfos[0].meta: - # continue - self._render_related_entities(entity, *relatedinfos) - self.w(u'
') - - if self._boxes_in_context: - for box in self._boxes_in_context: - try: - box.dispatch(w=self.w, row=self.row) - except NotImplementedError: - # much probably a context insensitive box, which only implements - # .call() and not cell_call() - box.dispatch(w=self.w) - - def is_side_related(self, rschema, eschema): - return rschema.meta and \ - not rschema.schema_relation() == eschema.schema_entity() - - def _render_related_entities(self, entity, rschema, related, - role='subject'): - if rschema.is_final(): - value = related - show_label = self.show_attr_label - else: - if not related: - return - show_label = self.show_rel_label - # if not too many entities, show them all in a list - maxrelated = self.req.property_value('navigation.related-limit') - if related.rowcount <= maxrelated: - if related.rowcount == 1: - value = self.view('incontext', related, row=0) - elif 1 < related.rowcount <= 5: - value = self.view('csv', related) - else: - value = '
' + self.view('simplelist', related) + '
' - # else show links to display related entities - else: - rql = related.printable_rql() - related.limit(maxrelated) - value = '
' + self.view('simplelist', related) - value += '[%s]' % (self.build_url(rql=rql), - self.req._('see them all')) - value += '
' - label = display_name(self.req, rschema.type, role) - self.field(label, value, show_label=show_label, tr=False) - - class SecondaryView(EntityView): id = 'secondary' title = _('secondary') @@ -586,7 +371,8 @@ except ImportError: pass # gae has no tableview module (yet) -from cubicweb.web.views import boxes, xmlrss +from cubicweb.web.views import boxes, xmlrss, primary +PrimaryView = class_moved(primary.PrimaryView) SideBoxView = class_moved(boxes.SideBoxView) XmlView = class_moved(xmlrss.XmlView) XmlItemView = class_moved(xmlrss.XmlItemView) diff -r 12bba5e13cf9 -r 742aff97dbf7 web/views/editforms.py --- a/web/views/editforms.py Fri Apr 24 19:49:47 2009 +0200 +++ b/web/views/editforms.py Tue Apr 28 10:10:04 2009 +0200 @@ -11,23 +11,21 @@ from simplejson import dumps -from logilab.common.decorators import iclassmethod from logilab.mtconverter import html_escape -from cubicweb import typed_eid from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity, specified_etype_implements, yes) from cubicweb.utils import make_uid from cubicweb.view import EntityView from cubicweb.common import tags -from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, uicfg -from cubicweb.web.form import (FieldNotFound, CompositeForm, EntityFieldsForm, - FormViewMixIn) +from cubicweb.web import stdmsgs +from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormViewMixIn from cubicweb.web.formfields import guess_field from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer, EntityCompositeFormRenderer, EntityInlinedFormRenderer) + _ = unicode def relation_id(eid, rtype, role, reid): @@ -125,317 +123,6 @@ self.w(form.form_render(renderer=renderer)) -class AutomaticEntityForm(EntityFieldsForm): - """base automatic form to edit any entity - - Designed to be flly generated from schema but highly configurable through: - * rtags (rcategories, rfields, rwidgets, inlined, rpermissions) - * various standard form parameters - - You can also easily customise it by adding/removing fields in - AutomaticEntityForm instances. - """ - id = 'edition' - - cwtarget = 'eformframe' - cssclass = 'entityForm' - copy_nav_params = True - form_buttons = [SubmitButton(stdmsgs.BUTTON_OK), - Button(stdmsgs.BUTTON_APPLY, cwaction='apply'), - Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] - attrcategories = ('primary', 'secondary') - # class attributes below are actually stored in the uicfg module since we - # don't want them to be reloaded - rcategories = uicfg.rcategories - rfields = uicfg.rfields - rwidgets = uicfg.rwidgets - rinlined = uicfg.rinlined - rpermissions_overrides = uicfg.rpermissions_overrides - - @classmethod - def vreg_initialization_completed(cls): - """set default category tags for relations where it's not yet defined in - the category relation tags - """ - for eschema in cls.schema.entities(): - for rschema, tschemas, role in eschema.relation_definitions(True): - for tschema in tschemas: - if role == 'subject': - X, Y = eschema, tschema - card = rschema.rproperty(X, Y, 'cardinality')[0] - composed = rschema.rproperty(X, Y, 'composite') == 'object' - else: - X, Y = tschema, eschema - card = rschema.rproperty(X, Y, 'cardinality')[1] - composed = rschema.rproperty(X, Y, 'composite') == 'subject' - if not cls.rcategories.rtag(rschema, role, X, Y): - if card in '1+': - if not rschema.is_final() and composed: - category = 'generated' - else: - category = 'primary' - elif rschema.is_final(): - category = 'secondary' - else: - category = 'generic' - cls.rcategories.set_rtag(category, rschema, role, X, Y) - - @classmethod - def erelations_by_category(cls, entity, categories=None, permission=None, rtags=None): - """return a list of (relation schema, target schemas, role) matching - categories and permission - """ - if categories is not None: - if not isinstance(categories, (list, tuple, set, frozenset)): - categories = (categories,) - if not isinstance(categories, (set, frozenset)): - categories = frozenset(categories) - eschema = entity.e_schema - if rtags is None: - rtags = cls.rcategories - permsoverrides = cls.rpermissions_overrides - if entity.has_eid(): - eid = entity.eid - else: - eid = None - for rschema, targetschemas, role in eschema.relation_definitions(True): - # check category first, potentially lower cost than checking - # permission which may imply rql queries - if categories is not None: - targetschemas = [tschema for tschema in targetschemas - if rtags.etype_rtag(eschema, rschema, role, tschema) in categories] - if not targetschemas: - continue - if permission is not None: - # tag allowing to hijack the permission machinery when - # permission is not verifiable until the entity is actually - # created... - if eid is None and '%s_on_new' % permission in permsoverrides.etype_rtags(eschema, rschema, role): - yield (rschema, targetschemas, role) - continue - if rschema.is_final(): - if not rschema.has_perm(entity.req, permission, eid): - continue - elif role == 'subject': - if not ((eid is None and rschema.has_local_role(permission)) or - rschema.has_perm(entity.req, permission, fromeid=eid)): - continue - # on relation with cardinality 1 or ?, we need delete perm as well - # if the relation is already set - if (permission == 'add' - and rschema.cardinality(eschema, targetschemas[0], role) in '1?' - and eid and entity.related(rschema.type, role) - and not rschema.has_perm(entity.req, 'delete', fromeid=eid, - toeid=entity.related(rschema.type, role)[0][0])): - continue - elif role == 'object': - if not ((eid is None and rschema.has_local_role(permission)) or - rschema.has_perm(entity.req, permission, toeid=eid)): - continue - # on relation with cardinality 1 or ?, we need delete perm as well - # if the relation is already set - if (permission == 'add' - and rschema.cardinality(targetschemas[0], eschema, role) in '1?' - and eid and entity.related(rschema.type, role) - and not rschema.has_perm(entity.req, 'delete', toeid=eid, - fromeid=entity.related(rschema.type, role)[0][0])): - continue - yield (rschema, targetschemas, role) - - @classmethod - def esrelations_by_category(cls, entity, categories=None, permission=None): - """filter out result of relations_by_category(categories, permission) by - removing final relations - - return a sorted list of (relation's label, relation'schema, role) - """ - result = [] - for rschema, ttypes, role in cls.erelations_by_category( - entity, categories, permission): - if rschema.is_final(): - continue - result.append((rschema.display_name(entity.req, role), rschema, role)) - return sorted(result) - - @iclassmethod - def field_by_name(cls_or_self, name, role='subject', 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) - except FieldNotFound: # XXX should raise more explicit exception - if eschema is None or not name in cls_or_self.schema: - raise - rschema = cls_or_self.schema.rschema(name) - fieldcls = cls_or_self.rfields.etype_rtag(eschema, rschema, role) - if fieldcls: - return fieldcls(name=name, role=role, eidparam=True) - widget = cls_or_self.rwidgets.etype_rtag(eschema, rschema, role) - if widget: - field = guess_field(eschema, rschema, role, - eidparam=True, widget=widget) - else: - field = guess_field(eschema, rschema, role, eidparam=True) - if field is None: - raise - return field - - def __init__(self, *args, **kwargs): - super(AutomaticEntityForm, self).__init__(*args, **kwargs) - entity = self.edited_entity - if entity.has_eid(): - entity.complete() - for rschema, role in self.editable_attributes(): - try: - self.field_by_name(rschema.type, role) - continue # explicitly specified - except FieldNotFound: - # has to be guessed - try: - field = self.field_by_name(rschema.type, role, - eschema=entity.e_schema) - self.fields.append(field) - except FieldNotFound: - # meta attribute such as _format - continue - self.maxrelitems = self.req.property_value('navigation.related-limit') - self.force_display = bool(self.req.form.get('__force_display')) - - @property - def related_limit(self): - if self.force_display: - return None - return self.maxrelitems + 1 - - def relations_by_category(self, categories=None, permission=None): - """return a list of (relation schema, target schemas, role) matching - given category(ies) and permission - """ - return self.erelations_by_category(self.edited_entity, categories, - permission) - - def inlined_relations(self): - """return a list of (relation schema, target schemas, role) matching - given category(ies) and permission - """ - # we'll need an initialized varmaker if there are some inlined relation - self.initialize_varmaker() - return self.erelations_by_category(self.edited_entity, True, 'add', self.rinlined) - - def srelations_by_category(self, categories=None, permission=None): - """filter out result of relations_by_category(categories, permission) by - removing final relations - - return a sorted list of (relation's label, relation'schema, role) - """ - return self.esrelations_by_category(self.edited_entity, categories, - permission) - - def action(self): - """return the form's action attribute. Default to validateform if not - explicitly overriden. - """ - try: - return self._action - except AttributeError: - return self.build_url('validateform') - - def set_action(self, value): - """override default action""" - self._action = value - - action = property(action, set_action) - - def editable_attributes(self): - """return a list of (relation schema, role) to edit for the entity""" - return [(rschema, x) for rschema, _, x in self.relations_by_category( - self.attrcategories, 'add') if rschema != 'eid'] - - 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.req.get_pending_deletes(entity.eid) - for label, rschema, role in self.srelations_by_category('generic', 'add'): - relatedrset = entity.related(rschema, role, limit=self.related_limit) - if rschema.has_perm(self.req, 'delete'): - toggable_rel_link_func = toggable_relation_link - else: - toggable_rel_link_func = lambda x, y, z: u'' - related = [] - for row in xrange(relatedrset.rowcount): - nodeid = relation_id(entity.eid, rschema, role, - relatedrset[row][0]) - if nodeid in pending_deletes: - status = u'pendingDelete' - label = '+' - else: - status = u'' - label = 'x' - dellink = toggable_rel_link_func(entity.eid, nodeid, label) - eview = self.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.req.get_pending_inserts(eid)) - for pendingid in pending_inserts: - eidfrom, rtype, eidto = pendingid.split(':') - if typed_eid(eidfrom) == eid: # subject - label = display_name(self.req, rtype, 'subject') - reid = eidto - else: - label = display_name(self.req, rtype, 'object') - reid = eidfrom - jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \ - % (pendingid, cell, eid) - rset = self.req.eid_rset(reid) - eview = self.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.req, 'Basket')) - yield rtype, pendingid, jscall, label, reid, eview - - # should_* method extracted to allow overriding - - 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 - """ - return self.rinlined.etype_rtag(self.edited_entity.id, rschema, role, targettype) - - def should_display_inline_creation_form(self, rschema, existant, card): - """return true if a creation form should be inlined - - by default true if there is no related entity and we need at least one - """ - return not existant and card in '1+' - - 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) - - by default true if there is no related entity or if the relation has - multiple cardinality - """ - return not existant or card in '+*' - -def etype_relation_field(etype, rtype, role='subject'): - eschema = AutomaticEntityForm.schema.eschema(etype) - return AutomaticEntityForm.field_by_name(rtype, role, eschema) - class EditionFormView(FormViewMixIn, EntityView): """display primary entity edition form""" id = 'edition' diff -r 12bba5e13cf9 -r 742aff97dbf7 web/views/primary.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/views/primary.py Tue Apr 28 10:10:04 2009 +0200 @@ -0,0 +1,226 @@ +"""The default primary view + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" + +from warnings import warn + +from cubicweb import Unauthorized +from cubicweb.view import EntityView + +_ = unicode + +PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity', + 'owned_by', 'created_by', + 'in_state', 'wf_info_for', 'require_permission', + 'from_entity', 'to_entity', + 'see_also']) + +class PrimaryView(EntityView): + """the full view of an non final entity""" + id = 'primary' + title = _('primary') + show_attr_label = True + show_rel_label = True + skip_none = True + skip_attrs = ('eid', 'creation_date', 'modification_date') + skip_rels = () + main_related_section = True + + def html_headers(self): + """return a list of html headers (eg something to be inserted between + and of the returned page + + by default primary views are indexed + """ + return [] + + def cell_call(self, row, col): + self.row = row + # XXX move render_entity implementation here + self.render_entity(self.complete_entity(row, col)) + + def render_entity(self, entity): + """return html to display the given entity""" + siderelations = [] + self.render_entity_title(entity) + self.render_entity_metadata(entity) + # entity's attributes and relations, excluding meta data + # if the entity isn't meta itself + boxes = self._preinit_side_related(entity, siderelations) + if boxes: + self.w(u'
') + self.w(u'
') + self.w(u'
') + self.render_entity_attributes(entity, siderelations) + self.w(u'
') + self.content_navigation_components('navcontenttop') + if self.main_related_section: + self.render_entity_relations(entity, siderelations) + self.w(u'
') + if boxes: + self.w(u'
') + # side boxes + self.w(u'
') + self.render_side_related(entity, siderelations) + self.w(u'
') + self.w(u'
') + self.content_navigation_components('navcontentbottom') + + + def content_navigation_components(self, context): + self.w(u'
' % context) + for comp in self.vreg.possible_vobjects('contentnavigation', + self.req, self.rset, row=self.row, + view=self, context=context): + try: + comp.dispatch(w=self.w, row=self.row, view=self) + except NotImplementedError: + warn('component %s doesnt implement cell_call, please update' + % comp.__class__, DeprecationWarning) + comp.dispatch(w=self.w, view=self) + self.w(u'
') + + def iter_attributes(self, entity): + for rschema, targetschema in entity.e_schema.attribute_definitions(): + if rschema.type in self.skip_attrs: + continue + yield rschema, targetschema + + def iter_relations(self, entity): + skip = set(self.skip_rels) + skip.update(PRIMARY_SKIP_RELS) + for rschema, targetschemas, x in entity.e_schema.relation_definitions(): + if rschema.type in skip: + continue + yield rschema, targetschemas, x + + def render_entity_title(self, entity): + title = self.content_title(entity) # deprecate content_title? + if title: + self.w(u'

%s %s

' + % (entity.dc_type().capitalize(), title)) + + def content_title(self, entity): + """default implementation return an empty string""" + return u'' + + def render_entity_metadata(self, entity): + entity.view('metadata', w=self.w) + summary = self.summary(entity) # deprecate summary? + if summary: + self.w(u'
%s
' % summary) + + def summary(self, entity): + """default implementation return an empty string""" + return u'' + + def render_entity_attributes(self, entity, siderelations): + for rschema, targetschema in self.iter_attributes(entity): + attr = rschema.type + if targetschema.type in ('Password', 'Bytes'): + continue + try: + wdg = entity.get_widget(attr) + except Exception, ex: + value = entity.printable_value(attr, entity[attr], targetschema.type) + else: + value = wdg.render(entity) + if self.skip_none and (value is None or value == ''): + continue + if rschema.meta: + continue + self._render_related_entities(entity, rschema, value) + + def _preinit_side_related(self, entity, siderelations): + self._sideboxes = None + self._related_entities = [] + if hasattr(self, 'get_side_boxes_defs'): + self._sideboxes = [(label, rset) for label, rset in self.get_side_boxes_defs(entity) + if rset] + else: + eschema = entity.e_schema + maxrelated = self.req.property_value('navigation.related-limit') + for rschema, targetschemas, x in self.iter_relations(entity): + try: + related = entity.related(rschema.type, x, limit=maxrelated+1) + except Unauthorized: + continue + if not related: + continue + if self.is_side_related(rschema, eschema): + siderelations.append((rschema, related, x)) + continue + self._related_entities.append((rschema, related, x)) + self._boxes_in_context = list(self.vreg.possible_vobjects('boxes', self.req, self.rset, + row=self.row, view=self, + context='incontext')) + return self._sideboxes or self._boxes_in_context or self._related_entities or siderelations + + def render_entity_relations(self, entity, siderelations): + if self._related_entities: + for rschema, related, x in self._related_entities: + self._render_related_entities(entity, rschema, related, x) + + + def render_side_related(self, entity, siderelations): + """display side related relations: + non-meta in a first step, meta in a second step + """ + if self._sideboxes: + for label, rset in self._sideboxes: + self.w(u'
') + self.wview('sidebox', rset, title=label) + self.w(u'
') + elif siderelations: + self.w(u'
') + for relatedinfos in siderelations: + # if not relatedinfos[0].meta: + # continue + self._render_related_entities(entity, *relatedinfos) + self.w(u'
') + + if self._boxes_in_context: + for box in self._boxes_in_context: + try: + box.dispatch(w=self.w, row=self.row) + except NotImplementedError: + # much probably a context insensitive box, which only implements + # .call() and not cell_call() + box.dispatch(w=self.w) + + def is_side_related(self, rschema, eschema): + return rschema.meta and \ + not rschema.schema_relation() == eschema.schema_entity() + + def _render_related_entities(self, entity, rschema, related, + role='subject'): + if rschema.is_final(): + value = related + show_label = self.show_attr_label + else: + if not related: + return + show_label = self.show_rel_label + # if not too many entities, show them all in a list + maxrelated = self.req.property_value('navigation.related-limit') + if related.rowcount <= maxrelated: + if related.rowcount == 1: + value = self.view('incontext', related, row=0) + elif 1 < related.rowcount <= 5: + value = self.view('csv', related) + else: + value = '
' + self.view('simplelist', related) + '
' + # else show links to display related entities + else: + rql = related.printable_rql() + related.limit(maxrelated) + value = '
' + self.view('simplelist', related) + value += '[%s]' % (self.build_url(rql=rql), + self.req._('see them all')) + value += '
' + label = display_name(self.req, rschema.type, role) + self.field(label, value, show_label=show_label, tr=False) +