web/views/boxes.py
author Laure Bourgois <Laure.Bourgois@logilab.fr>
Fri, 21 Nov 2008 17:36:42 +0100
changeset 125 979dbe0cade3
parent 107 4fe4ce7e2544
child 142 0425ee84cfa6
permissions -rw-r--r--
views with rss feed

"""
generic boxes for CubicWeb web client:

* actions box
* possible views box
* rss icon

additional (disabled by default) boxes
* schema box
* startup views box

:organization: Logilab
:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"

from logilab.mtconverter import html_escape

from cubicweb.common.selectors import rset_selector, nfentity_selector, onelinerset_selector
from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
from cubicweb.web.box import BoxTemplate, ExtResourcesBoxTemplate

_ = unicode


class EditBox(BoxTemplate):
    """
    box with all actions impacting the entity displayed: edit, copy, delete
    change state, add related entities
    """
    __selectors__ = (rset_selector,) + BoxTemplate.__selectors__
    id = 'edit_box'
    title = _('actions')
    order = 2

    def call(self, **kwargs):
        _ = self.req._
        title = _(self.title)
        if self.rset:
            etypes = self.rset.column_types(0)
            if len(etypes) == 1:
                plural = self.rset.rowcount > 1 and 'plural' or ''
                etypelabel = display_name(self.req, iter(etypes).next(), plural)
                title = u'%s - %s' % (title, etypelabel.lower())
        box = BoxWidget(title, self.id, _class="greyBoxFrame")
        # build list of actions
        actions = self.vreg.possible_actions(self.req, self.rset)
        add_menu = BoxMenu(_('add')) # 'addrelated' category
        other_menu = BoxMenu(_('more actions')) # 'moreactions' category
        searchstate = self.req.search_state[0]
        for category, menu in (('mainactions', box),
                               ('addrelated', add_menu),
                               ('moreactions', other_menu)):
            for action in actions.get(category, ()):
                menu.append(self.box_action(action))
        if self.rset and self.rset.rowcount == 1 and \
               not self.schema[self.rset.description[0][0]].is_final() and \
               searchstate == 'normal':
            entity = self.rset.get_entity(0, 0)
            #entity.complete()
            if add_menu.items:
                self.info('explicit actions defined, ignoring potential rtags for %s',
                          entity.e_schema)
            else:
                # some addrelated actions may be specified but no one is selectable
                # in which case we should not fallback to schema_actions. The proper
                # way to avoid this is to override add_related_schemas() on the
                # entity class to return an empty list
                for action in self.schema_actions(entity):
                    add_menu.append(action)            
            if 'in_state' in entity.e_schema.subject_relations() and entity.in_state:
                state = entity.in_state[0]
                transitions = list(state.transitions(entity))
                if transitions:
                    menu_title = u'%s: %s' % (_('state'), state.view('text'))
                    menu_items = []
                    for tr in state.transitions(entity):
                        url = entity.absolute_url(vid='statuschange', treid=tr.eid)
                        menu_items.append(self.mk_action(_(tr.name), url))
                    state_menu = BoxMenu(menu_title, menu_items)
                    box.append(state_menu)
                # when there are no possible transition, put state if the menu if
                # there are some other actions
                elif not box.is_empty():
                    menu_title = u'<a title="%s">%s: <i>%s</i></a>' % (
                        _('no possible transition'), _('state'), state.view('text'))
                    box.append(RawBoxItem(menu_title, 'boxMainactions'))
        if box.is_empty() and not other_menu.is_empty():
            box.items = other_menu.items
            other_menu.items = []
        self.add_submenu(box, add_menu, _('add'))
        self.add_submenu(box, other_menu)
        if not box.is_empty():
            box.render(self.w)

    def add_submenu(self, box, submenu, label_prefix=None):
        if len(submenu.items) == 1:
            boxlink = submenu.items[0]
            if label_prefix:
                boxlink.label = u'%s %s' % (label_prefix, boxlink.label)
            box.append(boxlink)
        elif submenu.items:
            box.append(submenu)
        
    def schema_actions(self, entity):
        user = self.req.user
        actions = []
        _ = self.req._
        eschema = entity.e_schema
        for rschema, teschema, x in entity.add_related_schemas():
            if x == 'subject':
                label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
                url = self.linkto_url(entity, rschema, teschema, 'object')
            else:
                label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
                url = self.linkto_url(entity, rschema, teschema, 'subject')
            actions.append(self.mk_action(_(label), url))
        return actions


    def linkto_url(self, entity, rtype, etype, target):
        
        return self.build_url(vid='creation', etype=etype,
                              __linkto='%s:%s:%s' % (rtype, entity.eid, target),
                              __redirectpath=entity.rest_path(), # should not be url quoted!
                              __redirectvid=self.req.form.get('vid', ''))


class SearchBox(BoxTemplate):
    """display a box with a simple search form"""
    id = 'search_box'
    visible = True # enabled by default
    title = _('search')
    order = 0
    need_resources = 'SEARCH_GO'
    formdef = u"""<form action="%s">
<table id="tsearch"><tr><td>
<input id="norql" type="text" accesskey="q" tabindex="%s" title="search text" value="%s" name="rql" />
<input type="hidden" name="__fromsearchbox" value="1" />
<input type="hidden" name="subvid" value="tsearch" />
</td><td>
<input tabindex="%s" type="submit" id="rqlboxsubmit" value="" />
</td></tr></table>
</form>"""


    def call(self, view=None, **kwargs):
        req = self.req
        if req.form.pop('__fromsearchbox', None):
            rql = req.form.get('rql', '')
        else:
            rql = ''
        form = self.formdef % (req.build_url('view'), req.next_tabindex(),
                               html_escape(rql), req.next_tabindex())
        title = u"""<span onclick="javascript: toggleVisibility('rqlinput')">%s</span>""" % req._(self.title)
        box = BoxWidget(title, self.id, _class="searchBoxFrame", islist=False, escape=False)
        box.append(BoxHtml(form))
        box.render(self.w)            


# boxes disabled by default ###################################################

class PossibleViewsBox(BoxTemplate):
    """display a box containing links to all possible views"""
    id = 'possible_views_box'
    
    
    title = _('possible views')
    order = 10
    require_groups = ('users', 'managers')
    visible = False

    def call(self, **kwargs):
        box = BoxWidget(self.req._(self.title), self.id)
        actions = [v for v in self.vreg.possible_views(self.req, self.rset)
                   if v.category != 'startupview']
        for category, actions in self.sort_actions(actions):
            menu = BoxMenu(category)
            for action in actions:
                menu.append(self.box_action(action))
            box.append(menu)
        if not box.is_empty():
            box.render(self.w)


class RSSIconBox(ExtResourcesBoxTemplate):
    """just display the RSS icon on uniform result set"""
    __selectors__ = ExtResourcesBoxTemplate.__selectors__ + (nfentity_selector,)
    
    id = 'rss'
    order = 999
    need_resources = 'RSS_LOGO',
    visible = False
    
    def call(self, **kwargs):
        url = html_escape(self.build_url(rql=self.limited_rql(), vid='rss'))
        rss = self.req.external_resource('RSS_LOGO')
        self.w(u'<a href="%s"><img src="%s" border="0" /></a>\n' % (url, rss))

class EntityRSSIconBox(RSSIconBox):
    """just display the RSS icon on uniform result set for a single entity"""
    __selectors__ = RSSIconBox.__selectors__ + (onelinerset_selector,)

    def call(self, **kwargs):
        entity = self.entity(0, 0)
        url = entity.rss_feed_url()
        eid = entity.eid
        rss = self.req.external_resource('RSS_LOGO')
        self.w(u'<a href="%s"><img src="%s" border="0" /></a>\n' %
               (html_escape(url), rss))

## warning("schemabox ne marche plus pour le moment")
## class SchemaBox(BoxTemplate):
##     """display a box containing link to list of entities by type"""
##     id = 'schema_box'
##     visible = False # disabled by default
##     title = _('entity list')
##     order = 60
        
##     def call(self, **kwargs):
##         box = BoxWidget(self.req._(title), self.id)
##         for etype in self.config.etypes(self.req.user, 'read'):
##             view = self.vreg.select_view('list', self.req, self.etype_rset(etype))
##             box.append(self.mk_action(display_name(self.req, etype, 'plural'),
##                                       view.url(), etype=etype))
##         if not box.is_empty():
##             box.render(self.w)

class StartupViewsBox(BoxTemplate):
    """display a box containing links to all startup views"""
    id = 'startup_views_box'
    visible = False # disabled by default
    title = _('startup views')
    order = 70

    def call(self, **kwargs):
        box = BoxWidget(self.req._(self.title), self.id)
        for view in self.vreg.possible_views(self.req, None):
            if view.category == 'startupview':
                box.append(self.box_action(view))
        
        if not box.is_empty():
            box.render(self.w)