Django Forms: Pagination of Filtered Results ¶
Django’s forms can be used, in conjunction with HTML forms, for pagination, using the inbuilt Django pagination features. Basically, anytime you have a URL that may have some parts of the query string that may need to be built, it’s simpler to use a form element, than to manually build up the url in your template.
Working example:
views.py
from django import forms from django.contrib.auth.decorators import login_required from django.views.generic import ListView, DetailView from django.views.generic.edit import FormMixin class NotificationRecordFilterForm(forms.Form): message_text = forms.CharField(label=_('Search'), required=False) def filter_by(self): return { 'notification__message__icontains': self.cleaned_data['message_text'] } class NotificationsRecordList(FormMixin, ListView): template_name = 'frontend/pages/user/notifications.html' paginate_by = 10 form_class = NotificationRecordFilterForm def get_form_kwargs(self): kwargs = { 'initial': self.get_initial(), 'prefix': self.get_prefix(), 'data': self.request.GET or None, } return kwargs def get_queryset(self): queryset = NotificationRecord.objects.filter(user=self.request.user) form = self.get_form(self.get_form_class()) if form.is_valid(): queryset = queryset.filter(**form.filter_by()) return queryset @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) user_notifications = NotificationsRecordList.as_view() class NotificationsRecordDetail(DetailView): template_name = 'frontend/pages/user/notification_view.html' pk_url_kwarg = 'id' def get_queryset(self): queryset = NotificationRecord.objects.filter(user=self.request.user) return queryset @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) def get_object(self, queryset=None): obj = super().get_object(queryset) obj.set_read() return obj user_notification_view = NotificationsRecordDetail.as_view()
urls.py
from django.urls import path from . import views app_name='frontend' urlpatterns = [ ... path('user/notifications/', views.user_notifications, name="user_notifications"), path('user/notification/<uuid:id>/view/', views.user_notification_view, name="user_notification_view"), ... ]
templates/notifications.html
{% extends 'frontend/base.html' %} {% load i18n static %} {% block content %} <div class="row"> <div class="col-sm-9"> *** *** We use the form for GET *** <form action="{% url 'frontend:user_notifications' %}"> <section class="panel"> <header class="panel-heading wht-bg"> <div style="max-width: 320px; margin-top: 5px;" class="input-append input-group pull-right"> *** *** Render search input items from form *** <input name="{{ form.message_text.html_name }}" value="{{ form.message_text.value|default:'' }}" class="form-control" type="search" placeholder="{% trans 'message text' %}"> *** *** Assign page=1 to search form submit button *** <span class="input-group-btn"> <button class="btn btn-success" type="submit" name="page" value="1">{% trans 'Search' %}</button> </span> </div> <h4 class="gen-case">{% trans 'Your notifications' %} ({{page_obj.paginator.count}})</h4> </header> <div class="panel-body minimal"> <div class="mail-option"> *** *** Sample compact pagination with prev/next buttons *** <ul class="unstyled inbox-pagination"> <li><span>{{page_obj.start_index}}-{{page_obj.end_index}} {% trans 'of' %} {{page_obj.paginator.count}}</span></li> <li> {# <a class="np-btn" href="#"><i class="fa fa-angle-left pagination-left"></i></a> #} <button class="np-btn" {% if page_obj.has_previous %} name="page" value="{{ page_obj.previous_page_number }}" type="submit" {% else %} disabled="disabled" {% endif %}> <i class="fa fa-angle-left pagination-left"></i> </button> </li> <li> {# <a class="np-btn" href="#"><i class="fa fa-angle-right pagination-right"></i></a> #} <button class="np-btn" {% if page_obj.has_next %} name="page" value="{{ page_obj.next_page_number }}" type="submit" {% else %} disabled="disabled" {% endif %}> <i class="fa fa-angle-right pagination-right"></i> </button> </li> </ul> </div> *** *** Results listing *** <div class="table-inbox-wrap "> <table class="table table-inbox table-hover"> <tbody> {% for object in object_list %} <tr class="{% if object.read %}read{% else %}unread{% endif %}"> <td class="inbox-small-cells"><input type="checkbox" class="mail-checkbox"></td> <td class="inbox-small-cells"><i class="fa fa-star"></i></td> <td class="view-message"> <a href="{% url 'frontend:user_notification_view' object.id %}">{{ object.notification }}</a> </td> <td class="view-message">{{ object.notification.get_level_display }}</td> <td class="view-message">{{ object.notification.type }}</td> {# <td class="view-message inbox-small-cells"><i class="fa fa-paperclip"></i></td> #} <td class="view-message text-right">{{ object.notification.date_created }}</td> </tr> {% endfor %} </tbody> </table> </div> *** *** Sample full pagination *** <ul class="pagination inbox-pagination"> <li> <button class="np-btn" {% if page_obj.has_previous %} name="page" value="{{ page_obj.previous_page_number }}" type="submit" {% else %} disabled="disabled" {% endif %}> <i class="fa fa-angle-left pagination-left"></i> </button> </li> {% for page_number in paginator.page_range %} <li class="{% if page_number == page_obj.number %}active{% endif %}"> <button class="np-btn" name="page" value="{{ page_number }}" type="submit" {% if page_number == page_obj.number %} disabled="disabled" {% endif %}> {{ page_number }} </button> </li> {% endfor %} <li> <button class="np-btn" {% if page_obj.has_next %} name="page" value="{{ page_obj.next_page_number }}" type="submit" {% else %} disabled="disabled" {% endif %}> <i class="fa fa-angle-right pagination-right"></i> </button> </li> </ul> </div> </section> </form> </div> </div> {% endblock content %}