web/views/boxes.py
changeset 0 b97547f5f1fa
child 107 4fe4ce7e2544
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/boxes.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,235 @@
+"""
+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
+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))
+
+
+## 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)
+