Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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__))
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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')
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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')
Ejemplo n.º 8
0
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 ()
Ejemplo n.º 9
0
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))
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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')
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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')
Ejemplo n.º 14
0
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()