========== Pagination ========== Djem provides some simple helpers to remove common boilerplate in fetching and rendering pages of results among some list of items. In the view =========== .. currentmodule:: djem.pagination Django's ``Page`` object provides a number of helpful methods and attributes that aid in rendering a paged result list, in addition to providing a slice of the result list itself. However, obtaining a ``Page`` instance can involve a bit of boilerplate, particularly in validating the page number and handling various possible invalid states. Moreover, it needs to be repeated in every view that uses pagination. Djem's :func:`get_page` removes this boilerplate. It is a basic wrapper around a Django ``Paginator`` object that primarily provides some sanity checking of the given page number. Internally, it constructs a ``Paginator`` instance and uses its ``page()`` method to retrieve a ``Page``. See the Django `pagination documentation `_ for details on the ``Paginator`` and ``Page`` objects provided by Django. .. versionadded:: 0.5 Usage ----- :func:`get_page` has three required arguments: ``number`` The page number to retrieve, as a 1-based index. The given value is validated in the following ways: * If it is not an integer, ``Paginator`` raises a ``PageNotAnInteger`` exception. ``get_page()`` catches this and returns the first page of results instead. * If it is less than ``1``, ``Paginator`` raises an ``EmptyPage`` exception. ``get_page()`` catches this and returns the first page of results instead. * If it is over the maximum number of pages for the given item sequence, ``Paginator`` also raises an ``EmptyPage`` exception. ``get_page()`` catches this and returns the last page of results instead. ``object_list`` The sequence of items from which to retrieve the specified page. As per the ``Paginator`` object itself, the sequence can be a list, tuple, Django ``QuerySet``, or any other sliceable object with a ``count()`` or ``__len__()`` method. ``per_page`` The number of results to be included in each page. While required by default, a project-wide default value can be set that removes the need to specify ``per_page`` on every call. See :ref:`pagination-page-length` below. While ``get_page()`` only returns a ``Page`` instance, the ``Paginator`` that created it is accessible through the ``Page.paginator`` attribute. Adapting `Django's example `_: .. code-block:: python >>> from djem.pagination import get_page >>> objects = ['john', 'paul', 'george', 'ringo'] >>> page1 = get_page(1, objects, per_page=2) >>> page1.paginator.count 4 >>> page1.paginator.num_pages 2 >>> page1.paginator.page_range range(1, 3) >>> page1 >>> page1.object_list ['john', 'paul'] >>> page1.has_next() True >>> page1.has_previous() False >>> get_page('spam', objects, per_page=2) >>> get_page(-1, objects, per_page=2) >>> get_page(9999, objects, per_page=2) ``get_page()`` also accepts all remaining keyword arguments of the ``Paginator`` constructor, which are passed through to the ``Paginator`` instance created internally. For example, using the ``orphans`` argument with the above example: .. code-block:: python >>> get_page(1, objects, per_page=3) >>> get_page(1, objects, per_page=3, orphans=1) ``get_page()`` can still raise an ``EmptyPage`` exception if ``allow_empty_first_page=False`` is given and the ``object_list`` is empty: .. code-block:: python >>> from djem.pagination import get_page >>> objects = [] >>> page1 = get_page(1, objects, per_page=20, allow_empty_first_page=False) Traceback (most recent call last): ... EmptyPage: That page contains no results .. note:: :func:`get_page` differs from the ``Paginator.get_page()`` `method introduced in Django 2.0 `_ in two ways: * The default page length support :ref:`described below `. * The behaviour of an out-of-range page number: For numbers less than 1, ``Paginator.get_page()`` returns the *last page* while ``get_page()`` returns the *first* page. .. _pagination-page-length: Controlling page length ----------------------- In a lot of cases, sites use a standard page length for multiple (sometimes numerous) paginated lists they display. Djem reduces the need to specify this value in repeated calls to :func:`get_page` by providing a setting that defines this standard page length: :setting:`DJEM_DEFAULT_PAGE_LENGTH`. If added to your ``settings.py`` file, ``get_page()`` will use the defined page length in any call that does not explicitly pass the ``per_page`` argument. Providing the argument will override the default on a call-by-call basis. Not providing the ``per_page`` argument when ``DJEM_DEFAULT_PAGE_LENGTH`` is **not** defined in ``settings.py`` will result in an exception. In the template =============== Rendering a block of links to control pagination of a result list (first, last, next, previous, etc) can also involve boilerplate code and repetition. The :ttag:`paginate` template tag allows one-line generation of such pagination links. Simply pass it the same Django ``Page`` instance used to render the list itself and it will render appropriate page navigation links. This allows quick and easy rendering of a consistent paged-list navigation block site wide. .. versionadded:: 0.6 For example, where ``user_list`` is a ``Page`` instance: .. code-block:: html+django {% load djem %} ... {% for user in user_list %} {{ user.name }} {% endfor %} {% paginate user_list %} ... The structure of the navigation block that is rendered is controlled by the ``djem/pagination.html`` template. Djem ships with a default template, but (as per any template provided by a Django app) this can be overridden by a specific project. See the `Django documentation for overriding templates `_. By default, the block will be rendered as a HTML ordered list (``
    ``) with the following items: * A link to the first page, labelled "First" (hidden if displaying the first page) * A link to the previous page, labelled "Previous" (hidden if displaying the first page) * "Page X of Y", where X is the current page number and Y is the total number of pages * A link to the next page, labelled "Next" (hidden if displaying the last page) * A link to the last page, labelled "Last" (hidden if displaying the last page) The links are defined simply as "?page=n", where n is the relevant page number. To alter the labels, change the format of the links (e.g. the name of the ``GET`` param), or completely change which links are displayed (e.g. adding links to individual page numbers), the ``djem/pagination.html`` will need to be overridden. However, this is not necessary to simply style the default navigation block. The following CSS classes are available by default: * The ``
      `` element has the ``pagination`` class * The ``
    1. `` element containing the first page link has the ``pagination__first`` class * The ``
    2. `` element containing the previous page link has the ``pagination__previous`` class * The ``
    3. `` element containing the next page link has the ``pagination__next`` class * The ``
    4. `` element containing the last page link has the ``pagination__last`` class The following CSS renders the ``
        `` and its items horizontally, removes their indentation and bullets, and adds double-angle quotation marks as glyphs to the "first" and "last" links (|<<| and |>>|): .. |<<| unicode:: U+00AB .. |>>| unicode:: U+00BB .. code-block:: css .pagination { list-style: none; margin-left: 0; text-align: center; } .pagination > li { display: inline-block; margin-left: 0; } .pagination__first > a:before{ content:"\00AB" "\00A0"; } .pagination__last > a:after{ content:"\00A0" "\00BB"; }