# Django Filters with Pagination

As I mentioned in [my last post](https://jeffpohlmeyer.com/my-mind-is-a-runaway-train) I tend to always have this desire to go back to Django, and I'm sort of acting on that now. I wrote a full API for the weight tracking app that I mentioned in that post in FastAPI only to realize that I didn't want to deal with the baggage of a front end framework for something that should work very well in an MVC setup. Thankfully Django and FastAPI are both Python so a lot of the functionality ports over well.

That said, I _did_ run into a weird little hiccup earlier today and I thought I'd share what I ended up doing. I have a view that lists out all foods that are either system foods or user-created foods, but as I have a fairly large database of foods to begin with, since I had kept this in a spreadsheet before and I scraped all of the restaurant foods from http://www.exercise4weightloss.com/weight-watchers-points.html, it amounted to a list of just shy of 50,000 foods so I clearly needed to add pagination.

# Pagination
This is easy to do in Django if you're using a [ListView](https://docs.djangoproject.com/en/4.0/ref/class-based-views/generic-display/#listview) in that you only need to add one line to your class-based view
```
# food/views.py

from django.db.models import QuerySet, Q
from django.views.generic import ListView

from .models import Food

class FoodListView(ListView):
    model = Food
    paginate_by = 20

    def get_queryset(self) -> QuerySet[Food]:
        self.queryset = Food.objects.filter(
            Q(user_created=False) | Q(user=self.request.user)
        )
        return super().get_queryset()
```
The `get_queryset` method is to set it to return all system foods and the foods created by the logged in user.

Then you simply need to update your template to include pagination as described in https://docs.djangoproject.com/en/4.0/topics/pagination/#paginating-a-listview
```
{# food_list.html #}

{% extends 'base.html' %}

{% block content %}
  <ul>
    {% for food in food_list %}
      <li>{{ food }}</li>
    {% endfor %}
  </ul>


  <div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
          <a href="?page=1">&laquo; first</a>
          <a href="?page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}

      <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
        </span>

      {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">next</a>
        <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
      {% endif %}
    </span>
  </div>
{% endblock content %}

```

# Filtering
If you want to include filtering, though, and you don't want to manually handle it, this can create a problem.
## Replacing Generic ListView
The first thing you would need to do is install [django-filter](https://django-filter.readthedocs.io/en/stable/index.html). Then after that you can simply create a filter and replace your `ListView` with a `FilterView`
```
# food/views.py

import django_filters
from django.db.models import QuerySet, Q
from django_filters.views import FilterView

from .models import Food

class FoodFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr="icontains")

    class Meta:
        model = Food
        fields = ["name"]

class FoodListView(FilterView):
    model = Food
    paginate_by = 20
    filterset_class = FoodFilter
    template_name_suffix = "_list"

    def get_queryset(self) -> QuerySet[Food]:
        self.queryset = Food.objects.filter(
            Q(user_created=False) | Q(user=self.request.user)
        )
        return super().get_queryset()
```
This, in theory, should return the same functionality as before, but now we can call `http://localhost:8000/foods/?name=chocolate` or anything like that and it will properly filter the queryset.

## The problem
The issue that we run into, though, has to do with going to the next page. Currently, the next and previous page links on the template don't have any way of knowing what the current query is. So, while the first link will go to `http://localhost:8000/foods/?name=chocolate` but if I click on the "next page" link it will go to `http://localhost:8000/foods/?page=2` and it will forget about the filter that I applied.

## The solution
The simplest way I found to do this, as of this moment, is two-fold.
### Update the context
Within the view itself we need to add a `get_context_data` method
```
class FoodListView(FilterView):
    model = Food
    paginate_by = 20
    filterset_class = FoodFilter
    template_name_suffix = "_list"

    def get_queryset(self) -> QuerySet[Food]:
        self.queryset = Food.objects.filter(
            Q(user_created=False) | Q(user=self.request.user)
        )
        return super().get_queryset()

    def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
        context = super().get_context_data(**kwargs)
        context["query"] = dict()
        for k, v in context["filter"].data.items():
            if k != "page":
                context["query"][k] = v

        return context
```
What this will do is it will check to see what the items are in the `QueryDict` that Django filter uses, and create a `query` object on the context that will include all existing query parameters but __not__ including any that have `page` as the key because we want to let the built-in pagination handle that.

### Update the template
Then we just update the template to include all of these query objects on the `next` and `prev` anchor tags:
```
{# food_list.html #}

{% extends 'base.html' %}

{% block content %}
  <ul>
    {% for food in food_list %}
      <li>{{ food }}</li>
    {% endfor %}
  </ul>


  <div class="pagination">
    <span class="step-links">
      {% if page_obj.has_previous %}
        <a href="?page=1{% for k, v in query.items %}&{{ k }}={{ v }}{% endfor %}">&laquo; first</a>
        <a href="?page={{ page_obj.previous_page_number }}{% for k, v in query.items %}&{{ k }}={{ v }}{% endfor %}">previous</a>
      {% endif %}

      <span class="current">
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
      </span>

      {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}{% for k, v in query.items %}&{{ k }}={{ v }}{% endfor %}">next</a>
        <a href="?page={{ page_obj.paginator.num_pages }}{% for k, v in query.items %}&{{ k }}={{ v }}{% endfor %}">last &raquo;</a>
      {% endif %}
    </span>
  </div>
{% endblock content %}
```
What this will now do is append an `&k=v` for each key/value pair that previously existed in the Django filter query. I'm sure I'm going to abstract this out into a snippet that will be more easily reusable down the road, but for now this was the easiest way to get it working quickly.
