📖 Теория — Урок 33
⚡ Ключевые концепции урока
- lookup_field — какое поле модели использовать для поиска (по умолчанию
pk) - lookup_url_kwarg — какой параметр URL соответствует
lookup_field - get_object() — переопределить для кастомной фильтрации и обработки ошибок
- get_serializer_context() — передать параметры запроса в сериализатор через контекст
- filter_backends — список бэкендов: DjangoFilterBackend, SearchFilter, OrderingFilter
- filterset_fields — поля для точной фильтрации (
?author=...) - search_fields — поля для текстового поиска (
?search=...) - ordering_fields — поля для сортировки (
?ordering=price)
Часть 1: Атрибуты lookup_field и lookup_url_kwarg
Атрибуты lookup_field и lookup_url_kwarg позволяют определять, какое поле модели и параметр URL будут использоваться для поиска объекта при обработке запросов. Эти атрибуты полезны, когда нужно идентифицировать объекты по полям, отличным от стандартного первичного ключа (pk).
lookup_field не задан, поиск выполняется по полю pk. Если lookup_url_kwarg не задан, используется значение lookup_field.
Назначение атрибутов
| Атрибут | Назначение |
|---|---|
lookup_field |
Указывает поле модели, по которому будет выполняться поиск объекта |
lookup_url_kwarg |
Указывает параметр URL, из которого берётся значение для поиска. Если не задан — совпадает с lookup_field |
Пример: поиск жанра по названию
Стандартный подход — поиск по pk: GET /genres/3/. С lookup_field = 'name' можно обращаться к жанру по имени: GET /genres/comedy/.
# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Genre
from .serializers import GenreSerializer
class GenreDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
lookup_field = 'name' # поле модели для поиска
lookup_url_kwarg = 'genre_name' # параметр URL (из path-конвертора)
# urls.py
from django.urls import path
from .views import GenreDetailUpdateDeleteView
urlpatterns = [
path('genres/<str:genre_name>/', GenreDetailUpdateDeleteView.as_view(),
name='genre-detail-update-delete'),
]
Запрос и ответ:
GET http://127.0.0.1:8000/genres/comedy/
{
"id": 3,
"name": "Comedy"
}
Применение
- Гибкость идентификации: идентифицировать объекты по slug, email, username или любому уникальному полю
- Читабельные URL:
/users/john_doe/вместо/users/42/
Часть 2: Метод get_object()
Метод get_object() — важный компонент Generic Views, который используется для получения одного экземпляра модели на основе определённого критерия поиска. Переопределяя этот метод, можно добавить кастомную логику фильтрации, обработки ошибок и дополнительных проверок.
Основное назначение
- Поиск объекта:
get_object()извлекает объект модели на основе значения из URL - Фильтрация объектов: можно исключать запрещённые или мягко удалённые элементы
- Обработка ошибок: кастомная логика при отсутствии объекта
Пример: фильтрация запрещённых книг
Добавим в модель Book поле is_banned, чтобы скрывать запрещённые книги через API:
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_date = models.DateField()
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE,
null=True, blank=True)
created_at = models.DateTimeField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2,
null=True, blank=True)
discounted_price = models.DecimalField(max_digits=10, decimal_places=2,
null=True, blank=True)
is_bestseller = models.BooleanField(default=False)
genres = models.ManyToManyField(Genre, related_name='books')
is_banned = models.BooleanField(default=False) # запрещённая книга
# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from rest_framework.exceptions import NotFound
from .models import Book
from .serializers import BookSerializer
class BookDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_object(self):
# Получение параметра pk из URL
pk = self.kwargs.get('pk')
# Попытка найти объект по pk, исключая запрещённые
try:
book = self.queryset.get(pk=pk, is_banned=False)
except Book.DoesNotExist:
# Кастомное сообщение об ошибке
raise NotFound(detail=f"Book with id '{pk}' not found or is banned.")
return book
get_object() автоматически вызывает self.check_object_permissions(). При переопределении не забудьте вызвать его явно: self.check_object_permissions(self.request, obj), если используете object-level permissions.
Резюме
Использование get_object() с полем is_banned позволяет эффективно фильтровать недоступные объекты. Это улучшает безопасность и контроль над доступностью данных.
Часть 3: Метод get_serializer_context()
Метод get_serializer_context() используется для передачи дополнительного контекста сериализатору. Контекст позволяет сериализатору иметь доступ к информации, которая недоступна из полей модели напрямую — например, параметрам запроса, текущему пользователю или флагам конфигурации.
Основное назначение
- Передача дополнительной информации: текущий пользователь, параметры запроса, флаги
- Расширение возможностей сериализатора: динамические поля, условная валидация
Что уже есть в стандартном контексте
Базовый super().get_serializer_context() возвращает словарь с тремя ключами: request, format, view. Расширяя его, мы добавляем свои ключи.
# views.py
from rest_framework.generics import ListAPIView
from .models import Book
from .serializers import BookSerializer
class BookListCreateView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_serializer_context(self):
context = super().get_serializer_context() # не терять request/format/view
# Передаём флаг из параметра запроса
context['include_related'] = (
self.request.query_params.get('include_related', 'false').lower() == 'true'
)
return context
# serializers.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
def to_representation(self, instance):
representation = super().to_representation(instance)
# Используем значение из контекста
if self.context.get('include_related'):
representation['genres'] = [genre.name for genre in instance.genres.all()]
else:
representation.pop('genres', None)
return representation
Пример запроса и ответа
GET http://127.0.0.1:8000/books/?include_related=true
{
"id": 1,
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"published_date": "1925-04-10",
"price": "15.99",
"discounted_price": "12.99",
"is_bestseller": false,
"genres": ["Classic", "Fiction"],
"publisher": 1,
"created_at": "2024-06-12T12:34:56.789Z",
"is_banned": false
}
При запросе без параметра (?include_related=false или без него) поле genres будет убрано из ответа.
Резюме
Метод get_serializer_context() позволяет адаптировать представление данных в зависимости от условий запроса или других факторов. Это делает API более динамичным и предоставляет клиентам возможность получать детализированные или упрощённые ответы.
Часть 4: Атрибут filter_backends
В DRF filter_backends — механизм, позволяющий автоматически фильтровать данные на основе параметров запроса. Это избавляет от необходимости вручную писать логику фильтрации в каждом представлении.
Основное назначение
- Фильтрация данных: автоматическая фильтрация на основе параметров запроса
- Удобство: встроенные инструменты без лишнего кода в каждом представлении
- Расширяемость: поддержка различных бэкендов фильтрации
Три вида фильтрации
| Бэкенд | Атрибут | Параметр запроса | Пример |
|---|---|---|---|
DjangoFilterBackend |
filterset_fields |
?field=value |
?author=Fitzgerald |
filters.SearchFilter |
search_fields |
?search=text |
?search=Gatsby |
filters.OrderingFilter |
ordering_fields |
?ordering=field или ?ordering=-field |
?ordering=-price |
Установка и настройка
Для фильтрации по полям нужен пакет django-filter:
pip install django-filter
# settings.py
INSTALLED_APPS = [
# ... другие приложения
'django_filters',
]
Представление с filter_backends
# views.py
from rest_framework.generics import ListAPIView
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Book
from .serializers import BookSerializer
class BookListCreateView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter,
filters.OrderingFilter]
filterset_fields = ['author', 'publisher', 'is_bestseller'] # точная фильтрация
search_fields = ['title', 'author'] # текстовый поиск
ordering_fields = ['published_date', 'price'] # сортировка
Примеры запросов
# Фильтрация по автору (точное совпадение)
GET http://127.0.0.1:8000/books/?author=F. Scott Fitzgerald
# Поиск по заголовку или автору (содержит подстроку)
GET http://127.0.0.1:8000/books/?search=Gatsby
# Сортировка по цене (по возрастанию)
GET http://127.0.0.1:8000/books/?ordering=price
# Сортировка по цене (по убыванию, минус перед полем)
GET http://127.0.0.1:8000/books/?ordering=-price
# Комбинирование: фильтрация + сортировка
GET http://127.0.0.1:8000/books/?is_bestseller=true&ordering=-published_date
Резюме
filter_backends предоставляет мощный механизм для автоматической фильтрации, поиска и сортировки данных. Это делает API гибким и удобным: клиенты управляют результатами через простые параметры запроса, а не через кастомный код в каждом представлении.