The @paginate() decorator transparently divides query results into subsets for easier or more useful display. The template of any controller method for which a paginate decorator is defined receives the query results and pagination-specific parameters. The controller just needs to do specify a dataset that shall be paginated, e.g. by passing an SQLObject/SQLAlchemy query. The paginate package also includes a DataGrid-based Widget (PaginateDataGrid) that extends the basic DataGrid widget template to use the pagination-specific parameters.
The following example (and all of the examples on this page) assume a database table named Person with the columns name and age. These examples are taken from the pagedemo1 application attached to this page.
Here is what the controller for a non-DataGrid based pagination implementation looks like:
@expose(template=".templates.paginate1")
@paginate('persons')
def paginate1(self):
persons = Person.select()
return dict(persons=persons)
This assumes that you use the SQLObject ORM. For SQLAlchemy, simply replace Person.select() with Person.query(). The ORM defers actually executing a query until a particular result is used. The controller code above defines a result set (persons) but doesn’t actually use any of the results. This allows the paginate decorator to control which rows are fetched. Here, the @paginate() decorator examines the controller’s return dictionary, finding the persons result set. The decorator modifies the persons object to return a limited number of results, sets a number of variables, and passes everything to the template.
Here is the accompanying template code for this decorator:
<p><b py:for="page in tg.paginate.pages">
<a py:strip="page == tg.paginate.current_page"
href="${tg.paginate.get_href(page)}" py:content="page"/></b></p>
<table border="1">
<tr><th>Name</th><th>Age</th></tr>
<tr py:for="person in persons">
<td py:content="person.name"/><td py:content="person.age"/>
</tr>
</table>
Aside from the code that uses tg.paginate, this is exactly what you would expect to work with a query result. The tg.paginate object is automatically created by the paginate decorator and filled with the data relevant for this page. If you click the numeric links provided by this template, it will take you to the appropriate page in the query result. Let’s take a look at the changes made to your page’s URL that are used to handle pagination. Here is a sample taken from paginate1:
Before we spend any more time looking at the @paginate() decorator, it is probably a good idea to review what the tg.paginate object does. This is used to communicate pagination information to the template. It contains a number of useful variables and the method get_href, which is used to build pagination-aware links. Here is a breakdown of what the member variables contain:
The tg.paginate.get_href method builds a url pointing to the page number specified. It uses tg.paginate.input_values to do it’s work, so ordering (and reversing) results by column will be maintained automatically unless you specify a new value. Here are the parameters for this method:
The @paginate decorator examines the output of the controller function it is decorating, makes some modifications to the dataset it has been instructed to modify, adds a pagination-specific variable (tg.paginate) and sends the newly modified controller function result to the template. The dataset that is handled by @paginate can be an SQLObject SelectResult, an SQLAlchemy Query, a list of Python objects with data fields in (nested) attributes, or a list of Python dicts. Here are the arguments that the paginate decorator accepts:
Some of the variables in tg.paginate, and thus some of the parameters in the URL will affect the behavior of the @paginate decorator. Those URL parameters and their effects are reviewed below:
By itself the paginate decorator is already very useful. However, one of the most common uses for paginate is to limit the amount of data displayed in a table, and we already have a widget that takes care of most of the work for this: DataGrid. The paginate system provides an extension to this widget that handles paged data: PaginateDataGrid. The PaginateDataGrid widget does not change the functionality of DataGrid, it simply adds pagination features to the template. To use PaginateDataGrid instead of a regular DataGrid, you only need to add the paginate decorator to your controller.
Paginate works also with non SQL datasets such as a list of dictionaries:
@expose(template='.templates.paginate5')
@paginate('persons')
def paginate5(self):
return dict(persons=self.persons())
def persons(self):
return [dict(id=i, name='name%d'%i, age=100-i) for i in range(100)]
Don’t forget to change the template accordingly, because person.name and person[‘name’] are different in Python (and thus in Kid templates): Here is the critical part of the paginate5.kid template:
<tr py:for="person in persons">
<td py:content="person['name']"/><td py:content="person['age']"/>
</tr>
If you use PaginateDataGrid, then don’t forget to change the getters for your datagrid columns accordingly.
To include a link inside a datagrid, just replace the string data by an ElementTree.Element.
Here is an example using non SQL data:
try:
from xml.etree import ElementTree
except ImportError:
from elementtree import ElementTree
class MakeLink:
"""Generate the link inside the datagrid."""
def __init__(self, baseurl, id, title, action):
self.baseurl = baseurl
self.id = id
self.title = title
self.action = action
def __call__(self, obj):
url = controllers.url(self.baseurl,
dict(id=obj[self.id], action=self.action))
link = ElementTree.Element(
'a', href=url, style='text-decoration: underline')
link.text = obj[self.title]
return link
# to get this kind of link : http://localhost:8080/person?action=edit&id=6
mylink = MakeLink('person', 'id', 'name', 'edit')
# just to get a element from a dictionary
def get(fieldname):
return lambda x: x.get(fieldname)
data_grid = PaginateDataGrid(
fields = [
PaginateDataGrid.Column(name='name', getter=mylink, title='Name',
options=dict(sortable=True)),
PaginateDataGrid.Column(name='age', getter=get('age'), title='Age',
options=dict(sortable=True, reverse_order=True)),
])
Instead of an ElementTree.Element, you can also use a kid.Element, or you can use the kid.XML function to pass the XHTML snippet for the link. Alternatively, you can also use a custom widget for creating the link.
The controller is very simple and use some the code from above:
@expose(template='.templates.paginate2')
@paginate('persons', default_order='name')
def paginate4(self):
return dict(persons=self.persons(), list=data_grid)
Attached are two demo applications using paginate in various ways. The paginate demo application 1 (attached as pagedemo1.tar.gz and pagedemo1.zip) contains the examples above, the paginate demo application 2 (attached as pagedemo2.tar.gz and pagedemo2.zip) offers some more sophisticated examples.