Changeset 2404

Show
Ignore:
Timestamp:
01/19/07 11:11:44 (2 years ago)
Author:
alberto
Message:

paginate.py now supports SA, improves sorting and fixes CompoundWidget? submissions. Closes #1115. Thanks Randall

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/1.0/turbogears/paginate.py

    r2326 r2404  
     1import re 
     2import types 
    13from math import ceil 
    24import logging 
     
    68    from sqlobject.main import SelectResults 
    79except ImportError: 
    8     pass 
    9  
     10    SelectResults = None 
     11 
     12try: 
     13    # Can't depend on sqlalchemy being available. 
     14    import sqlalchemy 
     15    from sqlalchemy.ext.selectresults import SelectResults as SASelectResults 
     16except ImportError: 
     17    SASelectResults = None 
     18  
    1019import turbogears 
    1120from turbogears.decorator import weak_signature_decorator 
    1221from turbogears.view import variable_providers 
     22from formencode.variabledecode import variable_encode, variable_decode 
    1323 
    1424log = logging.getLogger("turbogears.paginate") 
     
    2030            page = int(kw.pop('tg_paginate_no', 1)) 
    2131            limit_ = int(kw.pop('tg_paginate_limit', limit)) 
    22             order = kw.pop('tg_paginate_order', default_order) 
    23             reversed = kw.pop('tg_paginate_reversed', None) 
    24              
     32            order = kw.pop('tg_paginate_order', None) 
     33            ordering = kw.pop('tg_paginate_ordering', {}) 
     34 
     35            # Convert ordering str to a dict. 
     36            if ordering: 
     37                ordering = convert_ordering(ordering) 
     38 
    2539            if not allow_limit_override: 
    2640                limit_ = limit 
    27              
    28             log.debug("Pagination params: page=%s, limit=%s, order=%s,
    29                       "reversed=%s", page, limit_, order, reversed
     41 
     42            log.debug("Pagination params: page=%s, limit=%s, order=%s
     43                      "", page, limit_, order
    3044             
    3145            # get the output from the decorated function     
     
    4155                raise "If you want to enable ordering you need " \ 
    4256                      "to provide a default_order"  
    43              
     57            elif default_order and not ordering: 
     58                ordering = {default_order:[0, True]} 
     59            elif ordering and order: 
     60                sort_ordering(ordering, order) 
     61            log.debug('ordering %s' % ordering) 
     62 
    4463            row_count = 0 
    45             if isinstance(var_data, SelectResults): 
     64            if (SelectResults and isinstance(var_data, SelectResults)) or \ 
     65               (SASelectResults and isinstance(var_data, SASelectResults)): 
    4666                row_count = var_data.count() 
    47                 col = getattr(var_data.sourceClass.q, order, None) 
    48                 if default_order: 
    49                     if col: 
    50                         var_data = var_data.orderBy(col) 
     67                if ordering: 
     68                    # Build order_by list. 
     69                    order_cols = range(len(ordering)) 
     70                    for (colname, order_opts) in ordering.items(): 
     71                        col = sql_get_column(colname, var_data) 
     72                        if not col: 
     73                            raise StandardError, "The order column (%s) doesn't exist" % colname 
     74                        order_by_expr = sql_order_col(col, order_opts[1]) 
     75                        order_cols[order_opts[0]] = order_by_expr 
     76                    # May need to address potential of ordering already 
     77                    # existing in var_data. 
     78                    # SO and SA differ on this method name. 
     79                    if hasattr(var_data, 'orderBy'): 
     80                        var_data = var_data.orderBy(order_cols) 
    5181                    else: 
    52                         raise "The order column (%s) doesn't exist" % order 
    53                     if reversed: 
    54                         var_data = var_data.reversed() 
     82                        var_data = var_data.order_by(order_cols) 
    5583            elif isinstance(var_data, list): 
    5684                row_count = len(var_data) 
     
    7199            # which one should we use? cherrypy.request.input_values or kw? 
    72100            #input_values = cherrypy.request.input_values.copy() 
    73             input_values = kw.copy() 
     101            ##input_values = kw.copy() 
     102            input_values =  variable_encode(cherrypy.request.params.copy()) 
    74103            input_values.pop('self', None) 
    75              
     104            for input_key in input_values.keys(): 
     105                if input_key.startswith('tg_paginate'): 
     106                    del input_values[input_key] 
     107 
    76108            cherrypy.request.paginate = Paginate(current_page=page, 
    77                                                  limit=limit_,  
    78                                                  pages=pages_to_show,  
    79                                                  page_count=page_count,  
    80                                                  input_values=input_values,  
    81                                                  order=order, 
    82                                                  reversed=reversed
    83                                                   
     109                                             limit=limit_,  
     110                                             pages=pages_to_show,  
     111                                             page_count=page_count,  
     112                                             input_values=input_values,  
     113                                             order=order, 
     114                                             ordering=ordering
     115                                              
    84116            # we replace the var with the sliced one 
    85117            endpoint = offset + limit_ 
     
    102134    """class for variable provider""" 
    103135    def __init__(self, current_page, pages, page_count, input_values,  
    104                  limit, order, reversed): 
     136                 limit, order, ordering): 
     137                  
    105138        self.pages = pages 
    106139        self.limit = limit 
     
    109142        self.input_values = input_values 
    110143        self.order = order 
    111         self.reversed = reversed 
    112          
    113         self.input_values.update(dict(tg_paginate_limit=limit, 
    114                                       tg_paginate_order=order, 
    115                                       tg_paginate_reversed=reversed)) 
     144        self.ordering = ordering 
     145        self.reversed = False 
     146 
     147        # Should reversed be true? 
     148        for (field_name, ordering_values) in ordering.items(): 
     149            if ordering_values[0] == 0 and not ordering_values[1]: 
     150                self.reversed = True 
     151 
     152        # If ordering is empty, don't add it. 
     153        input_values = dict(tg_paginate_limit=limit) 
     154        if ordering: 
     155            input_values['tg_paginate_ordering'] = ordering 
     156        self.input_values.update(input_values) 
     157 
    116158        if current_page < page_count: 
    117159            self.input_values.update(dict( 
     
    141183             
    142184    def get_href(self, page, order=None, reverse_order=None): 
     185        # Note that reverse_order is not used.  It should be cleaned up here 
     186        # and in the template.  I'm not removing it now because I don't want 
     187        # to break the API. 
     188        order = order or None 
     189        self.input_values['tg_paginate_no'] = page 
    143190        if order: 
    144             if order == self.order: 
    145                 if self.reversed: 
    146                     reversed = None 
    147                 else: 
    148                     reversed = True 
    149             else: 
    150                 reversed = None 
    151                 if reverse_order: 
    152                     if reversed: 
    153                         reversed = None 
    154                     else: 
    155                         reversed = True 
    156         else: 
    157             order = self.order 
    158             reversed = self.reversed 
    159          
    160         self.input_values.update(dict(tg_paginate_no=page,  
    161                                       tg_paginate_order=order, 
    162                                       tg_paginate_reversed=reversed)) 
    163          
     191            self.input_values['tg_paginate_order'] = order 
     192 
    164193        return turbogears.url('', self.input_values) 
    165  
    166194 
    167195def _select_pages_to_show(current_page, page_count, max_pages): 
     
    190218         
    191219    return range(start, end+1) 
     220 
     221def sort_ordering(ordering, sort_name): 
     222    """Rearrange ordering based on sort_name.""" 
     223    log.debug('sort called with %s and %s' % (ordering, sort_name)) 
     224    if sort_name not in ordering: 
     225        ordering[sort_name] = [-1, True]  
     226    if ordering[sort_name][0] == 0: 
     227        # Flip 
     228        ordering[sort_name][1] = not ordering[sort_name][1] 
     229    else: 
     230        ordering[sort_name][0] = 0 
     231        for key in ordering.keys(): 
     232            if key != sort_name and ordering[key][0] < len(ordering) - 1: 
     233                ordering[key][0] += 1 
     234    log.debug('sort results is %s and %s' % (ordering, sort_name)) 
     235 
     236def sql_get_column(colname, var_data): 
     237    """Return a column from var_data based on colname.""" 
     238    if isinstance(var_data, SelectResults): 
     239        col = getattr(var_data.sourceClass.q, colname, None) 
     240    elif isinstance(var_data, SASelectResults): 
     241        col = getattr(var_data._query.table.c, colname, None) 
     242    else: 
     243        raise StandardError, 'expected SelectResults' 
     244    return col 
     245 
     246def sql_order_col(col, ascending=True): 
     247    """Return an ordered col for col.""" 
     248    if isinstance(col, sqlalchemy.schema.Column): 
     249        if ascending: 
     250            order_col = sqlalchemy.sql.asc(col) 
     251        else: 
     252            order_col = sqlalchemy.sql.desc(col) 
     253    elif isinstance(col, types.InstanceType): 
     254        # I don't like using InstanceType, but that's what sqlobject col type 
     255        # is. 
     256        if ascending: 
     257            order_col = col 
     258        else: 
     259            order_col = sqlobject.DESC(col) 
     260    else: 
     261        raise StandardError, 'expected Column, but got %s' % str(type(col)) 
     262    return order_col 
     263    
     264# Ordering re: 
     265ordering_expr = re.compile(r"('\w+'): ?\[(\d+), ?(True|False)\]") 
     266 
     267def convert_ordering(ordering): 
     268    """Covert ordering unicode string to dict.""" 
     269 
     270    log.debug('ordering received %s' % str(ordering)) 
     271 
     272    # eval would be simple, but insecure. 
     273    if not isinstance(ordering, (str, unicode)): 
     274        raise ValueError, "ordering should be string or unicode." 
     275    new_ordering = {} 
     276    if ordering == u"{}": 
     277        pass 
     278    else: 
     279        try: 
     280            ordering_info_find = ordering_expr.findall(ordering) 
     281            emsg = "Didn't match ordering for %s." % str(ordering) 
     282            assert len(ordering_info_find) > 0, emsg 
     283            for ordering_info in ordering_info_find: 
     284                ordering_key = str(ordering_info[0]).strip("'") 
     285                ordering_order = int(ordering_info[1]) 
     286                ordering_reverse = bool(ordering_info[2] == 'True') 
     287                new_ordering[ordering_key] = [ordering_order, 
     288                                              ordering_reverse] 
     289        except StandardError, e: 
     290            log.debug('FAILED to convert ordering.') 
     291            new_ordering = {} 
     292    log.debug('ordering converted to %s' % str(new_ordering)) 
     293    return new_ordering