Edgewall Software

Ticket #6127: Trac-0.11b1_reportpaginate_r412.patch

File Trac-0.11b1_reportpaginate_r412.patch, 34.2 KB (added by trac-ja@…, 4 years ago)

Patch againsts Trac-0.11b1

  • trac/ticket/report.py

     
    2323 
    2424from genshi.builder import tag 
    2525 
     26from trac.config import Option, IntOption 
    2627from trac.core import * 
    2728from trac.db import get_column_names 
    2829from trac.mimeview import Context 
     
    3031from trac.resource import Resource, ResourceNotFound 
    3132from trac.util import sorted 
    3233from trac.util.datefmt import format_datetime, format_time 
     34from trac.util.presentation import Paginator 
    3335from trac.util.text import to_unicode, unicode_urlencode 
    3436from trac.util.translation import _ 
    3537from trac.web.api import IRequestHandler, RequestDone 
     
    4345    implements(INavigationContributor, IPermissionRequestor, IRequestHandler, 
    4446               IWikiSyntaxProvider) 
    4547 
     48    default_max = IntOption('report', 'default_max', default=100, doc='Default max items per page') 
     49    default_rss_max = IntOption('report', 'default_rss_max', default=0, doc='Default max items per page for RSS') 
     50     
    4651    # INavigationContributor methods 
    4752 
    4853    def get_active_navigation_item(self, req): 
     
    276281                'report': {'id': id, 'resource': report_resource}, 
    277282                'context': context, 
    278283                'title': title, 'description': description, 
    279                 'args': args, 'message': None} 
     284                'args': args, 'message': None, 'paginator':None} 
     285 
     286        self.page = int(req.args.get('page', '1')) 
     287        if req.args.get('format', '') == 'rss': 
     288            self.limit = self.env.config.getint('report', 'default_rss_max') 
     289        else: 
     290            self.limit = self.env.config.getint('report', 'default_max') 
     291        self.offset = (self.page - 1) * self.limit 
     292 
    280293        try: 
    281294            cols, results = self.execute_report(req, db, id, sql, args) 
    282295            results = [list(row) for row in results] 
     296            numrows = len(results) 
     297 
    283298        except Exception, e: 
    284299            data['message'] = _('Report execution failed: %(error)s', 
    285300                                error=to_unicode(e)) 
    286301            return 'report_view.html', data, None 
     302        paginator = None 
     303        if id != -1 and self.limit > 0: 
     304            self.asc = req.args.get('asc', None) 
     305            self.sort = req.args.get('sort', None) 
     306            self.USER = req.args.get('USER', None) 
     307            paginator = Paginator(results, self.page - 1, self.limit, self.num_items) 
     308            data['paginator'] = paginator 
     309            if paginator.has_next_page: 
     310                next_href = req.href.report(id, asc=self.asc, sort=self.sort, USER=self.USER, page=self.page + 1) 
     311                add_link(req, 'next', next_href, _('Next Page')) 
     312            if paginator.has_previous_page: 
     313                prev_href = req.href.report(id, asc=self.asc, sort=self.sort, USER=self.USER, page=self.page - 1) 
     314                add_link(req, 'prev', prev_href, _('Previous Page')) 
     315            pagedata = [] 
     316            shown_pages = paginator.get_shown_pages(21) 
     317            for page in shown_pages: 
     318                pagedata.append([req.href.report(id, asc=self.asc, sort=self.sort, USER=self.USER, page=page), None, str(page), 'page ' + str(page)])           
     319            paginator.shown_pages = [{'href':p[0], 'class':p[1], 'string':p[2], 'title':p[3]} for p in pagedata] 
     320            paginator.current_page = {'href':None, 'class':'current', 'string':str(paginator.page + 1), 'title':None} 
     321            numrows = paginator.num_items 
    287322 
    288323        sort_col = req.args.get('sort', '') 
    289324        asc = req.args.get('asc', 1) 
     
    303338 
    304339            if col == sort_col: 
    305340                header['asc'] = asc 
    306                 def sortkey(row): 
    307                     val = row[idx] 
    308                     if isinstance(val, basestring): 
    309                         val = val.lower() 
    310                     return val 
    311                 results = sorted(results, key=sortkey, reverse=(not asc)) 
     341                if not paginator: 
     342                    def sortkey(row): 
     343                        val = row[idx] 
     344                        if isinstance(val, basestring): 
     345                            val = val.lower() 
     346                        return val 
     347                    results = sorted(results, key=sortkey, reverse=(not asc)) 
    312348 
    313349            header_group = header_groups[-1] 
    314350 
     
    387423 
    388424        data.update({'header_groups': header_groups, 
    389425                     'row_groups': row_groups, 
    390                      'numrows': len(results), 
     426                     'numrows': numrows, 
    391427                     'sorting_enabled': len(row_groups)==1, 
    392428                     'email_map': email_map}) 
    393429 
     
    416452                    req.session['query_tickets'] = \ 
    417453                        ' '.join([str(int(row['id'])) 
    418454                                  for rg in row_groups for row in rg[1]]) 
    419                     req.session['query_href'] = req.href.report(id) 
     455                    req.session['query_href'] = req.href.report(id, asc=self.asc, srot=self.sort, USER=self.USER, page=self.page) 
    420456                    # Kludge: we have to clear the other query session 
    421457                    # variables, but only if the above succeeded  
    422458                    for var in ('query_constraints', 'query_time'): 
     
    450486        if not sql: 
    451487            raise TracError(_('Report %(num)s has no SQL query.', num=id)) 
    452488        self.log.debug('Executing report with SQL "%s" (%s)', sql, args) 
     489        self.log.debug('Request args' + str(req.args)) 
     490        cursor = db.cursor() 
    453491 
    454         cursor = db.cursor() 
     492        if id != -1 and self.limit > 0: 
     493            # The number of tickets is obtained. 
     494            count_sql = 'SELECT COUNT(*) FROM (' + sql + ') AS tab' 
     495            cursor.execute(count_sql, args) 
     496            self.env.log.debug("Query SQL(Get num items): " + count_sql) 
     497            for row in cursor: 
     498                pass 
     499            self.num_items = row[0] 
     500     
     501            # The column name is obtained. 
     502            get_col_name_sql = 'SELECT * FROM ( ' + sql + ' ) AS tab LIMIT 1' 
     503            cursor.execute(get_col_name_sql, args) 
     504            self.env.log.debug("Query SQL(Get col names): " + get_col_name_sql) 
     505            cols = get_column_names(cursor) 
     506 
     507            sort_col = req.args.get('sort', '') 
     508            self.env.log.debug("Colnum Names %s, Sort column %s" % (str(cols), sort_col)) 
     509            order_cols = [] 
     510            try: 
     511                group_idx = cols.index('__group__') 
     512                order_cols.append(str(group_idx)) 
     513            except ValueError: 
     514                pass 
     515 
     516            if sort_col: 
     517                try: 
     518                    sort_idx = cols.index(sort_col) + 1 
     519                    order_cols.append(str(sort_idx)) 
     520                except ValueError: 
     521                    raise TrackError(_('Query parameter "sort=%(sort_col)s" is invalid', sort_col)) 
     522 
     523            # The report-query results is obtained 
     524            asc_str = ['DESC', 'ASC'] 
     525            asc_idx = int(req.args.get('asc','0')) 
     526            order_by = '' 
     527            if len(order_cols) != 0: 
     528                dlmt = ", " 
     529                order = dlmt.join(order_cols) 
     530                order_by = " ".join([' ORDER BY' ,order, asc_str[asc_idx]]) 
     531            sql = " ".join(['SELECT * FROM (', sql, ') AS tab', order_by]) 
     532            sql =" ".join([sql, 'LIMIT', str(self.limit), 'OFFSET', str(self.offset)]) 
     533            self.env.log.debug("Query SQL: " + sql) 
    455534        cursor.execute(sql, args) 
    456  
     535        self.env.log.debug("Query SQL: " + sql) 
    457536        # FIXME: fetchall should probably not be used. 
    458537        info = cursor.fetchall() or [] 
    459538        cols = get_column_names(cursor) 
  • trac/ticket/tests/wikisyntax.py

     
    197197<a class="query" href="/query?status=new&amp;status=reopened&amp;order=priority">query:status=new|reopened</a> 
    198198</p> 
    199199<p> 
    200 <a class="query" href="/query?order=priority&amp;milestone=%21">query:milestone!=</a> 
     200<a class="query" href="/query?milestone=%21&amp;order=priority">query:milestone!=</a> 
    201201</p> 
    202202<p> 
    203 <a class="query" href="/query?order=priority&amp;milestone=1.0&amp;milestone=2.0&amp;owner=me">query:milestone=1.0|2.0&amp;owner=me</a> 
     203<a class="query" href="/query?milestone=1.0&amp;milestone=2.0&amp;owner=me&amp;order=priority">query:milestone=1.0|2.0&amp;owner=me</a> 
    204204</p> 
    205205<p> 
    206206<a class="query" href="/query?group=owner&amp;order=priority">query:group=owner</a> 
  • trac/ticket/query.py

     
    1616# Author: Christopher Lenz <cmlenz@gmx.de> 
    1717 
    1818import csv 
     19from math import ceil 
    1920from datetime import datetime, timedelta 
    2021import re 
    2122from StringIO import StringIO 
     
    3334from trac.util.compat import groupby 
    3435from trac.util.datefmt import to_timestamp, utc 
    3536from trac.util.html import escape, unescape 
     37from trac.util.presentation import Paginator 
    3638from trac.util.text import shorten_line, CRLF 
    3739from trac.util.translation import _ 
    3840from trac.web import IRequestHandler 
    3941from trac.web.href import Href 
    4042from trac.web.chrome import add_ctxtnav, add_link, add_script, add_stylesheet, \ 
    4143                            INavigationContributor, Chrome 
     44 
    4245from trac.wiki.api import IWikiSyntaxProvider, parse_args 
    4346from trac.wiki.macros import WikiMacroBase # TODO: should be moved in .api 
    44 from trac.config import Option  
     47from trac.config import Option, IntOption  
    4548 
    4649class QuerySyntaxError(Exception): 
    4750    """Exception raised when a ticket query cannot be parsed from a string.""" 
     
    4952 
    5053class Query(object): 
    5154 
     55    IntOption = IntOption('query', 'default_max', default=100, doc='Default max items per page') 
     56 
    5257    def __init__(self, env, report=None, constraints=None, cols=None, 
    5358                 order=None, desc=0, group=None, groupdesc=0, verbose=0, 
    54                  rows=None, limit=None): 
     59                 rows=None, page=1, max=None): 
     60 
    5561        self.env = env 
    5662        self.id = report # if not None, it's the corresponding saved query 
    5763        self.constraints = constraints or {} 
     
    5965        self.desc = desc 
    6066        self.group = group 
    6167        self.groupdesc = groupdesc 
    62         self.limit = limit 
     68        self.default_page = 1 
     69        self.default_max = self.env.config.getint('query', 'default_max') 
     70 
     71        try: 
     72            if page is None: 
     73                page = self.default_page 
     74            elif int(page) < 1: 
     75                raise ValueError() 
     76        except ValueError: 
     77            raise TracError(_('Query parameter is invalid: [page = %(page)s]', str(page))) 
     78 
     79        if max == 'None' or max == '': 
     80            max = 0 
     81 
     82        try: 
     83            if max is None: 
     84                max = self.default_max 
     85            elif int(max) < 0: 
     86                raise ValueError() 
     87        except ValueError: 
     88            raise TracError(_('Query parameter is invalid [max = %(max)s]', str(max))) 
     89         
     90        self.page = 0 
     91        self.offset = 0 
     92        self.max =  0 
     93 
     94        if int(max) == 0: 
     95            self.has_more_pages = False 
     96        else: 
     97            self.has_more_pages = True 
     98            self.page = int(page) 
     99            self.offset = int(max) * (int(page) - 1) 
     100            self.max =  int(max) 
     101 
    63102        if rows == None: 
    64103            rows = [] 
    65104        if verbose and 'description' not in rows: # 0.10 compatibility 
     
    68107        field_names = [f['name'] for f in self.fields] 
    69108        self.cols = [c for c in cols or [] if c in field_names or c == 'id'] 
    70109        self.rows = [c for c in rows if c in field_names] 
    71  
    72110        if self.order != 'id' and self.order not in field_names: 
    73111            # TODO: fix after adding time/changetime to the api.py 
    74112            if order == 'created': 
     
    85123 
    86124    def from_string(cls, env, string, **kw): 
    87125        filters = string.split('&') 
    88         kw_strs = ['order', 'group', 'limit'] 
     126        kw_strs = ['order', 'group', 'page', 'max'] 
    89127        kw_arys = ['rows'] 
    90128        kw_bools = ['desc', 'groupdesc', 'verbose'] 
    91129        constraints = {} 
     
    190228        if not self.cols: 
    191229            self.get_columns() 
    192230 
    193         sql, args = self.get_sql(req) 
    194         self.env.log.debug("Query SQL: " + sql % tuple([repr(a) for a in args])) 
    195  
    196231        if not db: 
    197232            db = self.env.get_db_cnx() 
    198233        cursor = db.cursor() 
     234 
     235        sql, args = self.get_sql(req) 
     236        count_sql = 'SELECT COUNT(*) FROM (' + sql + ') AS foo' 
     237        self.env.log.debug("Query SQL: " + count_sql % tuple([repr(a) for a in args])) 
     238        cursor.execute(count_sql, args); 
     239        for row in cursor: 
     240            pass 
     241        self.env.log.debug("Get Tickets Count: " + str(row[0])) 
     242        self.num_items = row[0] 
     243 
     244        if self.num_items <= self.max: 
     245            self.has_more_pages = False 
     246 
     247        if self.has_more_pages: 
     248            sql = sql + " LIMIT %s OFFSET %s" % (self.max, self.offset) 
     249            if self.page > int(ceil(float(self.num_items) / self.max)) and self.num_items != 0: 
     250                raise ValueError('Page number is too leage') 
     251 
     252        self.env.log.debug("Query SQL: " + sql % tuple([repr(a) for a in args]))      
    199253        cursor.execute(sql, args) 
    200254        columns = get_column_names(cursor) 
    201255        fields = [] 
     
    226280        cursor.close() 
    227281        return results 
    228282 
    229     def get_href(self, href, id=None, order=None, desc=None, format=None): 
     283    def get_href(self, href, id=None, order=None, desc=None, format=None, max=None, page=None): 
    230284        """Create a link corresponding to this query. 
    231285 
    232286        :param href: the `Href` object used to build the URL 
     
    239293        """ 
    240294        if not isinstance(href, Href): 
    241295            href = href.href # compatibility with the `req` of the 0.10 API 
     296 
     297        if format == 'rss': 
     298            max = self.default_max 
     299            page = self.default_page 
     300 
    242301        if id is None: 
    243302            id = self.id 
    244303        if desc is None: 
    245304            desc = self.desc 
    246305        if order is None: 
    247306            order = self.order 
     307        if max is None: 
     308            max = self.max 
     309        if page is None: 
     310            page = self.page 
     311 
    248312        cols = self.get_columns() 
    249313        # don't specify the columns in the href if they correspond to 
    250         # the default columns, in the same order.  That keeps the query url 
    251         # shorter in the common case where we just want the default columns. 
     314        # the default columns, page and max in the same order. That keeps the 
     315        # query url shorter in the common case where we just want the default columns. 
    252316        if cols == self.get_default_columns(): 
    253317            cols = None 
     318        if page == self.default_page: 
     319            page = None 
     320        if max == self.default_max: 
     321            max = None 
     322 
    254323        return href.query(report=id, 
    255324                          order=order, desc=desc and 1 or None, 
    256325                          group=self.group or None, 
    257326                          groupdesc=self.groupdesc and 1 or None, 
    258327                          col=cols, 
    259328                          row=self.rows, 
     329                          max=max, 
     330                          page=page, 
    260331                          format=format, **self.constraints) 
    261332 
    262333    def to_string(self): 
     
    452523            if name == self.group and not name == self.order: 
    453524                sql.append(",") 
    454525        if self.order != 'id': 
    455             sql.append(",t.id") 
    456              
    457         # Limit number of records 
    458         if self.limit: 
    459             sql.append("\nLIMIT %s") 
    460             args.append(self.limit)        
     526            sql.append(",t.id")   
    461527 
    462528        return "".join(sql), args 
    463529 
    464     def template_data(self, context, tickets, orig_list=None, orig_time=None): 
     530    def template_data(self, context, tickets, orig_list=None, orig_time=None, req=None): 
    465531        constraints = {} 
    466532        for k, v in self.constraints.items(): 
    467533            constraint = {'values': [], 'mode': ''} 
     
    529595                    groupsequence.append(group_key) 
    530596        groupsequence = [(value, groups[value]) for value in groupsequence] 
    531597 
     598        results = Paginator(tickets, 
     599                            self.page - 1, 
     600                            self.max, 
     601                            self.num_items) 
     602         
     603        if results.has_next_page: 
     604            next_href = self.get_href(req.href, max=self.max, page=self.page + 1) 
     605            add_link(req, 'next', next_href, _('Next Page')) 
     606 
     607        if results.has_previous_page: 
     608            prev_href = self.get_href(req.href, max=self.max, page=self.page - 1) 
     609            add_link(req, 'prev', prev_href, _('Previous Page')) 
     610 
     611        pagedata = [] 
     612        shown_pages = results.get_shown_pages(21) 
     613        for page in shown_pages: 
     614            pagedata.append([self.get_href(req.href, page=page), None, str(page), 'page ' + str(page)])           
     615 
     616        results.shown_pages = [{'href':p[0], 'class':p[1], 'string':p[2], 'title':p[3]} for p in pagedata] 
     617        results.current_page = {'href':None, 'class':'current', 'string':str(results.page + 1), 'title':None} 
     618 
    532619        return {'query': self, 
    533620                'context': context, 
    534621                'col': cols, 
     
    539626                'fields': fields, 
    540627                'modes': modes, 
    541628                'tickets': tickets, 
    542                 'groups': groupsequence or [(None, tickets)]} 
    543  
    544  
     629                'groups': groupsequence or [(None, tickets)], 
     630                'paginator': results} 
     631     
    545632class QueryModule(Component): 
    546633 
    547634    implements(IRequestHandler, INavigationContributor, IWikiSyntaxProvider, 
     
    607694                       
    608695            if user:  
    609696                qstring = qstring.replace('$USER', user)  
    610             self.log.debug('QueryModule: Using default query: %s', qstring)  
     697            self.log.debug('QueryModule: Using default query: %s', str(qstring))  
    611698            constraints = Query.from_string(self.env, qstring).constraints  
    612699            # Ensure no field constraints that depend on $USER are used  
    613700            # if we have no username.  
     
    631718                      'desc' in req.args, req.args.get('group'), 
    632719                      'groupdesc' in req.args, 'verbose' in req.args, 
    633720                      rows, 
    634                       req.args.get('limit')) 
     721                      req.args.get('page'),  
     722                      req.args.get('max')) 
    635723 
    636724        if 'update' in req.args: 
    637725            # Reset session vars 
     
    698786        db = self.env.get_db_cnx() 
    699787        tickets = query.execute(req, db) 
    700788 
     789        self.env.log.debug("Display Tickets ID: " + ', '.join([str(t['id']) for t in tickets])) 
     790 
    701791        # The most recent query is stored in the user session; 
    702792        orig_list = rest_list = None 
    703793        orig_time = datetime.now(utc) 
     
    718808 
    719809        # Find out which tickets originally in the query results no longer 
    720810        # match the constraints 
    721         if rest_list: 
     811        befor_href = req.session.get('query_href') 
     812        self.env.log.debug("Query href: " + str(query.get_href(Context.from_request(req, 'query').href))) 
     813        if rest_list and (befor_href == query.get_href(Context.from_request(req, 'query').href)): 
    722814            for tid in [t['id'] for t in tickets if t['id'] in rest_list]: 
    723815                rest_list.remove(tid) 
    724816            for rest_id in rest_list: 
     
    734826                tickets.insert(orig_list.index(rest_id), data) 
    735827 
    736828        context = Context.from_request(req, 'query') 
    737         data = query.template_data(context, tickets, orig_list, orig_time) 
     829        data = query.template_data(context, tickets, orig_list, orig_time, req) 
    738830 
    739831        # For clients without JavaScript, we add a new constraint here if 
    740832        # requested 
     
    815907        query_href = req.abs_href.query(group=query.group, 
    816908                                        groupdesc=(query.groupdesc and 1 
    817909                                                   or None), 
    818                                         row=query.rows,  
     910                                        row=query.rows, 
     911                                        page=req.args.get('page'),  
     912                                        max=req.args.get('max'), 
    819913                                        **query.constraints) 
    820914        data = { 
    821915            'context': Context.from_request(req, 'query', absurls=True), 
  • trac/ticket/templates/query.html

     
    2727    </py:def> 
    2828 
    2929    <div id="content" class="query"> 
    30       <h1>$title ${num_matches(len(tickets))}</h1> 
     30      <h1>$title ${num_matches(query.num_items)}</h1> 
    3131 
    3232      <div py:if="description" id="description" xml:space="preserve"> 
    3333        ${wiki_to_html(context(report_resource), description)} 
     
    169169          </py:for> 
    170170        </p> 
    171171 
     172        <p class="option"> 
     173          <label for="max">Max items per page</label> 
     174          <input type="text" name="max" id="max" size="10" value="${query.max}" /> 
     175        </p> 
     176 
    172177        <div class="buttons"> 
    173178          <input py:if="report_resource" type="hidden" name="report" value="$report_resource.id" /> 
    174179          <input type="hidden" name="order" value="$query.order" /> 
  • trac/ticket/templates/report_view.html

     
    55      xmlns:py="http://genshi.edgewall.org/" 
    66      xmlns:xi="http://www.w3.org/2001/XInclude"> 
    77  <xi:include href="layout.html" /> 
     8  <xi:include href="presentation.html" /> 
    89  <head> 
    910    <title>$title</title> 
    1011  </head> 
     
    4041          </div> 
    4142        </form> 
    4243      </div> 
    43  
     44      <py:if test="report.id != -1"> 
     45        <h2 py:if="paginator.has_more_pages"> 
     46          Results 
     47          ${num_results(paginator)} 
     48        </h2> 
     49        ${paging(paginator, chrome)} 
     50      </py:if> 
    4451      <py:for each="value_for_group, row_group in row_groups"> 
    4552        <h2 py:if="value_for_group">$value_for_group 
    4653        <span class="numrows" py:with="cnt = len(row_group)">(${cnt or 'No'} match${cnt != 1 and 'es' or ''})</span></h2> 
     
    145152          </tbody> 
    146153        </table> 
    147154      </py:for> 
    148  
     155      <py:if test="report.id != -1"> 
     156        ${paging(paginator, chrome)} 
     157      </py:if> 
    149158      <div py:if="report.id == -1 and 'REPORT_CREATE' in perm(report.resource)" class="buttons"> 
    150159        <form action="" method="get"> 
    151160          <div> 
  • trac/ticket/templates/query_results.html

     
    1717     xmlns:py="http://genshi.edgewall.org/" 
    1818     xmlns:xi="http://www.w3.org/2001/XInclude"> 
    1919  <xi:include href="macros.html" /> 
    20  
     20  <xi:include href="presentation.html" /> 
     21   
    2122  <py:def function="num_matches(v)"> 
    2223    <span class="numrows">(${v or 'No'} match${v != 1 and 'es' or ''})</span> 
    2324  </py:def> 
    24  
     25   
     26  <h2 py:if="paginator.has_more_pages"> 
     27    Results 
     28    ${num_results(paginator)} 
     29  </h2> 
     30  ${paging(paginator, chrome)} 
    2531  <py:for each="groupname, results in groups"> 
    2632    <h2 py:if="groupname"> 
    2733      ${fields[query.group].label}: $groupname ${num_matches(len(results))} 
     
    8894      </tbody> 
    8995    </table> 
    9096  </py:for> 
     97  ${paging(paginator, chrome)} 
    9198</div> 
    9299 
     100 
  • trac/htdocs/css/trac.css

     
    525525#content.error #traceback td code { white-space: pre; } 
    526526#content.error #traceback pre { font-size: 95%; } 
    527527 
     528#content .paging { margin: 0 0 2em; padding: .5em 0 0; 
     529  font-size: 85%; line-height: 2em; text-align: center; 
     530} 
     531#content .paging .current {  
     532  padding: .1em .3em; 
     533  border: 1px solid #333; 
     534  background: #999; color: #fff;  
     535} 
     536 
     537#content .paging :link, #content .paging :visited { 
     538  padding: .1em .3em; 
     539  border: 1px solid #666; 
     540  background: transparent; color: #666; 
     541} 
     542#content .paging :link:hover, #content .paging :visited:hover { 
     543  background: #999; color: #fff;  border-color: #333; 
     544} 
     545#content .paging .previous a,  
     546#content .paging .next a { 
     547  font-size: 150%; font-weight: bold; border: none; 
     548} 
     549#content .paging .previous a:hover, 
     550#content .paging .next a:hover { 
     551  background: transparent; color: #666; 
     552} 
     553 
     554#content h2 .numresults { color: #666; font-size: 90%; } 
     555 
    528556/* Styles for search word highlighting */ 
    529557@media screen { 
    530558 .searchword0 { background: #ff9 } 
  • trac/htdocs/css/search.css

     
    55#content.search hr { clear: left; margin-bottom: 0 } 
    66#content.search #notfound { margin: 2em; font-size: 110% } 
    77 
    8 #content.search h2 .numresults { color: #666; font-size: 90%; } 
    98#content.search #results { margin-right: 3em } 
    109#content.search #results dt { margin: 1.5em 0 0 } 
    1110#content.search #results dt a { color: #33c } 
     
    1312#content.search #results .author, #results .date { color: #090; } 
    1413 
    1514#content.search #quickjump { font-style: italic; font-weight: bold; } 
    16  
    17 #content.search .paging { margin: 0 0 2em; padding: .5em 0 0; 
    18   font-size: 85%; line-height: 2em; text-align: center; 
    19 } 
    20 #content.search .paging span .current {  
    21   padding: .1em .3em; 
    22   border: 1px solid #333; 
    23   background: #999; color: #fff;  
    24 } 
    25 #content.search .paging :link, #content.search .paging :visited { 
    26   padding: .1em .3em; 
    27   border: 1px solid #666; 
    28   background: transparent; color: #666; 
    29 } 
    30 #content.search .paging :link:hover, #content.search .paging :visited:hover { 
    31   background: #999; color: #fff;  border-color: #333; 
    32 } 
    33 #content.search .paging .previous a,  
    34 #content.search .paging .next a { 
    35   font-size: 150%; font-weight: bold; border: none; 
    36 } 
    37 #content.search .paging .previous a:hover, 
    38 #content.search .paging .next a:hover { 
    39   background: transparent; color: #666; 
    40 } 
  • trac/templates/presentation.html

     
     1<div xmlns="http://www.w3.org/1999/xhtml" 
     2     xmlns:py="http://genshi.edgewall.org/" py:strip="True"> 
     3 
     4  <div py:def="paging(paginator, chrome)" class="paging" py:if="paginator.has_more_pages"> 
     5    <span py:if="paginator.has_previous_page" 
     6          py:with="prevlink = chrome.links.prev[0]" class="previous"> 
     7      <a href="${prevlink.href}" title="${prevlink.title}">&larr;</a> 
     8    </span> 
     9    <py:for each="page in paginator.shown_pages"> 
     10      <span py:if="page['string'] == paginator.current_page['string']"> 
     11        <span py:if="page['string'] == paginator.current_page['string']" class="${paginator.current_page['class']}">${paginator.current_page['string']}</span> 
     12      </span> 
     13      <span py:if="page['string'] != paginator.current_page['string']"> 
     14        <a href="${page['href']}" title="${page['title']}">${page['string']}</a> 
     15      </span> 
     16    </py:for> 
     17    <span py:if="paginator.has_next_page" 
     18          py:with="nextlink = chrome.links.next[0]" class="next"> 
     19      <a href="${nextlink.href}" title="${nextlink.title}">&rarr;</a> 
     20    </span> 
     21  </div> 
     22 
     23  <span py:def="num_results(paginator)"  class="numresults"> 
     24    (${paginator.span[0] + 1} - ${paginator.span[1]} of ${paginator.num_items}) 
     25  </span> 
     26</div> 
  • trac/search/web_ui.py

    Property changes on: trac/templates/presentation.html
    ___________________________________________________________________
    Name: svn:eol-style
       + native
    
     
    112112                results[idx] = {'href': result[0], 'title': result[1], 
    113113                                'date': format_datetime(result[2]), 
    114114                                'author': result[3], 'excerpt': result[4]} 
     115             
     116            pagedata = []     
    115117            data['results'] = results 
     118            shown_pages = results.get_shown_pages(21) 
     119            for shown_page in shown_pages: 
     120                page_href = req.href.search(zip(filters, ['on'] * len(filters)), q=req.args.get('q'), page=shown_page, noquickjump=1) 
     121                pagedata.append([page_href, None, str(shown_page), 'page ' + str(shown_page)]) 
    116122 
     123            results.shown_pages = [{'href':p[0], 'class':p[1], 'string':p[2], 'title':p[3]} for p in pagedata] 
     124            results.current_page = {'href':None, 'class':'current', 'string':str(results.page + 1), 'title':None} 
     125 
    117126            if results.has_next_page: 
    118127                next_href = req.href.search(zip(filters, ['on'] * len(filters)), 
    119128                                            q=req.args.get('q'), page=page + 1, 
  • trac/search/templates/search.html

     
    66      xmlns:xi="http://www.w3.org/2001/XInclude"> 
    77  <xi:include href="layout.html" /> 
    88  <xi:include href="macros.html" /> 
     9  <xi:include href="presentation.html" /> 
    910  <head> 
    1011    <title>Search<py:if test="query"> Results</py:if></title> 
    1112    <py:if test="results"> 
     
    1718      $(document).ready(function() {$("#q").get(0).focus()}); 
    1819    </script> 
    1920  </head> 
    20  
    21   <div py:def="paging" class="paging" py:if="results.has_more_pages"> 
    22     <span py:if="results.has_previous_page" 
    23           py:with="prevlink = chrome.links.prev[0]" class="previous"> 
    24       <a href="${prevlink.href}" title="${prevlink.title}">&larr;</a> 
    25     </span> 
    26     <span py:for="num in range(max(0, results.page - 10), 
    27                                min(results.page + 11, results.num_pages))" 
    28       py:choose=""> 
    29       <span py:when="num == results.page" class="current">${num+1}</span> 
    30       <a py:otherwise="" href="${page_href}&amp;page=${num + 1}">${num + 1}</a> 
    31     </span> 
    32     <span py:if="results.has_next_page" 
    33           py:with="nextlink = chrome.links.next[0]" class="next"> 
    34       <a href="${nextlink.href}" title="${nextlink.title}">&rarr;</a> 
    35     </span> 
    36   </div> 
    37  
    3821  <body> 
    3922    <div id="content" class="search"> 
    4023 
     
    5740      <py:if test="results or quickjump"><hr /> 
    5841        <h2> 
    5942          Results 
    60           <span class="numresults"> 
    61             (${results.span[0] + 1} - ${results.span[1]} of ${results.num_items}) 
    62           </span> 
     43          ${num_results(results)} 
    6344        </h2> 
    64         ${paging()} 
     45        ${paging(results, chrome)} 
    6546        <div> 
    6647          <dl id="results"> 
    6748            <py:if test="quickjump"> 
     
    8061            </py:for> 
    8162          </dl> 
    8263        </div> 
    83         ${paging()} 
     64        ${paging(results, chrome)} 
    8465      </py:if> 
    8566 
    8667      <div id="notfound" py:if="query and not (results or quickjump)"> 
  • trac/util/presentation.py

     
    1919from math import ceil 
    2020from itertools import izip, chain, repeat 
    2121 
    22 __all__ = ['classes', 'first_last', 'group', 'istext', 'paginate', 'Paginator'] 
     22__all__ = ['classes', 'first_last', 'group', 'istext', 'prepared_paginate', 'paginate', 'Paginator'] 
    2323 
    2424 
    2525def classes(*args, **kwargs): 
     
    106106    from genshi.core import Markup 
    107107    return isinstance(text, basestring) and not isinstance(text, Markup) 
    108108 
     109def prepared_paginate(items, num_items, max_per_page): 
     110    if max_per_page == 0: 
     111        num_pages = 1 
     112    else: 
     113        num_pages = int(ceil(float(num_items) / max_per_page)) 
     114    return items, num_items, num_pages 
    109115 
    110116def paginate(items, page=0, max_per_page=10): 
    111117    """Simple generic pagination. 
     
    179185 
    180186class Paginator(object): 
    181187 
    182     def __init__(self, items, page=0, max_per_page=10): 
     188    def __init__(self, items, page=0, max_per_page=10, num_items=None): 
    183189        if not page: 
    184190            page = 0 
    185         offset = page * max_per_page 
     191 
     192        if num_items is None: 
     193            items, num_items, num_pages = paginate(items, page, max_per_page) 
     194        else: 
     195            items, num_items, num_pages = prepared_paginate(items, num_items, max_per_page) 
     196 
     197        offset = page * max_per_page         
    186198        self.page = page 
    187199        self.max_per_page = max_per_page 
    188  
    189         items, num_items, num_pages = paginate(items, page, max_per_page) 
    190  
    191200        self.items = items 
    192201        self.num_items = num_items 
    193202        self.num_pages = num_pages 
     
    216225    def has_previous_page(self): 
    217226        return self.page > 0 
    218227    has_previous_page = property(has_previous_page) 
     228    
     229    def get_shown_pages(self, page_index_count = 11): 
     230        if self.has_more_pages == False: 
     231            return range(1, 2) 
    219232 
     233        min_page = 1 
     234        max_page = int(ceil(float(self.num_items) / self.max_per_page)) 
     235        current_page = self.page + 1 
     236        start_page = current_page - page_index_count / 2 
     237        end_page = current_page + page_index_count / 2 + (page_index_count % 2 - 1) 
     238 
     239        if start_page < min_page: 
     240            start_page = min_page 
     241        if end_page > max_page: 
     242            end_page = max_page 
     243 
     244        return range(start_page, end_page + 1) 
     245 
    220246def separated(items, sep=','): 
    221247    """Yield `(item, sep)` tuples, one for each element in `items`. 
    222248