Pagination is a common practice for large datasets, enhancing user experience by breaking content into manageable pages. It optimizes load times and navigation and allows users to explore extensive datasets with ease while maintaining system performance and responsiveness.
EllarSQL offers two styles of pagination:
- PageNumberPagination: This pagination internally configures items
per_pageand max item size (max_size) and, allows users to set thepageproperty. - LimitOffsetPagination: This pagination internally configures max item size (
max_limit) and, allows users to set thelimitandoffsetproperties.
EllarSQL pagination is activated when a route function is decorated with paginate function.
The result of the route function is expected to be a SQLAlchemy.sql.Select instance or a Model type.
For example:
import ellar.common as ec
from ellar_sql import model, paginate
from .models import User
from .schemas import UserSchema
@ec.get('/users')
@paginate(item_schema=UserSchema)
def list_users():
return model.select(User)- pagination_class: t.Optional[t.Type[PaginationBase]]=None: specifies pagination style to use. if not set, it will be set to
PageNumberPagination - model: t.Optional[t.Type[ModelBase]]=None: specifies a
Modeltype to get list of data. If set, route function can returnNoneor override by returning a select/filtered statement - as_template_context: bool=False: indicates that the paginator object be added to template context. See Template Pagination
- item_schema: t.Optional[t.Type[BaseModel]]=None: This is required if
template_contextis False. It is used to serialize the SQLAlchemy model and create a response-schema/docs. - paginator_options:t.Any: keyword argument for configuring
pagination_classset to use for pagination.
API pagination simply means pagination in an API route function.
This requires item_schema for the paginate decorator
to create a 200 response documentation for the decorated route and for the paginated result to be serialized to json.
import ellar.common as ec
from ellar_sql import paginate
from .models import User
class UserSchema(ec.Serializer):
id: int
username: str
email: str
@ec.get('/users')
@paginate(item_schema=UserSchema, per_page=100)
def list_users():
return UserWe can also rewrite the illustration above since we are not making any modification to the User query.
...
@ec.get('/users')
@paginate(model=User, item_schema=UserSchema)
def list_users():
passThis is for route functions
decorated with render function
that need to be paginated.
For this to happen, paginate
function need to return a context and this is achieved by setting as_template_context=True
import ellar.common as ec
from ellar_sql import model, paginate
from .models import User
@ec.get('/users')
@ec.render('list.html')
@paginate(as_template_context=True)
def list_users():
return model.select(User), {'name': 'Template Pagination'} # pagination model, template contextIn the illustration above, a tuple of select statement and a template context was returned.
The template context will be updated with a paginator as an extra key by the paginate function
before been processed by render function.
We can re-write the example above to return just the template context since there is no form of
filter directly affecting the User model query.
...
@ec.get('/users')
@ec.render('list.html')
@paginate(model=model.select(User), as_template_context=True)
def list_users():
return {'name': 'Template Pagination'}Also, in the list.html we have the following codes:
<!DOCTYPE html>
<html lang="en">
<h3>{{ name }}</h3>
{% macro render_pagination(paginator, endpoint) %}
<div>
{{ paginator.first }} - {{ paginator.last }} of {{ paginator.total }}
</div>
<div>
{% for page in paginator.iter_pages() %}
{% if page %}
{% if page != paginator.page %}
<a href="{{ url_for(endpoint) }}?page={{page}}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{% endfor %}
</div>
{% endmacro %}
<ul>
{% for user in paginator %}
<li>{{ user.id }} @ {{ user.name }}
{% endfor %}
</ul>
{{render_pagination(paginator=paginator, endpoint="list_users") }}
</html>The paginator object in the template context has a iter_pages() method which produces up to three group of numbers,
seperated by None.
It defaults to showing 2 page numbers at either edge, 2 numbers before the current, the current, and 4 numbers after the current. For example, if there are 20 pages and the current page is 7, the following values are yielded.
paginator.iter_pages()
[1, 2, None, 5, 6, 7, 8, 9, 10, 11, None, 19, 20]
The total attribute showcases the total number of results, while first and last display the range of items on the current page.
The accompanying Jinja macro renders a simple pagination widget.
{% macro render_pagination(paginator, endpoint) %}
<div>
{{ paginator.first }} - {{ paginator.last }} of {{ paginator.total }}
</div>
<div>
{% for page in paginator.iter_pages() %}
{% if page %}
{% if page != paginator.page %}
<a href="{{ url_for(endpoint) }}?page={{page}}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{% endfor %}
</div>
{% endmacro %}