Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`_get_page` to instantiate the Page class

Tags:

django

I am learning the coding style through reading the Django source code:
In the pagination.py Pagination, the source code define a private method to instantiate the class Page.

def page(self, number):
    """
    Returns a Page object for the given 1-based page number.
    """
    number = self.validate_number(number)
    bottom = (number - 1) * self.per_page
    top = bottom + self.per_page
    if top + self.orphans >= self.count:
        top = self.count
    return self._get_page(self.object_list[bottom:top], number, self)


def _get_page(self, *args, **kwargs):
    """
    Returns an instance of a single page.

    This hook can be used by subclasses to use an alternative to the
    standard :cls:`Page` object.
    """
    return Page(*args, **kwargs)

I think it more readable if instantiate it with method page as

def page(self, number):
    """
    Returns a Page object for the given 1-based page number.
    """
    number = self.validate_number(number)
    bottom = (number - 1) * self.per_page
    top = bottom + self.per_page
    if top + self.orphans >= self.count:
        top = self.count
    return Page(self.object_list[bottom:top], number, self)

What's the advantage to define separate _get_page method in extra step?

like image 679
AbstProcDo Avatar asked Oct 15 '25 20:10

AbstProcDo


2 Answers

The advantage is, like the docstring says (that you also show in your question):

This hook can be used by subclasses to use an alternative to the standard :cls:Page object.

If we just want to change the Page class, we override _get_page() but don't need to repeat the whole logic that builds top and bottom parameters in the page() method (DRY principle).

We would override the page() method only if we want to change some part of the logic calculated in there.

like image 177
Ralf Avatar answered Oct 17 '25 13:10

Ralf


It is already in the comment of the function:

def _get_page(self, *args, **kwargs):
    """
    Returns an instance of a single page.

    This hook can be used by subclasses to use an alternative to the
    standard :cls:`Page` object.
    """
    return Page(*args, **kwargs)

Imagine that later you write your own paging method (with a more advanced Page object), then you will need not only to fix all your code by using the new Page object (and perhaps some still need to use the "old" page object), but you would have to patch some parts of the Django codebase as well.

By introducing a method that acts as a wrapper around the Page constructor, we can monkey patch the construction of a page.

For example we can define our own speciale Page class:

class SpecialPage(Page):

    # ...
    pass

and then we can add a monkey_patching.py file to any of the applications, and write:

import django.core.paginator

def _new_get_page(self, *args, **kwargs):
    return SpecialPage(*args, **kwargs)

django.core.paginator._get_page = _new_get_page

Typically by creating "levels of indirection", one allows a user to fix certain parts at those indirection levels, and thus can inject their own implementation into Django parts.

Subclassing is the most straightforward fix, but not per se the only one. Perhaps you want to preprocess certain arguments, or post-process the Page object, or fire some "signals" to all kind of functions that do something with the Page that is created. Perhaps for a page those are not very logical things, but for other object constructions (like model instances), adding those levels of indirection are more sensical.

like image 39
Willem Van Onsem Avatar answered Oct 17 '25 12:10

Willem Van Onsem