Skip to content

Pagination

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_page and max item size (max_size) and, allows users to set the page property.
  • LimitOffsetPagination: This pagination internally configures max item size (max_limit) and, allows users to set the limit and offset properties.

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)

paginate properties

  • 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 Model type to get list of data. If set, route function can return None or 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_context is False. It is used to serialize the SQLAlchemy model and create a response-schema/docs.
  • paginator_options:t.Any: keyword argument for configuring pagination_class set to use for pagination.

API 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 User
We 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():
    pass

Template Pagination

This 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 context
In 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 %}