'Django, django-filter and pagination

my goal is to have a 'user_profile' page that displays relevant information of the user of interest.

Furthermore, the 'user_profile' page should include all the posts that were created by the respective user as new blog entries.

These posts, however, should be filterable with the application 'django-filter' and be paginated. At the moment I have difficulties to paginate the filtered posts. So my question is how to achieve the latter?

So far, I used following approach:

filters.py

import django_filters

class AccountPostFilter(django_filters.FilterSet):
title = django_filters.CharFilter(lookup_expr='icontains')
category = django_filters.ChoiceFilter(choices=cat_list)

class Meta:
    model = Post
    fields = ['title', 'category']

views.py

class UserProfile(DetailView, MultipleObjectMixin):
model = Account
template_name = 'account/user_profile.html'
paginate_by = 5


def get_context_data(self, **kwargs):
    posts = Post.objects.all().filter(author=self.kwargs['pk'])
    context = super().get_context_data(object_list=posts, **kwargs)
    context['filterset'] = AccountPostFilter(self.request.GET, queryset=posts)

    return context

Thank you very much for your time. Best wishes, Daniel



Solution 1:[1]

There is another way of doing this, and do it in a clean and professional way, which will save you the trouble of using Django Filters:

Create a helper function called clean_filters (This will help you clean filters that come in from the brownser:

def clean_filters(filters):
    filters = {k: v for k, v in filters.items() if v}
    return filters

Create another help function called search (this will help you get the parameters from the GET request and put them in a **filters inside the django filter directive. And return them back with the paginator so you can keep the same filters when moving from page to page):

from 'your_utils_file' import clean_filters
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def search(request):
    filters = {
        "account__first_name__icontains": request.GET.get("fname_kw"), # put your filters here
        "account__last_name__icontains": request.GET.get("lname_kw"), # put your filters here
    }

    html_queries = {
        "fname_kw": request.GET.get("fname_kw"),
        "lname_kw": request.GET.get("lname_kw"),
    }

    filters = clean_filters(filters)
    html_queries = clean_filters(html_queries)

    posts = Post.objects.filter(**filters) # put your model here

    page = request.GET.get('page', 1)
    paginator = Paginator(posts, 8)
    try:
        posts= paginator.page(page)
    except PageNotAnInteger:
        posts= paginator.page(1)
    except EmptyPage:
        posts= paginator.page(paginator.num_pages)

    return posts

Here is your view (this simply calls the search function to reduce the code of your view and make it easy for your code to maintain):

def search_page(request):
    posts = search(request)

    if posts is not None:
        context = {
            'posts': posts,
        }
        return render(request, "core/index.html", context)
    return redirect("index")

Here is your HTML (just a classic pagination code for Django and Bootstrap. This also has the filter and the value of the filter in a loop inside the GET request):

<div class="mb-5">
{% if posts.has_other_pages %}
    <nav aria-label="Page navigation example">
        <ul class="pagination justify-content-center">
            {% if posts.has_previous %}
            <li class="page-item">
                <a class="page-link" href="?page={{ posts.previous_page_number }}{% for fil, fil_value in filters.items %}&{{fil}}={{fil_value}}{% endfor %}" tabindex="-1">
                    <i class="fa fa-angle-left"></i>
                    <span class="sr-only">Prev</span>
                </a>
            </li>
            {% else %}
            <li class="page-item disabled">
                <a class="page-link" href="javascript:void(0)" tabindex="-1">
                    <i class="fa fa-angle-left"></i>
                    <span class="sr-only">Prev</span>
                </a>
            </li>
            {% endif %}
            {% for i in posts.paginator.page_range %}
            {% if posts.number == i %}
                <li class="page-item active"><a class="page-link" href="javascript:void(0)">{{ i }}</a></li>
            {% else %}
                <li class="page-item"><a class="page-link" href="?page={{ i }}{% for fil, fil_value in filters.items %}&{{fil}}={{fil_value}}{% endfor %}">{{ i }}</a></li>
            {% endif %}
            {% endfor %}
            {% if posts.has_next %}
            <li class="page-item">
                <a class="page-link" href="?page={{ posts.next_page_number }}{% for fil, fil_value in filters.items %}&{{fil}}={{fil_value}}{% endfor %}">
                    <i class="fa fa-angle-right"></i>
                    <span class="sr-only">Next</span>
                </a>
            </li>
            {% else %}
            <li class="page-item disabled">
                <a class="page-link" href="javascript:void(0)">
                    <i class="fa fa-angle-right"></i>
                    <span class="sr-only">Next</span>
                </a>
            </li>
            {% endif %}  
        </ul>
    </nav>
{% endif %}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DharmanBot