Reputation: 57741
I'm trying to understand some source code which is used in a ListView
to generate pagination links at the bottom of the page. For example, on a page listing 'experts', it looks like this:
It seems to be implemented somewhat differently from the way described in https://docs.djangoproject.com/en/2.0/topics/pagination/. There is an inclusion tag called paginator
:
from django import template
from django.core.paginator import EmptyPage
from dashboard.views.utils import query_dict_from_params_or_cookies
register = template.Library()
def query_params(params):
query = params.copy()
if query.get('page'):
del query['page']
return '&' + query.urlencode() if query.urlencode() else ''
@register.inclusion_tag('templatetags/paginator.html', takes_context=True)
def paginator(context, adjacent_pages=2):
request = context['request']
page_obj = context['page_obj']
page_range = context['paginator'].page_range
page_number = page_obj.number
last_page_number = len(page_range)
left_idx = max(0, page_number - adjacent_pages - 1)
right_idx = min(page_number + adjacent_pages, last_page_number)
if left_idx == 2:
left_idx -= 1
if right_idx == last_page_number - 2:
right_idx += 1
window = page_range[left_idx:right_idx]
try:
previous_page_number = page_obj.previous_page_number()
except EmptyPage:
previous_page_number = None
try:
next_page_number = page_obj.next_page_number()
except EmptyPage:
next_page_number = None
params = query_dict_from_params_or_cookies(request)
return {
'has_previous': page_obj.has_previous(),
'has_next': page_obj.has_next(),
'previous_page_number': previous_page_number,
'next_page_number': next_page_number,
'last_page_number': last_page_number,
'page_range': window,
'page_number': page_number,
'show_first': page_number > adjacent_pages + 1,
'show_left_gap': page_number > adjacent_pages + 3,
'show_last': page_number < last_page_number - adjacent_pages,
'show_right_gap': page_number < last_page_number - adjacent_pages - 2,
'query_params': query_params(params)
}
The corresponding template, templatetags/paginator.html
, looks like this:
<ul class="pagination right"
{% if has_previous %}data-previous-page="?page={{ previous_page_number }}{{ query_params }}"{% endif %}
{% if has_next %}data-next-page="?page={{ next_page_number }}{{ query_params }}"{% endif %}>
{% if has_previous %}
<li class="waves-effect">
<a href="?page={{ previous_page_number }}{{ query_params }}">
<i class="material-icons">chevron_left</i>
</a>
</li>
{% else %}
<li class="disabled">
<a><i class="material-icons">chevron_left</i></a>
</li>
{% endif %}
{% if show_first %}
<li><a href="?page=1{{ query_params }}">1</a></li>
{% endif %}
{% if show_left_gap %}
<li class="disabled"><a>...</a></li>
{% endif %}
{% for i in page_range %}
{% if page_number == i %}
<li class="active"><a>{{ i }}</a></li>
{% else %}
<li class="waves-effect"><a href="?page={{ i }}{{ query_params }}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if show_right_gap %}
<li class="disabled"><a>...</a></li>
{% endif %}
{% if show_last %}
<li>
<a href="?page={{ last_page_number }}{{ query_params }}">{{ last_page_number }}</a>
</li>
{% endif %}
{% if has_next %}
<li class="waves-effect">
<a href="?page={{ next_page_number }}{{ query_params }}">
<i class="material-icons">chevron_right</i>
</a>
</li>
{% else %}
<li class="disabled">
<a><i class="material-icons">chevron_right</i></a>
</li>
{% endif %}
</ul>
What I don't quite understand is how paginator
and page_obj
get inserted into the template's context. For example, I did a project-wide search for page_obj
and it does not appear that it is being injected in our own code, which would mean that it is being injected by Django's source code. But how is this being done?
Also, it seems to me like some of the logic essentially replicates what is in Django's Paginator
class and this code could be simplified/refactored, would anyone agree?
Upvotes: 3
Views: 2224
Reputation: 57741
It would appear that this is indeed done by Django's source code - specifically, by the generic ListView
. The get_context_data()
method of the MultipleObjectMixin
(from which ListView
inherits) reads:
def get_context_data(self, *, object_list=None, **kwargs):
"""Get the context for this view."""
queryset = object_list if object_list is not None else self.object_list
page_size = self.get_paginate_by(queryset)
context_object_name = self.get_context_object_name(queryset)
if page_size:
paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
context = {
'paginator': paginator,
'page_obj': page,
'is_paginated': is_paginated,
'object_list': queryset
}
else:
context = {
'paginator': None,
'page_obj': None,
'is_paginated': False,
'object_list': queryset
}
if context_object_name is not None:
context[context_object_name] = queryset
context.update(kwargs)
return super().get_context_data(**context)
Clearly, paginator
and page_obj
are added to the context here.
Upvotes: 1