djangodjango-viewsdjango-pagination

How to enrich page navigator by adding field from first and last objects in Django paginator?


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">&laquo;</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">&raquo;</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?


Solution

  • I found a solution to implement the following: enter image description here

    1. The view:
    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)
    
    1. The template:
    {% 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">&laquo;</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">&raquo;</a>
            {% endif %}
        </div>
    </div>
    {% endif %}
    {# Page navigator: end #}
    
    1. Custom 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]