Django: использование плагина django-filter с CBV

Django-filter — чрезвычайно мощный плагин для Django, который расширяет структуру, предоставляя общий набор API-интерфейсов для простой настройки и управления пользовательскими фильтрами в моделях. Этот плагин предоставляет огромное количество новых функций, при этом избегая дополнительного шаблонного кода для разработчиков.

Этот плагин настолько невероятно полезен, что очень популярный плагин Django Rest Framework построен поверх плагина django-filter для помощи в построении его представлений REST API.

К сожалению, документация django-filters, хотя и очень краткая и хорошо написанная, не дает примера того, как ее интегрировать с представлениями на основе классов. В этой статье я исправлю это, приведя один такой пример.

Наша конечная цель в этой статье — использовать плагин django-filter для очень быстрого написания приложения Django, которое позволяет пользователям просматривать доступные книги в библиотеке и фильтровать их по автору, жанру, рейтингу и другим полям. Конечно, все это будет выполняться только с использованием представлений на основе классов.

Образцы моделей

Ниже приведены модели Django, которые мы будем использовать в нашем примере.

class Author(models.Model):
    first_name = models.CharField(max_length=64)
    last_name = models.CharField(max_length=64)

class Category(models.Model):
    name = models.CharField(max_length=64)

class Book(models.Model):
    RATINGS = [
        (1, "Trash"),
        (2, "C-tier"),
        (3, "B-tier"),
        (4, "A-tier"),
        (5, "S-tier"),
    ]
    title = models.CharField(max_length=128)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    librarian_rating = models.IntegerField(choices=RATINGS, help_text="Our local librarian's rating of this book.")

Эти модели предназначены для представления коллекции книг, найденных в библиотеке.

Далее давайте создадим класс django-filter для этой модели, адаптируя примеры, уже представленные в отличной документации по django-filter.

from django_filters.filterset import FilterSet
from .models import Book


class BookFilter(FilterSet):
    class Meta:
        model = Book
        fields = ['author', 'category', 'librarian_rating']

Как и в случае с любыми классами FilterSet django-filter, это полностью настраивается по вашему желанию. Обратитесь к документации django-filter, чтобы узнать обо всех доступных параметрах настройки. А пока мы перейдем к подключению этого к нашему CBV.

Подключение фильтра к CBV

Наше представление без фильтров для наших классов Book и Author показано ниже. Этот CBV предоставляет нашим конечным пользователям простой способ просматривать все книги одновременно, без какой-либо фильтрации:

from django.views.generic import ListView
from .models import Book

class BookListView(ListView):
    template_name = 'books.html'
    model = Book
    context_object_name = 'books'

Теперь, если мы хотим предоставить нашим пользователям возможность фильтровать эти книги, нам нужно использовать класс фильтра, который мы определили выше, и подключить его к этому CBV.

Для этого мы воспользуемся миксином FilterView, который поставляется вместе с плагином django_filters. Это включает в себя два простых шага:

  1. Переключение наследования нашего класса с ListView на FilterView в определении класса.
  2. Установка поля filterset_class в классе и указание его на класс BookFilter, который мы создали ранее.
from django_filters.views import FilterView
from .filters import BookFilter
from .models import Book

class BookListView(FilterView):
    template_name = 'books.html'
    model = Book
    context_object_name = 'books'
    filterset_class = BookFilter

Этот обновленный FilterView поставляется с новой переменной filter, которая отправляет весь объект FilterSet в шаблон. Внутри этого объекта находится FilterSet.form, который является стандартным объектом формы Django, предварительно заполненным всеми полями, необходимыми пользователю для управления настройками фильтра. Мы можем взаимодействовать с ним на стороне шаблона, как и с любой обычной формой:

{% extends 'base.html' %}
{% block title %}Books{% endblock %}

{% block content %}
<h1>Books</h1>
<div class="row my-5 mx-3">
    <div class="col">
        <div class="card">
            <form method="get" action="">
            <h5 class="card-header">Filters</h5>
            <div class="card-body">
                {{ filter.form.as_p }}
            </div>
            <div class="card-footer text-center">
                <input class="btn btn-secondary" type="submit" value="Filter" />
            </div>
        </form>
        </div>
    </div>
</div>

<div class="row">
    <div class="col">
        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th scope="col">Title</th>
                    <th scope="col">Author</th>
                    <th scope="col">Category</th>
                    <th scope="col">Rating</th>
                </tr>
            </thead>
            <tbody>
                {% for book in books %}
                <tr>
                    <td>{{ book.title }}</td>
                    <td>{{ book.author }}</td>
                    <td>{{ book.category }}</td>
                    <td>{{ book.get_librarian_rating_display }}</td>
                </tr>
                {% empty %}
                <tr><td colspan="6"><i>No books in this filter.</td></tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

И вуаля! Это так просто! Благодаря мощи Django и django-filter у нас теперь есть полнофункциональная форма фильтра для пользователя, и мы сделали это, написав очень мало дополнительного кода.

Ресурсы