Фильтры Django с django-filter

Django-filter - это мощная библиотека, которая упрощает создание сложных фильтров для списков объектов. Она предоставляет гибкий API для создания фильтров любой сложности.

Установка и настройка

Сначала установи библиотеку:

1pip install django-filter

Добавь в INSTALLED_APPS:

1INSTALLED_APPS = [
2    # ... другие приложения
3    'django_filters',
4]

Базовые фильтры

Создай простой фильтр для модели Book:

 1import django_filters
 2from .models import Book
 3
 4class BookFilter(django_filters.FilterSet):
 5    title = django_filters.CharFilter(lookup_expr='icontains')
 6    published_date = django_filters.DateFromToRangeFilter()
 7    author = django_filters.ModelChoiceFilter(queryset=Author.objects.all())
 8    price = django_filters.RangeFilter()
 9    is_available = django_filters.BooleanFilter()
10
11    class Meta:
12        model = Book
13        fields = ['author', 'published', 'genre']

Типы фильтров

CharFilter - для текстовых полей:

1title = django_filters.CharFilter(
2    lookup_expr='icontains',
3    label='Название книги',
4    help_text='Введите часть названия для поиска'
5)

NumberFilter - для числовых полей:

 1price_min = django_filters.NumberFilter(
 2    field_name='price',
 3    lookup_expr='gte',
 4    label='Цена от'
 5)
 6price_max = django_filters.NumberFilter(
 7    field_name='price',
 8    lookup_expr='lte',
 9    label='Цена до'
10)

DateFromToRangeFilter - для диапазонов дат:

1published_date = django_filters.DateFromToRangeFilter(
2    label='Дата публикации',
3    widget=django_filters.widgets.RangeWidget(
4        attrs={'type': 'date'}
5    )
6)

ChoiceFilter - для выбора из списка:

 1GENRE_CHOICES = [
 2    ('fiction', 'Художественная литература'),
 3    ('non-fiction', 'Документальная литература'),
 4    ('sci-fi', 'Научная фантастика'),
 5]
 6
 7genre = django_filters.ChoiceFilter(
 8    choices=GENRE_CHOICES,
 9    label='Жанр'
10)

Множественный выбор

Для множественного выбора используй MultipleChoiceFilter:

1tags = django_filters.MultipleChoiceFilter(
2    choices=TAG_CHOICES,
3    widget=forms.CheckboxSelectMultiple,
4    label='Теги'
5)

Кастомные фильтры

Создай собственный фильтр для сложной логики:

 1class PriceRangeFilter(django_filters.Filter):
 2    def filter(self, qs, value):
 3        if not value:
 4            return qs
 5
 6        try:
 7            min_price, max_price = map(float, value.split('-'))
 8            return qs.filter(price__gte=min_price, price__lte=max_price)
 9        except (ValueError, AttributeError):
10            return qs
11
12class BookFilter(django_filters.FilterSet):
13    price_range = PriceRangeFilter(
14        label='Диапазон цен (например: 100-500)'
15    )
16
17    class Meta:
18        model = Book
19        fields = ['title', 'author']

Использование в views

В Class-Based View:

 1from django.views.generic import ListView
 2from django_filters.views import FilterView
 3
 4class BookListView(FilterView):
 5    model = Book
 6    template_name = 'books/book_list.html'
 7    filterset_class = BookFilter
 8    paginate_by = 20
 9
10    def get_context_data(self, **kwargs):
11        context = super().get_context_data(**kwargs)
12        context['total_count'] = Book.objects.count()
13        context['filtered_count'] = context['filter'].qs.count()
14        return context

В Function-Based View:

 1def book_list(request):
 2    books = Book.objects.all()
 3    book_filter = BookFilter(request.GET, queryset=books)
 4
 5    # Применяем фильтр
 6    filtered_books = book_filter.qs
 7
 8    # Пагинация
 9    paginator = Paginator(filtered_books, 20)
10    page_number = request.GET.get('page')
11    page_obj = paginator.get_page(page_number)
12
13    context = {
14        'filter': book_filter,
15        'page_obj': page_obj,
16        'total_count': books.count(),
17        'filtered_count': filtered_books.count(),
18    }
19    return render(request, 'books/book_list.html', context)

Шаблон для фильтров

Создай форму фильтров в шаблоне:

 1{% load django_filters %}
 2
 3<form method="get" class="filter-form">
 4    <div class="row">
 5        <div class="col-md-3">
 6            {{ filter.form.title.label_tag }}
 7            {{ filter.form.title }}
 8        </div>
 9        <div class="col-md-3">
10            {{ filter.form.author.label_tag }}
11            {{ filter.form.author }}
12        </div>
13        <div class="col-md-3">
14            {{ filter.form.genre.label_tag }}
15            {{ filter.form.genre }}
16        </div>
17        <div class="col-md-3">
18            {{ filter.form.price_min.label_tag }}
19            {{ filter.form.price_min }}
20        </div>
21    </div>
22
23    <div class="row mt-3">
24        <div class="col-md-3">
25            {{ filter.form.published_date.label_tag }}
26            {{ filter.form.published_date }}
27        </div>
28        <div class="col-md-3">
29            {{ filter.form.is_available.label_tag }}
30            {{ filter.form.is_available }}
31        </div>
32        <div class="col-md-6">
33            <button type="submit" class="btn btn-primary">Применить фильтры</button>
34            <a href="{% url 'book_list' %}" class="btn btn-secondary">Сбросить</a>
35        </div>
36    </div>
37</form>
38
39<div class="results-info mt-3">
40    <p>Найдено: {{ filtered_count }} из {{ total_count }} книг</p>
41</div>
42
43<div class="books-list">
44    {% for book in page_obj %}
45        <div class="book-item">
46            <h4>{{ book.title }}</h4>
47            <p>Автор: {{ book.author }}</p>
48            <p>Цена: {{ book.price }} ₽</p>
49        </div>
50    {% empty %}
51        <p>По вашему запросу ничего не найдено</p>
52    {% endfor %}
53</div>
54
55{% include 'pagination.html' with page_obj=page_obj %}

Продвинутые техники

Фильтрация по связанным моделям:

 1class BookFilter(django_filters.FilterSet):
 2    author_name = django_filters.CharFilter(
 3        field_name='author__name',
 4        lookup_expr='icontains',
 5        label='Имя автора'
 6    )
 7    publisher_city = django_filters.CharFilter(
 8        field_name='publisher__city',
 9        lookup_expr='icontains',
10        label='Город издательства'
11    )
12
13    class Meta:
14        model = Book
15        fields = ['title', 'genre']

Фильтрация по аннотациям:

 1from django.db.models import Count, Avg
 2
 3class BookFilter(django_filters.FilterSet):
 4    min_rating = django_filters.NumberFilter(
 5        method='filter_min_rating',
 6        label='Минимальный рейтинг'
 7    )
 8
 9    def filter_min_rating(self, queryset, name, value):
10        if value:
11            return queryset.annotate(
12                avg_rating=Avg('reviews__rating')
13            ).filter(avg_rating__gte=value)
14        return queryset

Настройка URL параметров

Создай чистые URL для фильтров:

1# urls.py
2from django.urls import path
3from .views import BookListView
4
5urlpatterns = [
6    path('books/', BookListView.as_view(), name='book_list'),
7]

Кэширование фильтров

Для оптимизации производительности используй кэширование:

 1from django.core.cache import cache
 2
 3class BookFilter(django_filters.FilterSet):
 4    def get_queryset(self):
 5        cache_key = f"book_filter_{hash(frozenset(self.data.items()))}"
 6        queryset = cache.get(cache_key)
 7
 8        if queryset is None:
 9            queryset = super().get_queryset()
10            cache.set(cache_key, queryset, 300)  # 5 минут
11
12        return queryset

FAQ

Q: Как создать кастомные фильтры?
A: Создай класс, наследующий от django_filters.Filter, и переопредели filter метод.

Q: Как фильтровать по диапазону дат?
A: Используй DateFromToRangeFilter с виджетом RangeWidget.

Q: Можно ли фильтровать по связанным моделям?
A: Да, используй двойное подчеркивание: author__name__icontains.

Q: Как добавить пагинацию к фильтрам?
A: Используй Django Paginator или встроенную пагинацию в Class-Based Views.

Q: Как оптимизировать производительность фильтров?
A: Используй select_related, prefetch_related и кэширование.

Лучшие практики

  • Всегда добавляй labels и help_text для полей фильтров
  • Используй подходящие lookup_expr для каждого типа поля
  • Группируй связанные фильтры в отдельные секции
  • Добавляй кнопку сброса фильтров
  • Показывай количество найденных результатов
  • Используй пагинацию для больших списков
  • Кэшируй результаты фильтрации при необходимости