Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Ticket #2302: paginate.patch

File paginate.patch, 8.6 KB (added by chrisz, 3 years ago)

improved paginate decorator

  • tg/decorators.py

     
    2222from webhelpers.paginate import Page 
    2323from pylons import config, request, response 
    2424from pylons.controllers.util import abort 
    25 from pylons import tmpl_context as c 
     25from tg import tmpl_context 
    2626from tg.util import partial 
    2727from repoze.what.plugins.pylonshq import ActionProtector, ControllerProtector 
    2828 
     
    3030from tg.flash import flash 
    3131#from tg.controllers import redirect 
    3232 
     33 
    3334class Decoration(object): 
    3435    """ Simple class to support 'simple registration' type decorators 
    3536    """ 
     
    304305                self.content_type, self.engine, self.template, self.exclude_names) 
    305306        return func 
    306307 
     308 
    307309def use_custom_format(controller, custom_format): 
    308310    """Use use_custom_format in a controller in order to change 
    309311    the active @expose decorator when available.""" 
     
    315317 
    316318    deco.render_custom_format = custom_format 
    317319 
     320 
    318321def override_template(controller, template): 
    319322    """Use overide_template in a controller in order to change the 
    320323    template that will be used to render the response dictionary 
     
    384387        return func 
    385388 
    386389 
    387 def paginate(name, items_per_page=10, use_prefix=False): 
    388     """ 
    389     Paginate a given collection. 
     390class paginate(object): 
     391    """Paginate a given collection. 
    390392 
    391393    This decorator is mainly exposing the functionality 
    392394    of :func:`webhelpers.paginate`. 
     
    405407 
    406408    To render the actual pager, use:: 
    407409 
    408       ${c.paginators.<name>.pager()} 
     410      ${tmpl_context.paginators.<name>.pager()} 
    409411 
    410     where c is the tmpl_context. 
    411  
    412412    It is possible to have several :func:`paginate`-decorators for 
    413413    one controller action to paginate several collections independently 
    414414    from each other. If this is desired, don't forget to set the :attr:`use_prefix`-parameter 
     
    417417    :Parameters: 
    418418      name 
    419419        the collection to be paginated. 
    420       items_per_page 
    421         the number of items to be rendered. Defaults to 10 
    422420      use_prefix 
    423421        if True, the parameters the paginate 
    424422        decorator renders and reacts to are prefixed with 
    425423        "<name>_". This allows for multi-pagination. 
     424      items_per_page 
     425        the number of items to be rendered. Defaults to 10. 
     426      max_items_per_page 
     427        the maximum number of items allowed to be set via parameter. 
     428        Defaults to 0 (does not allow to change that value). 
    426429 
    427430    """ 
    428     prefix = "" 
    429     if use_prefix: 
    430         prefix = name + "_" 
    431     own_parameters = dict( 
    432         page="%spage" % prefix, 
    433         items_per_page="%sitems_per_page" % prefix 
    434         ) 
    435     #@decorator 
    436     def _d(f): 
    437         def _w(*args, **kwargs): 
    438             page = int(kwargs.pop(own_parameters["page"], 1)) 
    439             real_items_per_page = int( 
    440                     kwargs.pop( 
    441                             own_parameters['items_per_page'], 
    442                             items_per_page)) 
    443431 
    444             res = f(*args, **kwargs) 
    445             if isinstance(res, dict) and name in res: 
    446                 additional_parameters = MultiDict() 
    447                 for key, value in request.str_params.iteritems(): 
    448                     if key not in own_parameters: 
    449                         additional_parameters.add(key, value) 
     432    def __init__(self, name, use_prefix=False, 
     433            items_per_page=10, max_items_per_page=0): 
     434        self.name = name 
     435        prefix = use_prefix and name + '_' or '' 
     436        self.page_param = prefix + 'page' 
     437        self.items_per_page_param = prefix + 'items_per_page' 
     438        self.items_per_page = items_per_page 
     439        self.max_items_per_page = max_items_per_page 
    450440 
    451                 collection = res[name] 
    452                 page = Page( 
    453                     collection, 
    454                     page, 
    455                     items_per_page=real_items_per_page, 
    456                     **additional_parameters.dict_of_lists() 
    457                     ) 
    458                 # wrap the pager so that it will render 
    459                 # the proper page-parameter 
    460                 page.pager = partial(page.pager, 
    461                         page_param=own_parameters["page"]) 
    462                 res[name] = page 
    463                 # this is a bit strange - it appears 
    464                 # as if c returns an empty 
    465                 # string for everything it dosen't know. 
    466                 # I didn't find that documented, so I 
    467                 # just put this in here and hope it works. 
    468                 if not hasattr(c, 'paginators') or type(c.paginators) == str: 
    469                     c.paginators = Bunch() 
    470                 c.paginators[name] = page 
    471             return res 
    472         return _w 
    473     return _d 
     441    def __call__(self, func): 
     442        decoration = Decoration.get_decoration(func) 
     443        decoration.register_hook('before_validate', self.before_validate) 
     444        decoration.register_hook('before_render', self.before_render) 
     445        return func 
    474446 
     447    def before_validate(self, remainder, params): 
     448        page = params.pop(self.page_param, None) 
     449        if page: 
     450            try: 
     451                page = int(page) 
     452                if page < 1: 
     453                    raise ValueError 
     454            except ValueError: 
     455                page = 1 
     456        else: 
     457            page = 1 
     458        request.paginate_page = page or 1 
     459        items_per_page = params.pop(self.items_per_page_param, None) 
     460        if items_per_page: 
     461            try: 
     462                items_per_page = min( 
     463                    int(items_per_page), self.max_items_per_page) 
     464                if items_per_page < 1: 
     465                    raise ValueError 
     466            except ValueError: 
     467                items_per_page = self.items_per_page 
     468        else: 
     469            items_per_page = self.items_per_page 
     470        request.paginate_items_per_page = items_per_page 
     471        request.paginate_params = params.copy() 
     472        if items_per_page != self.items_per_page: 
     473            request.paginate_params[self.items_per_page_param] = items_per_page 
     474 
     475    def before_render(self, remainder, params, output): 
     476        if not isinstance(output, dict) or not self.name in output: 
     477            return 
     478        collection = output[self.name] 
     479        page = Page(collection, request.paginate_page, 
     480            request.paginate_items_per_page) 
     481        page.kwargs = request.paginate_params 
     482        if self.page_param != 'name': 
     483            page.pager = partial(page.pager, page_param=self.page_param) 
     484        if not getattr(tmpl_context, 'paginators', None): 
     485            tmpl_context.paginators = Bunch() 
     486        tmpl_context.paginators[self.name] = output[self.name] = page 
     487 
     488 
    475489@decorator 
    476490def postpone_commits(func, *args, **kwargs): 
    477491    """Turns sqlalchemy commits into flushes in the decorated method 
     
    488502    s.commit = old_commit 
    489503    return retval 
    490504 
     505 
    491506@decorator 
    492507def without_trailing_slash(func, *args, **kwargs): 
    493508    """This decorator allows you to ensure that the URL does not end in "/" 
     
    512527        redirect(request.url[:-1]) 
    513528    return func(*args, **kwargs) 
    514529 
     530 
    515531@decorator 
    516532def with_trailing_slash(func, *args, **kwargs): 
    517533    """This decorator allows you to ensure that the URL ends in "/" 
     
    543559class require(ActionProtector): 
    544560    """ 
    545561    TurboGears-specific repoze.what-pylons action protector. 
    546      
     562 
    547563    The default authorization denial handler of this protector will flash 
    548564    the message of the unmet predicate with ``warning`` or ``error`` as the 
    549565    flash status if the HTTP status code is 401 or 403, respectively. 
    550      
     566 
    551567    See :class:`allow_only` for controller-wide authorization. 
    552      
     568 
    553569    """ 
    554      
     570 
    555571    def default_denial_handler(self, reason): 
    556572        """Authorization denial handler for repoze.what-pylons protectors.""" 
    557573        if response.status_int == 401: 
     
    566582class allow_only(ControllerProtector): 
    567583    """ 
    568584    TurboGears-specific repoze.what-pylons controller protector. 
    569      
     585 
    570586    The default authorization denial handler of this protector will flash 
    571587    the message of the unmet predicate with ``warning`` or ``error`` as the 
    572588    flash status if the HTTP status code is 401 or 403, respectively, since 
    573589    by default the ``__before__`` method of the controller is decorated with 
    574590    :class:`require`. 
    575      
     591 
    576592    If the controller class has the ``_failed_authorization`` *class method*, 
    577593    it will replace the default denial handler. 
    578      
     594 
    579595    """ 
    580596    protector = require 
    581      
     597 
    582598    def __call__(self, cls, *args, **kwargs): 
    583599        if hasattr(cls, '_failed_authorization'): 
    584600            self.denial_handler = cls._failed_authorization