ibreadcrumbs.py 7.58 KB
Newer Older
Aurelien Campeas's avatar
Aurelien Campeas committed
1
# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2
3
4
5
6
7
8
9
10
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
11
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
12
13
14
15
16
17
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
18
"""breadcrumbs components definition for CubicWeb web client"""
Adrien Di Mascio's avatar
Adrien Di Mascio committed
19

Sylvain Thénault's avatar
Sylvain Thénault committed
20

21
from cubicweb import _
Adrien Di Mascio's avatar
Adrien Di Mascio committed
22

23
24
from warnings import warn

Sylvain Thénault's avatar
Sylvain Thénault committed
25
from logilab.mtconverter import xml_escape
Adrien Di Mascio's avatar
Adrien Di Mascio committed
26

27
from cubicweb import tags, uilib
28
from cubicweb.entity import Entity, EntityAdapter
29
from cubicweb.predicates import (is_instance, one_line_rset, adaptable,
30
31
                                one_etype_rset, multi_lines_rset, any_rset,
                                match_form_params)
32
from cubicweb.view import EntityView
33
from cubicweb.web.views import basecomponents
34
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
Adrien Di Mascio's avatar
Adrien Di Mascio committed
35
36


37
38
39
40
41
42

class IBreadCrumbsAdapter(EntityAdapter):
    """adapters for entities which can be"located" on some path to display in
    the web ui
    """
    __regid__ = 'IBreadCrumbs'
43
    __select__ = is_instance('Any', accept_none=False)
44
45
46
47
48
49
50

    def parent_entity(self):
        itree = self.entity.cw_adapt_to('ITree')
        if itree is not None:
            return itree.parent()
        return None

51
    def breadcrumbs(self, view=None, recurs=None):
52
53
54
55
56
57
58
59
        """return a list containing some:

        * tuple (url, label)
        * entity
        * simple label string

        defining path from a root to the current view

60
61
62
63
        the main view is given as argument so breadcrumbs may vary according to
        displayed view (may be None). When recursing on a parent entity, the
        `recurs` argument should be a set of already traversed nodes (infinite
        loop safety belt).
64
65
66
        """
        parent = self.parent_entity()
        if parent is not None:
Aurelien Campeas's avatar
Aurelien Campeas committed
67
            if recurs:
68
69
70
71
72
73
74
                _recurs = recurs
            else:
                _recurs = set()
            if _recurs and parent.eid in _recurs:
                self.error('cycle in breadcrumbs for entity %s' % self.entity)
                return []
            _recurs.add(parent.eid)
Aurelien Campeas's avatar
Aurelien Campeas committed
75
            adapter = parent.cw_adapt_to('IBreadCrumbs')
76
            path = adapter.breadcrumbs(view, _recurs) + [self.entity]
77
78
        else:
            path = [self.entity]
79
80
81
82
83
84
85
86
87
88
        if not recurs:
            if view is None:
                if 'vtitle' in self._cw.form:
                    # embeding for instance
                    path.append( self._cw.form['vtitle'] )
            elif view.__regid__ != 'primary' and hasattr(view, 'title'):
                path.append( self._cw._(view.title) )
        return path


89
class BreadCrumbEntityVComponent(basecomponents.HeaderComponent):
90
    __regid__ = 'breadcrumbs'
91
92
93
    __select__ = (basecomponents.HeaderComponent.__select__
                  & one_line_rset() & adaptable('IBreadCrumbs'))
    order = basecomponents.ApplicationName.order + 1
94
    context = basecomponents.ApplicationName.context
95
    separator = u'&#160;&gt;&#160;'
96
    link_template = u'<a href="%s">%s</a>'
97
    first_separator = True
Adrien Di Mascio's avatar
Adrien Di Mascio committed
98

99
100
101
    # XXX support kwargs for compat with other components which gets the view as
    # argument
    def render(self, w, **kwargs):
102
103
104
105
        try:
            entity = self.cw_extra_kwargs['entity']
        except KeyError:
            entity = self.cw_rset.get_entity(0, 0)
Aurelien Campeas's avatar
Aurelien Campeas committed
106
        adapter = entity.cw_adapt_to('IBreadCrumbs')
107
        view = self.cw_extra_kwargs.get('view')
108
        path = adapter.breadcrumbs(view)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
109
        if path:
110
111
112
            self.open_breadcrumbs(w)
            self.render_breadcrumbs(w, entity, path)
            self.close_breadcrumbs(w)
113

114
115
    def open_breadcrumbs(self, w):
        w(u'<span id="breadcrumbs" class="pathbar">')
116
117
        if self.first_separator:
            w(self.separator)
118

119
120
    def close_breadcrumbs(self, w):
        w(u'</span>')
121

122
    def render_root(self, w, contextentity, path):
123
124
        root = path.pop(0)
        if isinstance(root, Entity):
125
            w(self.link_template % (self._cw.build_url(root.__regid__),
126
                                         root.dc_type('plural')))
127
128
            w(self.separator)
        self.wpath_part(w, root, contextentity, not path)
129
130
131
 
    def render_breadcrumbs(self, w, contextentity, path):
        self.render_root(w, contextentity, path)
132
        for i, parent in enumerate(path):
133
134
135
            w(self.separator)
            w(u"\n")
            self.wpath_part(w, parent, contextentity, i == len(path) - 1)
136

137
    def wpath_part(self, w, part, contextentity, last=False): # XXX deprecates last argument?
Adrien Di Mascio's avatar
Adrien Di Mascio committed
138
        if isinstance(part, Entity):
139
            w(part.view('breadcrumbs'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
140
141
        elif isinstance(part, tuple):
            url, title = part
142
            textsize = self._cw.property_value('navigation.short-line-size')
143
            w(self.link_template % (
144
                xml_escape(url), xml_escape(uilib.cut(title, textsize))))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
145
        else:
146
            textsize = self._cw.property_value('navigation.short-line-size')
Denis Laxalde's avatar
Denis Laxalde committed
147
            w(xml_escape(uilib.cut(str(part), textsize)))
148

Adrien Di Mascio's avatar
Adrien Di Mascio committed
149

150
class BreadCrumbETypeVComponent(BreadCrumbEntityVComponent):
151
152
153
    __select__ = (basecomponents.HeaderComponent.__select__
                  & multi_lines_rset() & one_etype_rset()
                  & adaptable('IBreadCrumbs'))
154

155
    def render_breadcrumbs(self, w, contextentity, path):
156
157
158
        # XXX hack: only display etype name or first non entity path part
        root = path.pop(0)
        if isinstance(root, Entity):
159
160
            w(u'<a href="%s">%s</a>' % (self._cw.build_url(root.__regid__),
                                        root.dc_type('plural')))
161
        else:
162
            self.wpath_part(w, root, contextentity, not path)
163
164
165


class BreadCrumbAnyRSetVComponent(BreadCrumbEntityVComponent):
166
167
    __select__ = basecomponents.HeaderComponent.__select__ & any_rset()

168
169
170
    # XXX support kwargs for compat with other components which gets the view as
    # argument
    def render(self, w, **kwargs):
171
        self.open_breadcrumbs(w)
172
        w(self._cw._('search'))
173
        self.close_breadcrumbs(w)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
174
175


176
177
178
179
180
181
182
183
184
185
186
class BreadCrumbLinkToVComponent(BreadCrumbEntityVComponent):
    __select__ = basecomponents.HeaderComponent.__select__ & match_form_params('__linkto')

    def render(self, w, **kwargs):
        eid = self._cw.list_form_param('__linkto')[0].split(':')[1]
        entity = self._cw.entity_from_eid(eid)
        ecmp = self._cw.vreg[self.__registry__].select(
            self.__regid__, self._cw, entity=entity, **kwargs)
        ecmp.render(w, **kwargs)


Adrien Di Mascio's avatar
Adrien Di Mascio committed
187
class BreadCrumbView(EntityView):
188
    __regid__ = 'breadcrumbs'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
189

190
    def cell_call(self, row, col, **kwargs):
191
        entity = self.cw_rset.get_entity(row, col)
192
        desc = uilib.cut(entity.dc_description(), 50)
Sylvain Thénault's avatar
Sylvain Thénault committed
193
        # NOTE remember camember: tags.a autoescapes
194
195
        self.w(tags.a(entity.view('breadcrumbtext'),
                      href=entity.absolute_url(), title=desc))
196
197
198


class BreadCrumbTextView(EntityView):
199
    __regid__ = 'breadcrumbtext'
200

201
    def cell_call(self, row, col, **kwargs):
202
203
        entity = self.cw_rset.get_entity(row, col)
        textsize = self._cw.property_value('navigation.short-line-size')
204
        self.w(uilib.cut(entity.dc_title(), textsize))