0

When using Django CBV ListView with pagination:

class Proposals(ListView):
    model = Proposal
    ordering = "id"
    paginate_by = 10

In the browser, if I provide a page that is out of range, I get an error:

enter image description here

I would like to have a different behaviour: to fallback to the last existing page if the provided page is out of range.

I dug into Django source code paginator.py file and was surprised to find some code that does exactly this:

enter image description here

So using paginator.get_page(page) (and not paginator.page(page)) would be the way to go. However, ListView does not use it as you can see here:

enter image description here

What is the best way to deal with this?

Thanks.

David Dahan
  • 10,576
  • 11
  • 64
  • 137

1 Answers1

0

The only solution I found is by overriding the paginate_queryset method. However I don't like it as I'm forced to rewrite the whole logic while I just want to change a single line.

Open to any better suggestion.

class PermissivePaginationListView(ListView):
    def paginate_queryset(self, queryset, page_size):
        """
        This is an exact copy of the original method, jut changing `page` to `get_page` method to prevent errors with out of range pages.
        This is useful with HTMX, when the last row of the table is deleted, as the current page in URL is not valid anymore because there is no result in it.
        """
        paginator = self.get_paginator(
            queryset,
            page_size,
            orphans=self.get_paginate_orphans(),
            allow_empty_first_page=self.get_allow_empty(),
        )
        page_kwarg = self.page_kwarg
        page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
        try:
            page_number = int(page)
        except ValueError:
            if page == "last":
                page_number = paginator.num_pages
            else:
                raise Http404(_("Page is not “last”, nor can it be converted to an int."))
        try:
            page = paginator.get_page(page_number)
            return (paginator, page, page.object_list, page.has_other_pages())
        except InvalidPage as e:
            raise Http404(
                _("Invalid page (%(page_number)s): %(message)s")
                % {"page_number": page_number, "message": str(e)}
            )
David Dahan
  • 10,576
  • 11
  • 64
  • 137
  • This is odd… `get_page()` should handle that for you according to the documentation. I’ll have to dig into the CBV ListView code, this might warrant a change request to either the docs or the code. – Jarvis Aug 06 '22 at 08:34
  • @Jarvis I added the ListView code in the question to help. – David Dahan Aug 06 '22 at 10:39
  • Yea, I think that should be documented with a best practice if you need the IMHO better behavior… or maybe a flag you can set… – Jarvis Aug 07 '22 at 07:00