class EntityStartupView(EntityView): """base class for entity views which may also be applied to None result set (usually a default rql is provided by the view class) """ __select__ = none_rset() | non_final_entity() default_rql = None def __init__(self, req, rset=None, **kwargs): super(EntityStartupView, self).__init__(req, rset=rset, **kwargs) if rset is None: # this instance is not in the "entityview" category self.category = 'startupview' def startup_rql(self): """return some rql to be executed if the result set is None""" return self.default_rql def no_entities(self, **kwargs): """override to display something when no entities were found""" pass def call(self, **kwargs): """override call to execute rql returned by the .startup_rql method if necessary """ rset = self.cw_rset if rset is None: rset = self.cw_rset = self._cw.execute(self.startup_rql()) if rset: for i in range(len(rset)): self.wview(self.__regid__, rset, row=i, **kwargs) else: self.no_entities(**kwargs)
class EntityView(View): """base class for views applying on an entity (i.e. uniform result set)""" __select__ = non_final_entity() category = _('entityview') def call(self, **kwargs): if self.cw_rset is None: # * cw_extra_kwargs is the place where extra selection arguments are # stored # * when calling req.view('somevid', entity=entity), 'entity' ends # up in cw_extra_kwargs and kwargs # # handle that to avoid a TypeError with a sanity check # # Notice that could probably be avoided by handling entity_call in # .render entity = self.cw_extra_kwargs.pop('entity') if 'entity' in kwargs: assert kwargs.pop('entity') is entity self.entity_call(entity, **kwargs) else: super(EntityView, self).call(**kwargs) def cell_call(self, row, col, **kwargs): self.entity_call(self.cw_rset.get_entity(row, col), **kwargs) def entity_call(self, entity, **kwargs): raise NotImplementedError('%r %r' % (self.__regid__, self.__class__))
class SearchForAssociationView(EntityView): """view called by the edition view when the user asks to search for something to link to the edited eid """ __regid__ = 'search-associate' __select__ = (one_line_rset() & match_search_state('linksearch') & non_final_entity()) title = _('search for association') def cell_call(self, row, col): rset, vid, divid, paginate = self.filter_box_context_info() self.cw_rset = rset self.w(u'<div id="%s">' % divid) self.paginate() self.wview(vid, rset, 'noresult') self.w(u'</div>') @cached def filter_box_context_info(self): entity = self.cw_rset.get_entity(0, 0) role, eid, rtype, etype = self._cw.search_state[1] assert entity.eid == int(eid) # the default behaviour is to fetch all unrelated entities and display # them. Use fetch_order and not fetch_unrelated_order as sort method # since the latter is mainly there to select relevant items in the combo # box, it doesn't give interesting result in this context rql, args = entity.cw_unrelated_rql(rtype, etype, role, ordermethod='fetch_order', vocabconstraints=False) rset = self._cw.execute(rql, args) return rset, 'list', "search-associate-content", True
class ManagePermissionsAction(action.Action): __regid__ = 'managepermission' __select__ = (action.Action.__select__ & one_line_rset() & non_final_entity() & match_user_groups('managers')) title = _('manage permissions') category = 'moreactions' order = 15 def url(self): return self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0).absolute_url(vid='security')
class EntityCtxComponent(CtxComponent): """base class for boxes related to a single entity""" __select__ = CtxComponent.__select__ & non_final_entity() & one_line_rset() context = 'incontext' contextual = True def __init__(self, *args, **kwargs): super(EntityCtxComponent, self).__init__(*args, **kwargs) try: entity = kwargs['entity'] except KeyError: entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0) self.entity = entity def layout_select_args(self): args = super(EntityCtxComponent, self).layout_select_args() args['entity'] = self.entity return args @property def domid(self): return domid(self.__regid__) + str(self.entity.eid) def lazy_view_holder(self, w, entity, oid, registry='views'): """add a holder and return a URL that may be used to replace this holder by the html generate by the view specified by registry and identifier. Registry defaults to 'views'. """ holderid = '%sHolder' % self.domid w(u'<div id="%s"></div>' % holderid) params = self.cw_extra_kwargs.copy() params.pop('view', None) params.pop('entity', None) form = params.pop('formparams', {}) if entity.has_eid(): eid = entity.eid else: eid = None form['etype'] = entity.cw_etype form['tempEid'] = entity.eid args = [json_dumps(x) for x in (registry, oid, eid, params)] return self._cw.ajax_replace_url(holderid, fname='render', arg=args, **form)
class ViewAction(action.Action): __regid__ = 'view' __select__ = (action.Action.__select__ & match_user_groups('users', 'managers') & view_is_not_default_view() & non_final_entity()) title = _('view') category = 'mainactions' order = 0 def url(self): params = self._cw.form.copy() for param in ('vid', '__message') + controller.NAV_FORM_PARAMETERS: params.pop(param, None) if self._cw.ajax_request: path = 'view' if self.cw_rset is not None: params = {'rql': self.cw_rset.printable_rql()} else: path = self._cw.relative_path(includeparams=False) return self._cw.build_url(path, **params)
class EditionFormView(FormViewMixIn, EntityView): """display primary entity edition form""" __regid__ = 'edition' # add yes() so it takes precedence over deprecated views in baseforms, # though not baseforms based customized view __select__ = one_line_rset() & non_final_entity() & yes() form_id = 'edition' title = _('modification') def cell_call(self, row, col, **kwargs): entity = self.cw_rset.complete_entity(row, col) self.render_form(entity) def render_form(self, entity): """fetch and render the form""" self.form_title(entity) form = self._cw.vreg['forms'].select(self.form_id, self._cw, entity=entity, submitmsg=self.submited_message()) self.init_form(form, entity) form.render(w=self.w) def init_form(self, form, entity): """customize your form before rendering here""" pass def form_title(self, entity): """the form view title""" ptitle = self._cw._(self.title) self.w(u'<div class="formTitle"><span>%s %s</span></div>' % (entity.dc_type(), ptitle and '(%s)' % ptitle)) def submited_message(self): """return the message that will be displayed on successful edition""" return self._cw._('entity edited')
class EntityFieldsForm(FieldsForm): """This class is designed for forms used to edit some entities. It should handle for you all the underlying stuff necessary to properly work with the generic :class:`~cubicweb.web.views.editcontroller.EditController`. """ __regid__ = 'base' __select__ = (match_kwargs('entity') | (one_line_rset() & non_final_entity())) domid = 'entityForm' uicfg_aff = uicfg.autoform_field uicfg_affk = uicfg.autoform_field_kwargs @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(EntityFieldsForm, cls_or_self).field_by_name(name, role) except form.FieldNotFound: if eschema is None or role is None or name not in eschema.schema: raise rschema = eschema.schema.rschema(name) # XXX use a sample target type. Document this. tschemas = rschema.targets(eschema, role) fieldclass = cls_or_self.uicfg_aff.etype_get( eschema, rschema, role, tschemas[0]) kwargs = cls_or_self.uicfg_affk.etype_get(eschema, rschema, role, tschemas[0]) if kwargs is None: kwargs = {} if fieldclass: if not isinstance(fieldclass, type): return fieldclass # already an instance kwargs['fieldclass'] = fieldclass if isinstance(cls_or_self, type): req = None else: req = cls_or_self._cw field = guess_field(eschema, rschema, role, req=req, eidparam=True, **kwargs) if field is None: raise return field def __init__(self, _cw, rset=None, row=None, col=None, **kwargs): try: self.edited_entity = kwargs.pop('entity') except KeyError: self.edited_entity = rset.complete_entity(row or 0, col or 0) msg = kwargs.pop('submitmsg', None) super(EntityFieldsForm, self).__init__(_cw, rset, row, col, **kwargs) self.uicfg_aff = self._cw.vreg['uicfg'].select( 'autoform_field', self._cw, entity=self.edited_entity) self.uicfg_affk = self._cw.vreg['uicfg'].select( 'autoform_field_kwargs', self._cw, entity=self.edited_entity) self.add_hidden('__type', self.edited_entity.cw_etype, eidparam=True) self.add_hidden('eid', self.edited_entity.eid) self.add_generation_time() # mainform default to true in parent, hence default to True if kwargs.get('mainform', True) or kwargs.get('mainentity', False): self.add_hidden(u'__maineid', self.edited_entity.eid) # If we need to directly attach the new object to another one if '__linkto' in self._cw.form: if msg: msg = '%s %s' % (msg, self._cw._('and linked')) else: msg = self._cw._('entity linked') if msg: msgid = self._cw.set_redirect_message(msg) self.add_hidden('_cwmsgid', msgid) def add_generation_time(self): # use %f to prevent (unlikely) display in exponential format self.add_hidden('__form_generation_time', '%.6f' % time.time(), eidparam=True) def add_linkto_hidden(self): """add the __linkto hidden field used to directly attach the new object to an existing other one when the relation between those two is not already present in the form. Warning: this method must be called only when all form fields are setup """ for (rtype, role), eids in self.linked_to.items(): # if the relation is already setup by a form field, do not add it # in a __linkto hidden to avoid setting it twice in the controller try: self.field_by_name(rtype, role) except form.FieldNotFound: for eid in eids: self.add_hidden('__linkto', '%s:%s:%s' % (rtype, eid, role)) def render(self, *args, **kwargs): self.add_linkto_hidden() return super(EntityFieldsForm, self).render(*args, **kwargs) @property @cached def linked_to(self): linked_to = {} # case where this is an embeded creation form try: eid = int(self.cw_extra_kwargs['peid']) except (KeyError, ValueError): # When parent is being created, its eid is not numeric (e.g. 'A') # hence ValueError. pass else: ltrtype = self.cw_extra_kwargs['rtype'] ltrole = neg_role(self.cw_extra_kwargs['role']) linked_to[(ltrtype, ltrole)] = [eid] # now consider __linkto if the current form is the main form try: self.field_by_name('__maineid') except form.FieldNotFound: return linked_to for linkto in self._cw.list_form_param('__linkto'): ltrtype, eid, ltrole = linkto.split(':') linked_to.setdefault((ltrtype, ltrole), []).append(int(eid)) return linked_to 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 """ if self.force_session_key is not None: return self.force_session_key # XXX if this is a json request, suppose we should redirect to the # entity primary view if self._cw.ajax_request and self.edited_entity.has_eid(): return '%s#%s' % (self.edited_entity.absolute_url(), self.domid) # XXX we should not consider some url parameters that may lead to # different url after a validation error return '%s#%s' % (self._cw.url(), self.domid) def default_renderer(self): return self._cw.vreg['formrenderers'].select(self.form_renderer_id, self._cw, rset=self.cw_rset, row=self.cw_row, col=self.cw_col, entity=self.edited_entity) def should_display_add_new_relation_link(self, rschema, existant, card): return False # controller side method (eg POST reception handling) def actual_eid(self, eid): # should be either an int (existant entity) or a variable (to be # created entity) assert eid or eid == 0, repr(eid) # 0 is a valid eid try: return int(eid) except ValueError: try: return self._cw.data['eidmap'][eid] except KeyError: self._cw.data['eidmap'][eid] = None return None def editable_relations(self): return ()
class EditBox(component.CtxComponent): """ box with all actions impacting the entity displayed: edit, copy, delete change state, add related entities... """ __regid__ = 'edit_box' title = _('actions') order = 2 contextual = True __select__ = component.CtxComponent.__select__ & non_final_entity() def init_rendering(self): super(EditBox, self).init_rendering() _ = self._cw._ self._menus_in_order = [] self._menus_by_id = {} # build list of actions actions = self._cw.vreg['actions'].possible_actions(self._cw, self.cw_rset, view=self.cw_extra_kwargs['view']) other_menu = self._get_menu('moreactions', _('more actions')) for category, defaultmenu in (('mainactions', self), ('moreactions', other_menu), ('addrelated', None)): for action in actions.get(category, ()): if action.submenu: menu = self._get_menu(action.submenu) else: menu = defaultmenu action.fill_menu(self, menu) # if we've nothing but actions in the other_menu, add them directly into the box if not self.items and len(self._menus_by_id) == 1 and not other_menu.is_empty(): self.items = other_menu.items else: # ensure 'more actions' menu appears last self._menus_in_order.remove(other_menu) self._menus_in_order.append(other_menu) for submenu in self._menus_in_order: self.add_submenu(self, submenu) if not self.items: raise component.EmptyComponent() def render_title(self, w): title = self._cw._(self.title) if self.cw_rset: etypes = self.cw_rset.column_types(0) if len(etypes) == 1: plural = self.cw_rset.rowcount > 1 and 'plural' or '' etypelabel = display_name(self._cw, next(iter(etypes)), plural) title = u'%s - %s' % (title, etypelabel.lower()) w(title) def render_body(self, w): self.render_items(w) def _get_menu(self, id, title=None, label_prefix=None): try: return self._menus_by_id[id] except KeyError: if title is None: title = self._cw._(id) self._menus_by_id[id] = menu = htmlwidgets.BoxMenu(title) menu.label_prefix = label_prefix self._menus_in_order.append(menu) return menu def add_submenu(self, box, submenu, label_prefix=None): appendanyway = getattr(submenu, 'append_anyway', False) if len(submenu.items) == 1 and not appendanyway: boxlink = submenu.items[0] if submenu.label_prefix: # XXX iirk if hasattr(boxlink, 'label'): boxlink.label = u'%s %s' % (submenu.label_prefix, boxlink.label) else: boxlink = u'%s %s' % (submenu.label_prefix, boxlink) box.append(boxlink) elif submenu.items: box.append(submenu) elif appendanyway: box.append(xml_escape(submenu.label))
class AddRelatedActions(action.Action): """fill 'addrelated' sub-menu of the actions box""" __regid__ = 'addrelated' __select__ = action.Action.__select__ & one_line_rset() & non_final_entity( ) submenu = _('addrelated') order = 17 def fill_menu(self, box, menu): # when there is only one item in the sub-menu, replace the sub-menu by # item's title prefixed by 'add' menu.label_prefix = self._cw._('add') super(AddRelatedActions, self).fill_menu(box, menu) def redirect_params(self, entity): return { '__redirectpath': entity.rest_path(), # should not be url quoted! '__redirectvid': self._cw.form.get('vid', '') } def actual_actions(self): entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0) eschema = entity.e_schema params = self.redirect_params(entity) for rschema, teschema, role in self.add_related_schemas(entity): if rschema.role_rdef(eschema, teschema, role).role_cardinality(role) in '1?': if entity.related(rschema, role): continue if role == 'subject': label = 'add %s %s %s %s' % (eschema, rschema, teschema, role) url = self.linkto_url(entity, rschema, teschema, 'object', **params) else: label = 'add %s %s %s %s' % (teschema, rschema, eschema, role) url = self.linkto_url(entity, rschema, teschema, 'subject', **params) yield self.build_action(self._cw._(label), url) def add_related_schemas(self, entity): """this is actually used ui method to generate 'addrelated' actions from the schema. If you don't want any auto-generated actions, you should overrides this method to return an empty list. If you only want some, you can configure them by using uicfg.actionbox_appearsin_addmenu """ appearsin_addmenu = self._cw.vreg['uicfg'].select( 'actionbox_appearsin_addmenu', self._cw, entity=entity) req = self._cw eschema = entity.e_schema for role, rschemas in (('subject', eschema.subject_relations()), ('object', eschema.object_relations())): for rschema in rschemas: if rschema.final: continue for teschema in rschema.targets(eschema, role): if not appearsin_addmenu.etype_get(eschema, rschema, role, teschema): continue rdef = rschema.role_rdef(eschema, teschema, role) # check the relation can be added # XXX consider autoform_permissions_overrides? if role == 'subject' and not rdef.has_perm( req, 'add', fromeid=entity.eid): continue if role == 'object' and not rdef.has_perm( req, 'add', toeid=entity.eid): continue # check the target types can be added as well if teschema.may_have_permission('add', req): yield rschema, teschema, role def linkto_url(self, entity, rtype, etype, target, **kwargs): return self._cw.vreg["etypes"].etype_class(etype).cw_create_url( self._cw, __linkto='%s:%s:%s' % (rtype, entity.eid, target), **kwargs)
class FilterBox(FacetFilterMixIn, component.CtxComponent): """filter results of a query""" __regid__ = 'facet.filterbox' __select__ = ((non_final_entity() & has_facets()) | contextview_selector()) # can't use has_facets because of # contextview mecanism context = 'left' # XXX doesn't support 'incontext', only 'left' or 'right' title = _('facet.filters') visible = True # functionality provided by the search box by default order = 1 bk_linkbox_template = u'<div class="facetTitle">%s</div>' def render_body(self, w, **kwargs): req = self._cw rset, vid, divid, paginate = self._get_context() assert len(rset) > 1 if vid is None: vid = req.form.get('vid') if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm( req, 'add'): w(self.bookmark_link(rset)) w(self.focus_link(rset)) hiddens = {} for param in ('subvid', 'vtitle'): if param in req.form: hiddens[param] = req.form[param] self.generate_form(w, rset, divid, vid, paginate=paginate, hiddens=hiddens, **self.cw_extra_kwargs) def _get_context(self): view = self.cw_extra_kwargs.get('view') context = getattr(view, 'filter_box_context_info', lambda: None)() if context: rset, vid, divid, paginate = context else: rset = self.cw_rset vid, divid = None, 'contentmain' paginate = view and view.paginable return rset, vid, divid, paginate def bookmark_link(self, rset): req = self._cw bk_path = u'rql=%s' % req.url_quote(rset.printable_rql()) if req.form.get('vid'): bk_path += u'&vid=%s' % req.url_quote(req.form['vid']) bk_path = u'view?' + bk_path bk_title = req._('my custom search') linkto = u'bookmarked_by:%s:subject' % req.user.eid bkcls = req.vreg['etypes'].etype_class('Bookmark') bk_add_url = bkcls.cw_create_url(req, path=bk_path, title=bk_title, __linkto=linkto) bk_base_url = bkcls.cw_create_url(req, title=bk_title, __linkto=linkto) bk_link = u'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % ( xml_escape(bk_base_url), xml_escape(bk_add_url), req._('bookmark this search')) return self.bk_linkbox_template % bk_link def focus_link(self, rset): return self.bk_linkbox_template % tags.a( self._cw._('focus on this selection'), href=self._cw.url(), id='focusLink')
class InlineEntityEditionFormView(f.FormViewMixIn, EntityView): """ :attr peid: the parent entity's eid hosting the inline form :attr rtype: the relation bridging `etype` and `peid` :attr role: the role played by the `peid` in the relation :attr pform: the parent form where this inlined form is being displayed """ __regid__ = 'inline-edition' __select__ = non_final_entity() & match_kwargs('peid', 'rtype') _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype') removejs = "removeInlinedEntity('%s', '%s', '%s')" form_renderer_id = 'inline' # make pylint happy peid = rtype = role = pform = etype = None def __init__(self, *args, **kwargs): for attr in self._select_attrs: # don't pop attributes from kwargs, so the end-up in # self.cw_extra_kwargs which is then passed to the edition form (see # the .form method) setattr(self, attr, kwargs.get(attr)) super(InlineEntityEditionFormView, self).__init__(*args, **kwargs) def _entity(self): assert self.cw_row is not None, self return self.cw_rset.get_entity(self.cw_row, self.cw_col) @property def petype(self): assert isinstance(self.peid, int) pentity = self._cw.entity_from_eid(self.peid) return pentity.e_schema.type @property @cached def form(self): entity = self._entity() form = self._cw.vreg['forms'].select( 'edition', self._cw, entity=entity, formtype='inlined', form_renderer_id=self.form_renderer_id, copy_nav_params=False, mainform=False, parent_form=self.pform, **self.cw_extra_kwargs) if self.pform is None: form.restore_previous_post(form.session_key()) # assert form.parent_form self.add_hiddens(form, entity) return form def cell_call(self, row, col, i18nctx, **kwargs): """ :param peid: the parent entity's eid hosting the inline form :param rtype: the relation bridging `etype` and `peid` :param role: the role played by the `peid` in the relation """ entity = self._entity() divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % ( self.peid, self.rtype, entity.eid) self.render_form(i18nctx, divonclick=divonclick, **kwargs) def _get_removejs(self): """ Don't display the remove link in edition form if the cardinality is 1. Handled in InlineEntityCreationFormView for creation form. """ entity = self._entity() rdef = entity.e_schema.rdef(self.rtype, neg_role(self.role), self.petype) card = rdef.role_cardinality(self.role) if card == '1': # don't display remove link return None # if cardinality is 1..n (+), dont display link to remove an inlined form for the first form # allowing to edit the relation. To detect so: # # * if parent form (pform) is None, we're generated through an ajax call and so we know this # is not the first form # # * if parent form is not None, look for previous InlinedFormField in the parent's form # fields if card == '+' and self.pform is not None: # retrieve all field'views handling this relation and return None if we're the first of # them first_view = next( iter((f.view for f in self.pform.fields if isinstance(f, InlinedFormField) and f.view.rtype == self.rtype and f.view.role == self.role))) if self == first_view: return None return self.removejs and self.removejs % (self.peid, self.rtype, entity.eid) def render_form(self, i18nctx, **kwargs): """fetch and render the form""" entity = self._entity() divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid) title = self.form_title(entity, i18nctx) removejs = self._get_removejs() countkey = '%s_count' % self.rtype try: self._cw.data[countkey] += 1 except KeyError: self._cw.data[countkey] = 1 self.form.render(w=self.w, divid=divid, title=title, removejs=removejs, i18nctx=i18nctx, counter=self._cw.data[countkey], **kwargs) def form_title(self, entity, i18nctx): return self._cw.pgettext(i18nctx, entity.cw_etype) def add_hiddens(self, form, entity): """to ease overriding (see cubes.vcsfile.views.forms for instance)""" iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid) # * str(self.rtype) in case it's a schema object # * neged_role() since role is the for parent entity, we want the role # of the inlined entity form.add_hidden(name=str(self.rtype), value=self.peid, role=neg_role(self.role), eidparam=True, id=iid) def keep_entity(self, form, entity): if not entity.has_eid(): return True # are we regenerating form because of a validation error? if form.form_previous_values: cdvalues = self._cw.list_form_param( eid_param(self.rtype, self.peid), form.form_previous_values) if str(entity.eid) not in cdvalues: return False return True
class RSSFeedURL(Component): __regid__ = 'rss_feed_url' __select__ = non_final_entity() def feed_url(self): return self._cw.build_url(rql=self.cw_rset.limited_rql(), vid='rss')
class AutoClickAndEditFormView(EntityView): __regid__ = 'reledit' __select__ = non_final_entity() & match_kwargs('rtype') # ui side continuations _onclick = ( u"cw.reledit.loadInlineEditionForm('%(formid)s', %(eid)s, '%(rtype)s', '%(role)s', " "'%(divid)s', %(reload)s, '%(vid)s', '%(action)s');") _cancelclick = "cw.reledit.cleanupAfterCancel('%s')" # ui side actions/buttons _addzone = u'<img title="%(msg)s" src="%(logo)s" alt="%(msg)s"/>' _addmsg = _('click to add a value') _addlogo = 'plus.png' _deletezone = u'<img title="%(msg)s" src="%(logo)s" alt="%(msg)s"/>' _deletemsg = _('click to delete this value') _deletelogo = 'cancel.png' _editzone = u'<img title="%(msg)s" src="%(logo)s" alt="%(msg)s"/>' _editzonemsg = _('click to edit this field') _editlogo = 'pen_icon.png' # renderer _form_renderer_id = 'base' def entity_call( self, entity, rtype=None, role='subject', reload=False, # controls reloading the whole page after change # boolean, eid (to redirect), or # function taking the subject entity & returning a boolean or an eid rvid=None, # vid to be applied to other side of rtype (non final relations only) default_value=None, formid='base', action=None): """display field to edit entity's `rtype` relation on click""" assert rtype self._cw.add_css('cubicweb.form.css') self._cw.add_js( ('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js')) self.entity = entity rschema = self._cw.vreg.schema[rtype] rctrl = self._cw.vreg['uicfg'].select('reledit', self._cw, entity=entity) self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*') reload = self._compute_reload(rschema, role, reload) divid = self._build_divid(rtype, role, self.entity.eid) if rschema.final: self._handle_attribute(rschema, role, divid, reload, action) else: if self._is_composite(): self._handle_composite(rschema, role, divid, reload, formid, action) else: self._handle_relation(rschema, role, divid, reload, formid, action) def _handle_attribute(self, rschema, role, divid, reload, action): rvid = self._rules.get('rvid', None) if rvid is not None: value = self._cw.view(rvid, entity=self.entity, rtype=rschema.type, role=role) else: value = self.entity.printable_value(rschema.type) if not self._should_edit_attribute(rschema): self.w(value) return form, renderer = self._build_form(self.entity, rschema, role, divid, 'base', reload, action) value = value or self._compute_default_value(rschema, role) self.view_form(divid, value, form, renderer) def _compute_formid_value(self, rschema, role, rvid, formid): related_rset = self.entity.related(rschema.type, role) if related_rset: value = self._cw.view(rvid, related_rset) else: value = self._compute_default_value(rschema, role) if not self._should_edit_relation(rschema, role): return None, value return formid, value def _handle_relation(self, rschema, role, divid, reload, formid, action): rvid = self._rules.get('rvid', 'autolimited') formid, value = self._compute_formid_value(rschema, role, rvid, formid) if formid is None: return self.w(value) form, renderer = self._build_form(self.entity, rschema, role, divid, formid, reload, action, dict(vid=rvid)) self.view_form(divid, value, form, renderer) def _handle_composite(self, rschema, role, divid, reload, formid, action): # this is for attribute-like composites (1 target type, 1 related entity at most, for now) entity = self.entity related_rset = entity.related(rschema.type, role) add_related = self._may_add_related(related_rset, rschema, role) edit_related = self._may_edit_related_entity(related_rset, rschema, role) delete_related = edit_related and self._may_delete_related( related_rset, rschema, role) rvid = self._rules.get('rvid', 'autolimited') formid, value = self._compute_formid_value(rschema, role, rvid, formid) if formid is None or not (edit_related or add_related): # till we learn to handle cases where not (edit_related or add_related) self.w(value) return form, renderer = self._build_form(entity, rschema, role, divid, formid, reload, action, dict(vid=rvid)) self.view_form(divid, value, form, renderer, edit_related, add_related, delete_related) @cached def _compute_ttypes(self, rschema, role): dual_role = neg_role(role) return getattr(rschema, '%ss' % dual_role)() def _compute_reload(self, rschema, role, reload): ctrl_reload = self._rules.get('reload', reload) if callable(ctrl_reload): ctrl_reload = ctrl_reload(self.entity) if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False ctrl_reload = self._cw.build_url(ctrl_reload) return ctrl_reload def _compute_default_value(self, rschema, role): default = self._rules.get('novalue_label') if default is None: if self._rules.get('novalue_include_rtype'): default = self._cw._('<%s not specified>') % display_name( self._cw, rschema.type, role) else: default = self._cw._('<not specified>') else: default = self._cw._(default) return xml_escape(default) def _is_composite(self): return self._rules.get('edit_target') == 'related' def _may_add_related(self, related_rset, rschema, role): """ ok for attribute-like composite entities """ ttypes = self._compute_ttypes(rschema, role) if len(ttypes) > 1: # many etypes: learn how to do it return False rdef = rschema.role_rdef(self.entity.e_schema, ttypes[0], role) card = rdef.role_cardinality(role) if related_rset or card not in '?1': return False if role == 'subject': kwargs = {'fromeid': self.entity.eid} else: kwargs = {'toeid': self.entity.eid} return rdef.has_perm(self._cw, 'add', **kwargs) def _may_edit_related_entity(self, related_rset, rschema, role): """ controls the edition of the related entity """ ttypes = self._compute_ttypes(rschema, role) if len(ttypes) > 1 or len(related_rset.rows) != 1: return False if self.entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1': return False return related_rset.get_entity(0, 0).cw_has_perm('update') def _may_delete_related(self, related_rset, rschema, role): # we assume may_edit_related, only 1 related entity if not related_rset: return False rentity = related_rset.get_entity(0, 0) entity = self.entity if role == 'subject': kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid} cardinality = rschema.rdefs[(entity.cw_etype, rentity.cw_etype)].cardinality[0] else: kwargs = {'fromeid': rentity.eid, 'toeid': entity.eid} cardinality = rschema.rdefs[(rentity.cw_etype, entity.cw_etype)].cardinality[1] if cardinality in '1+': return False # NOTE: should be sufficient given a well built schema/security return rschema.has_perm(self._cw, 'delete', **kwargs) def _build_zone(self, zonedef, msg, logo): return zonedef % { 'msg': xml_escape(self._cw._(msg)), 'logo': xml_escape(self._cw.data_url(logo)) } def _build_edit_zone(self): return self._build_zone(self._editzone, self._editzonemsg, self._editlogo) def _build_delete_zone(self): return self._build_zone(self._deletezone, self._deletemsg, self._deletelogo) def _build_add_zone(self): return self._build_zone(self._addzone, self._addmsg, self._addlogo) def _build_divid(self, rtype, role, entity_eid): """ builds an id for the root div of a reledit widget """ return '%s-%s-%s' % (rtype, role, entity_eid) def _build_args(self, entity, rtype, role, formid, reload, action, extradata=None): divid = self._build_divid(rtype, role, entity.eid) event_args = { 'divid': divid, 'eid': entity.eid, 'rtype': rtype, 'formid': formid, 'reload': json_dumps(reload), 'action': action, 'role': role, 'vid': u'' } if extradata: event_args.update(extradata) return event_args def _prepare_form(self, entity, rschema, role, action): assert action in ('edit_rtype', 'edit_related', 'add', 'delete'), action if action == 'edit_rtype': return False, entity label = True if action in ('edit_related', 'delete'): edit_entity = entity.related(rschema, role).get_entity(0, 0) elif action == 'add': add_etype = self._compute_ttypes(rschema, role)[0] _new_entity = self._cw.vreg['etypes'].etype_class(add_etype)( self._cw) _new_entity.eid = next(self._cw.varmaker) edit_entity = _new_entity # XXX see forms.py ~ 276 and entities.linked_to method # is there another way? self._cw.form['__linkto'] = '%s:%s:%s' % (rschema, entity.eid, neg_role(role)) assert edit_entity return label, edit_entity def _build_renderer(self, related_entity, display_label): return self._cw.vreg['formrenderers'].select( self._form_renderer_id, self._cw, entity=related_entity, display_label=display_label, table_class='attributeForm' if display_label else '', display_help=False, button_bar_class='buttonbar', display_progress_div=False) def _build_form(self, entity, rschema, role, divid, formid, reload, action, extradata=None, **formargs): rtype = rschema.type event_args = self._build_args(entity, rtype, role, formid, reload, action, extradata) if not action: form = _DummyForm() form.event_args = event_args return form, None label, edit_entity = self._prepare_form(entity, rschema, role, action) cancelclick = self._cancelclick % divid form = self._cw.vreg['forms'].select( formid, self._cw, rset=edit_entity.as_rset(), entity=edit_entity, domid='%s-form' % divid, formtype='inlined', action=self._cw.build_url( 'validateform', __onsuccess='window.parent.cw.reledit.onSuccess'), cwtarget='eformframe', cssclass='releditForm', **formargs) # pass reledit arguments for pname, pvalue in event_args.items(): form.add_hidden('__reledit|' + pname, pvalue) # handle buttons if form.form_buttons: # edition, delete form_buttons = [] for button in form.form_buttons: if not button.label.endswith('apply'): if button.label.endswith('cancel'): button = copy.deepcopy(button) button.cwaction = None button.onclick = cancelclick if 'class' in button.attrs: new_class = button.attrs['class'].replace( 'cwjs-edition-cancel', '') button.attrs['class'] = new_class form_buttons.append(button) form.form_buttons = form_buttons else: # base form.form_buttons = [ SubmitButton(), Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick) ] form.event_args = event_args if formid == 'base': field = form.field_by_name(rtype, role, entity.e_schema) form.append_field(field) return form, self._build_renderer(edit_entity, label) def _should_edit_attribute(self, rschema): entity = self.entity rdef = entity.e_schema.rdef(rschema) # check permissions if not entity.cw_has_perm('update'): return False rdef = entity.e_schema.rdef(rschema) return rdef.has_perm(self._cw, 'update', eid=entity.eid) def _should_edit_relation(self, rschema, role): eeid = self.entity.eid perm_args = {'fromeid': eeid} if role == 'subject' else {'toeid': eeid} return rschema.has_perm(self._cw, 'add', **perm_args) def _open_form_wrapper(self, divid, value, form, renderer, _edit_related, _add_related, _delete_related): w = self.w w( u'<div id="%(id)s-reledit" onmouseout="%(out)s" onmouseover="%(over)s" class="%(css)s">' % { 'id': divid, 'css': 'releditField', 'out': "jQuery('#%s').addClass('invisible')" % divid, 'over': "jQuery('#%s').removeClass('invisible')" % divid }) w(u'<div id="%s-value" class="editableFieldValue">' % divid) w(value) w(u'</div>') form.render(w=w, renderer=renderer) w(u'<div id="%s" class="editableField invisible">' % divid) def _edit_action(self, divid, args, edit_related, add_related, _delete_related): # XXX disambiguate wrt edit_related if not add_related: # currently, excludes edition w = self.w args['formid'] = 'edition' if edit_related else 'base' args['action'] = 'edit_related' if edit_related else 'edit_rtype' w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">' % (divid, xml_escape( self._onclick % args), self._cw._(self._editzonemsg))) w(self._build_edit_zone()) w(u'</div>') def _add_action(self, divid, args, _edit_related, add_related, _delete_related): if add_related: w = self.w args['formid'] = 'edition' args['action'] = 'add' w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">' % (divid, xml_escape( self._onclick % args), self._cw._(self._addmsg))) w(self._build_add_zone()) w(u'</div>') def _del_action(self, divid, args, _edit_related, _add_related, delete_related): if delete_related: w = self.w args['formid'] = 'deleteconf' args['action'] = 'delete' w(u'<div id="%s-delete" class="editableField" onclick="%s" title="%s">' % (divid, xml_escape( self._onclick % args), self._cw._(self._deletemsg))) w(self._build_delete_zone()) w(u'</div>') def _close_form_wrapper(self): self.w(u'</div>') self.w(u'</div>') def view_form(self, divid, value, form=None, renderer=None, edit_related=False, add_related=False, delete_related=False): self._open_form_wrapper(divid, value, form, renderer, edit_related, add_related, delete_related) args = form.event_args.copy() self._edit_action(divid, args, edit_related, add_related, delete_related) self._add_action(divid, args, edit_related, add_related, delete_related) self._del_action(divid, args, edit_related, add_related, delete_related) self._close_form_wrapper()