예제 #1
0
class PageNavigationSelect(PageNavigation):
    """This pagination component displays a result-set by page as
    :class:`PageNavigation` but in a <select>, which is better when there are a
    lot of results.

    By default it will be selected when there are more than 4 pages to be
    displayed.
    """
    __select__ = paginated_rset(4)

    page_link_templ = u'<option value="%s" title="%s">%s</option>'
    selected_page_link_templ = u'<option value="%s" selected="selected" title="%s">%s</option>'

    def call(self):
        params = dict(self._cw.form)
        self.clean_params(params)
        basepath = self._cw.relative_path(includeparams=False)
        w = self.w
        w(u'<div class="pagination">')
        w(self.previous_link(basepath, params))
        w(u'<select onchange="javascript: document.location=this.options[this.selectedIndex].value">'
          )
        for option in self.iter_page_links(basepath, params):
            w(option)
        w(u'</select>')
        w(u'&#160;&#160;%s' % self.next_link(basepath, params))
        w(u'</div>')
예제 #2
0
 def test_paginated_rset(self):
     default_nb_pages = 1
     web_request = self.admin_access.web_request
     with web_request() as req:
         rset = req.execute('Any G WHERE G is CWGroup')
     self.assertEqual(len(rset), 34)
     with web_request(vid='list', page_size='10') as req:
         self.assertEqual(paginated_rset()(None, req, rset), default_nb_pages)
     with web_request(vid='list', page_size='20') as req:
         self.assertEqual(paginated_rset()(None, req, rset), default_nb_pages)
     with web_request(vid='list', page_size='50') as req:
         self.assertEqual(paginated_rset()(None, req, rset), 0)
     with web_request(vid='list', page_size='10/') as req:
         self.assertEqual(paginated_rset()(None, req, rset), 0)
     with web_request(vid='list', page_size='.1') as req:
         self.assertEqual(paginated_rset()(None, req, rset), 0)
     with web_request(vid='list', page_size='not_an_int') as req:
         self.assertEqual(paginated_rset()(None, req, rset), 0)
예제 #3
0
class SortedNavigation(NavigationComponent):
    """This pagination component will be selected by default if there are less
    than 4 pages and if the result set is sorted.

    Displayed links to navigate accross pages of a result set are done according
    to the first variable on which the sort is done, and looks like:

        [ana - cro] | [cro - ghe] | ... | [tim - zou]

    You may want to override this component to customize display in some cases.

    .. automethod:: sort_on
    .. automethod:: display_func
    .. automethod:: format_link_content
    .. automethod:: write_links

    Below an example from the tracker cube:

    .. sourcecode:: python

      class TicketsNavigation(navigation.SortedNavigation):
          __select__ = (navigation.SortedNavigation.__select__
                        & ~paginated_rset(4) & is_instance('Ticket'))
          def sort_on(self):
              col, attrname = super(TicketsNavigation, self).sort_on()
              if col == 6:
                  # sort on state, we don't want that
                  return None, None
              return col, attrname

    The idea is that in trackers'ticket tables, result set is first ordered on
    ticket's state while this doesn't make any sense in the navigation. So we
    override :meth:`sort_on` so that if we detect such sorting, we disable the
    feature to go back to item number in the pagination.

    Also notice the `~paginated_rset(4)` in the selector so that if there are
    more than 4 pages to display, :class:`PageNavigationSelect` will still be
    selected.
    """
    __select__ = paginated_rset() & sorted_rset()

    # number of considered chars to build page links
    nb_chars = 5

    def call(self):
        # attrname = the name of attribute according to which the sort
        # is done if any
        col, attrname = self.sort_on()
        index_display = self.display_func(self.cw_rset, col, attrname)
        basepath = self._cw.relative_path(includeparams=False)
        params = dict(self._cw.form)
        self.clean_params(params)
        blocklist = []
        start = 0
        total = self.cw_rset.rowcount
        while start < total:
            stop = min(start + self.page_size - 1, total - 1)
            cell = self.format_link_content(index_display(start),
                                            index_display(stop))
            blocklist.append(
                self.page_link(basepath, params, start, stop, cell))
            start = stop + 1
        self.write_links(basepath, params, blocklist)

    def display_func(self, rset, col, attrname):
        """Return a function that will be called with a row number as argument
        and should return a string to use as link for it.
        """
        if attrname is not None:

            def index_display(row):
                if not rset[row][col]:  # outer join
                    return u''
                entity = rset.get_entity(row, col)
                return entity.printable_value(attrname, format='text/plain')
        elif col is None:  # smart links disabled.

            def index_display(row):
                return str(row)
        elif self._cw.vreg.schema.eschema(rset.description[0][col]).final:

            def index_display(row):
                return str(rset[row][col])
        else:

            def index_display(row):
                return rset.get_entity(row, col).view('text')

        return index_display

    def sort_on(self):
        """Return entity column number / attr name to use for nice display by
        inspecting the rset'syntax tree.
        """
        rschema = self._cw.vreg.schema.rschema
        for sorterm in self.cw_rset.syntax_tree().children[0].orderby:
            if isinstance(sorterm.term, Constant):
                col = sorterm.term.value - 1
                return col, None
            var = sorterm.term.get_nodes(VariableRef)[0].variable
            col = None
            for ref in var.references():
                rel = ref.relation()
                if rel is None:
                    continue
                attrname = rel.r_type
                if attrname in ('is', 'has_text'):
                    continue
                if not rschema(attrname).final:
                    col = var.selected_index()
                    attrname = None
                if col is None:
                    # final relation or not selected non final relation
                    if var is rel.children[0]:
                        relvar = rel.children[1].children[0].get_nodes(
                            VariableRef)[0]
                    else:
                        relvar = rel.children[0].variable
                    col = relvar.selected_index()
                if col is not None:
                    break
            else:
                # no relation but maybe usable anyway if selected
                col = var.selected_index()
                attrname = None
            if col is not None:
                # if column type is date[time], set proper 'nb_chars'
                if var.stinfo['possibletypes'] & frozenset(
                    ('TZDatetime', 'Datetime', 'Date')):
                    self.nb_chars = len(self._cw.format_date(datetime.today()))
                return col, attrname
        # nothing usable found, use the first column
        return 0, None

    def format_link_content(self, startstr, stopstr):
        """Return text for a page link, where `startstr` and `stopstr` are the
        text for the lower/upper boundaries of the page.

        By default text are stripped down to :attr:`nb_chars` characters.
        """
        text = u'%s - %s' % (startstr.lower()[:self.nb_chars],
                             stopstr.lower()[:self.nb_chars])
        return xml_escape(text)

    def write_links(self, basepath, params, blocklist):
        """Return HTML for the whole navigation: `blocklist` is a list of HTML
        snippets for each page, `basepath` and `params` will be necessary to
        build previous/next links.
        """
        self.w(u'<div class="pagination">')
        self.w(u'%s&#160;' % self.previous_link(basepath, params))
        self.w(u'[&#160;%s&#160;]' % u'&#160;| '.join(blocklist))
        self.w(u'&#160;%s' % self.next_link(basepath, params))
        self.w(u'</div>')
예제 #4
0
class NavigationComponent(Component):
    """abstract base class for navigation components"""
    __regid__ = 'navigation'
    __select__ = paginated_rset()

    cw_property_defs = {
        _('visible'):
        dict(type='Boolean',
             default=True,
             help=_('display the component or not')),
    }

    page_size_property = 'navigation.page-size'
    start_param = '__start'
    stop_param = '__stop'
    page_link_templ = u'<span class="slice"><a href="%s" title="%s">%s</a></span>'
    selected_page_link_templ = u'<span class="selectedSlice"><a href="%s" title="%s">%s</a></span>'
    previous_page_link_templ = next_page_link_templ = page_link_templ

    def __init__(self, req, rset, **kwargs):
        super(NavigationComponent, self).__init__(req, rset=rset, **kwargs)
        self.starting_from = 0
        self.total = rset.rowcount

    def get_page_size(self):
        try:
            return self._page_size
        except AttributeError:
            page_size = self.cw_extra_kwargs.get('page_size')
            if page_size is None:
                try:
                    page_size = int(self._cw.form.get('page_size'))
                except (ValueError, TypeError):
                    # no or invalid value, fall back
                    pass
            if page_size is None:
                page_size = self._cw.property_value(self.page_size_property)
            self._page_size = page_size
            return page_size

    def set_page_size(self, page_size):
        self._page_size = page_size

    page_size = property(get_page_size, set_page_size)

    def page_boundaries(self):
        try:
            stop = int(self._cw.form[self.stop_param]) + 1
            start = int(self._cw.form[self.start_param])
        except KeyError:
            start, stop = 0, self.page_size
        if start >= len(self.cw_rset):
            start, stop = 0, self.page_size
        self.starting_from = start
        return start, stop

    def clean_params(self, params):
        if self.start_param in params:
            del params[self.start_param]
        if self.stop_param in params:
            del params[self.stop_param]

    def page_url(self, path, params, start=None, stop=None):
        params = dict(params)
        params['__fromnavigation'] = 1
        if start is not None:
            params[self.start_param] = start
        if stop is not None:
            params[self.stop_param] = stop
        view = self.cw_extra_kwargs.get('view')
        if view is not None and hasattr(view, 'page_navigation_url'):
            url = view.page_navigation_url(self, path, params)
        elif path in ('json', 'ajax'):
            # 'ajax' is the new correct controller, but the old 'json'
            # controller should still be supported
            url = self.ajax_page_url(**params)
        else:
            url = self._cw.build_url(path, **params)
        # XXX hack to avoid opening a new page containing the evaluation of the
        # js expression on ajax call
        if url.startswith('javascript:'):
            url += '; $.noop();'
        return url

    def ajax_page_url(self, **params):
        divid = params.setdefault('divid', 'contentmain')
        params['rql'] = self.cw_rset.printable_rql()
        return js_href(
            "$(%s).loadxhtml(AJAX_PREFIX_URL, %s, 'get', 'swap')" %
            (json_dumps('#' + divid), js.ajaxFuncArgs('view', params)))

    def page_link(self, path, params, start, stop, content):
        url = xml_escape(self.page_url(path, params, start, stop))
        if start == self.starting_from:
            return self.selected_page_link_templ % (url, content, content)
        return self.page_link_templ % (url, content, content)

    @property
    def prev_icon_url(self):
        return xml_escape(self._cw.data_url('go_prev.png'))

    @property
    def next_icon_url(self):
        return xml_escape(self._cw.data_url('go_next.png'))

    @property
    def no_previous_page_link(self):
        return (u'<img src="%s" alt="%s" class="prevnext_nogo"/>' %
                (self.prev_icon_url, self._cw._('there is no previous page')))

    @property
    def no_next_page_link(self):
        return (u'<img src="%s" alt="%s" class="prevnext_nogo"/>' %
                (self.next_icon_url, self._cw._('there is no next page')))

    @property
    def no_content_prev_link(self):
        return (u'<img src="%s" alt="%s" class="prevnext"/>' %
                ((self.prev_icon_url, self._cw._('no content prev link'))))

    @property
    def no_content_next_link(self):
        return (u'<img src="%s" alt="%s" class="prevnext"/>' %
                (self.next_icon_url, self._cw._('no content next link')))

    def previous_link(self,
                      path,
                      params,
                      content=None,
                      title=_('previous_results')):
        if not content:
            content = self.no_content_prev_link
        start = self.starting_from
        if not start:
            return self.no_previous_page_link
        start = max(0, start - self.page_size)
        stop = start + self.page_size - 1
        url = xml_escape(self.page_url(path, params, start, stop))
        return self.previous_page_link_templ % (url, self._cw._(title),
                                                content)

    def next_link(self, path, params, content=None, title=_('next_results')):
        if not content:
            content = self.no_content_next_link
        start = self.starting_from + self.page_size
        if start >= self.total:
            return self.no_next_page_link
        stop = start + self.page_size - 1
        url = xml_escape(self.page_url(path, params, start, stop))
        return self.next_page_link_templ % (url, self._cw._(title), content)

    def render_link_back_to_pagination(self, w):
        """allow to come back to the paginated view"""
        req = self._cw
        params = dict(req.form)
        basepath = req.relative_path(includeparams=False)
        del params['__force_display']
        url = self.page_url(basepath, params)
        w(u'<div class="displayAllLink"><a href="%s">%s</a></div>\n' %
          (xml_escape(url),
           req._('back to pagination (%s results)') % self.page_size))

    def render_link_display_all(self, w):
        """make a link to see them all"""
        req = self._cw
        params = dict(req.form)
        self.clean_params(params)
        basepath = req.relative_path(includeparams=False)
        params['__force_display'] = 1
        params['__fromnavigation'] = 1
        url = self.page_url(basepath, params)
        w(u'<div class="displayAllLink"><a href="%s">%s</a></div>\n' %
          (xml_escape(url), req._('show %s results') % len(self.cw_rset)))