Reputation: 141
I am building a Django app form managing the digitalisation process of analog film rolls.
I would like to add the film_roll.from_year
field of the FilmRoll
model to my page navigation widget in a Django template. This greatly facilitates navigating to the right page by looking at the year_from
range below each page.
My view is defined as follows:
def index(request):
film_roll_list = FilmRoll.objects.order_by(
"from_year", "from_month", "title"
).annotate(
scan_count=Count("myScanned_VS_NLP", distinct=True),
)
paginator = Paginator(film_roll_list, 20)
page_number = request.GET.get("page")
try:
page_obj = paginator.get_page(page_number)
except PageNotAnInteger:
# If the page parameter is not an integer, show the first page
page_obj = paginator.get_page(1)
except EmptyPage:
# If the page is out of range, show the last page
page_obj = paginator.page(paginator.num_pages)
film_roll_count = paginator.count
context = {
"film_roll_list": film_roll_list,
"page_obj": page_obj,
"film_roll_count": film_roll_count,
}
return render(request, "filmrolls/index.html", context)
Here's the page navigator code in the template:
{# Page navigator: start #}
{% if page_obj.has_other_pages %}
<div class="row">
<div class="btn-group" role="group" aria-label="Item pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}" class="btn btn-outline-primary">«</a>
{% endif %}
{% for page_number in page_obj.paginator.page_range %}
{% if page_obj.number == page_number %}
<button class="btn btn-outline-primary active">
<span>{{ page_number }} <span class="sr-only"></span></span>
# TODO - add 'year_from' range for the current page
</button>
{% else %}
{% if page_number == paginator.ELLIPSIS %}
<button class="btn">
<span>{{ paginator.ELLIPSIS }} <span class="sr-only"></span></span>
</button>
{% else %}
<a href="?page={{ page_number }}" class="btn btn-outline-primary">
{{ page_number }}
# TODO - add 'year_from' range for each other page
</a>
{% endif %}
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="btn btn-outline-primary">»</a>
{% endif %}
</div>
</div>
{% endif %}
{# Page navigator: end #}
Since the film roll collection is already ordered by year_from
, I could probably fetch year_from
from the first and last object in the paginated list.
Is this something that can be added in the Django template, or do I have to compute these properties in the view?
Upvotes: 0
Views: 47
Reputation: 141
I found a solution to implement the following:
def index(request):
# Paginator: max items per page:
items_per_page = 20
film_roll_list = FilmRoll.objects.order_by(
"from_year", "from_month", "title"
).annotate(
vs_nlp_scan_count=Count("myScanned_VS_NLP", distinct=True),
)
paginator = Paginator(film_roll_list, items_per_page)
date_shot_start = {}
date_shot_end = {}
p = 1
while p <= paginator.num_pages:
index_start = (p - 1) * items_per_page + 1
index_end = min(p * items_per_page + 1, paginator.count - 1)
range_start_year = film_roll_list[index_start].from_year
range_start_month = film_roll_list[index_start].from_month
range_end_year = film_roll_list[index_end].from_year
range_end_month = film_roll_list[index_end].from_month
if range_start_year:
if range_start_month:
date_shot_start[p] = f"{range_start_year}-{range_start_month:02d}"
else:
date_shot_start[p] = f"{range_start_year}"
else:
date_shot_start[p] = "?"
if range_end_year:
if range_end_month:
date_shot_end[p] = f"{range_end_year}-{range_end_month:02d}"
else:
date_shot_end[p] = f"{range_end_year}"
else:
date_shot_end[p] = "?"
p += 1
page_number = request.GET.get("page")
try:
page_obj = paginator.get_page(page_number)
except PageNotAnInteger:
# If the page parameter is not an integer, show the first page
page_obj = paginator.get_page(1)
except EmptyPage:
# If the page is out of range, show the last page
page_obj = paginator.page(paginator.num_pages)
context = {
"page_obj": page_obj,
"date_shot_start": year_start,
"date_shot_end": year_end,
}
return render(request, "filmrolls/index.html", context)
{% load index_tag %}
{# Page navigator: start #}
{% if page_obj.has_other_pages %}
<div class="row">
<div class="btn-group" role="group" aria-label="Item pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}" class="btn btn-outline-primary">«</a>
{% endif %}
{% for page_number in page_obj.paginator.page_range %}
{% if page_obj.number == page_number %}
<button class="btn btn-outline-primary active">
<span>{{ page_number }} <br />{{ date_shot_start|index:page_number }}<br/>-<br/>{{ date_shot_end|index:page_number }}<span class="sr-only"></span></span>
</button>
{% else %}
{% if page_number == paginator.ELLIPSIS %}
<button class="btn">
<span>{{ paginator.ELLIPSIS }} <span class="sr-only"></span></span>
</button>
{% else %}
<a href="?page={{ page_number }}" class="btn btn-outline-primary">
{{ page_number }} <br />{{ date_shot_start|index:page_number }}<br/>-<br/>{{ date_shot_end|index:page_number }}
</a>
{% endif %}
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="btn btn-outline-primary">»</a>
{% endif %}
</div>
</div>
{% endif %}
{# Page navigator: end #}
index
tag (<app>/templatetags/index_tag.py
) to index items in a Django template:from django import template
register = template.Library()
@register.filter
def index(indexable, i):
return indexable[i]
Upvotes: 0
Reputation: 4882
The cleanest way is probably to create a custom pagination schema:
from django.core.paginator import Page, Paginator
# Custom page class so you can add the range per page
class MoviePage(Page):
def __init__(self, object_list, number, paginator):
super().__init__(object_list, number, paginator)
# Add the range
if object_list:
self.first_item_year = object_list[0].from_year
self.last_item_year = object_list[-1].from_year
# Custom paginator for the purpose of invoking the custom page class
class MoviePaginator(Paginator):
def _get_page(self, *args, **kwargs):
return MoviePage(*args, **kwargs)
The above code will only add the range for the page being loaded into the view. If you're trying to map the range for every page all at once, I'm not sure how feasible that is. Maybe you can .annotate()
the queryset with some kind of lookup that selects every n
element and adds its from_year
attribute in dict
with the index of n
.
Upvotes: 0