Большинство Django-проектов в России отдают API через Django REST Framework (DRF). На собесе спрашивают, как устроены сериализаторы, чем APIView отличается от ViewSet, как работают права и где в DRF прячется N+1.
Сериализатор
Сериализатор делает две вещи: превращает объекты в JSON-совместимые данные и валидирует входящие данные.
class OrderSerializer(serializers.ModelSerializer):
customer_name = serializers.CharField(source='customer.name', read_only=True)
class Meta:
model = Order
fields = ['id', 'status', 'total', 'customer_name', 'created_at']
read_only_fields = ['status', 'created_at']
def validate_total(self, value):
if value <= 0:
raise serializers.ValidationError('Сумма должна быть больше нуля')
return value
Валидация идёт по слоям: поля (типы, required, max_length), методы validate_<field>, общий validate(self, attrs) для проверок между полями, валидаторы модели вроде уникальности. После is_valid() в serializer.validated_data лежат чистые данные, а serializer.save() вызывает create или update.
Важно явно перечислять fields. fields = '__all__' завтра отдаст наружу новое поле password_hash или internal_note.
Вьюхи
APIView: методыget,postи т.д., всё вручную.- Generic-вьюхи (
ListCreateAPIView,RetrieveUpdateDestroyAPIView): типовые операции сquerysetиserializer_class. ViewSetиModelViewSet: набор действийlist,retrieve,create,update,partial_update,destroyв одном классе, URL создаёт роутер.
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return (Order.objects.filter(customer__user=self.request.user)
.select_related('customer'))
@action(detail=True, methods=['post'])
def cancel(self, request, pk=None):
order = self.get_object()
...
get_queryset с фильтром по пользователю это и безопасность, и производительность: чужие объекты не видны даже по прямому id, а select_related убирает N+1.
Аутентификация и права
- Authentication classes определяют, кто пришёл: сессия, токен, JWT (через simplejwt).
- Permission classes решают, можно ли:
IsAuthenticated,IsAdminUser,DjangoModelPermissions, свои. has_permission(request, view)проверяется для всего запроса,has_object_permission(request, view, obj)для конкретного объекта, но только когда вызванget_object(). Для списков объектная проверка не вызывается, там фильтруютget_queryset.
Пагинация, фильтры, троттлинг
PageNumberPagination,LimitOffsetPaginationиCursorPagination. Курсорная стабильна при вставках и не тормозит на глубоких страницах, но не умеет прыгать на страницу N.- Фильтры:
django-filter,SearchFilter,OrderingFilter. Разрешённые поля сортировки задают явно. - Троттлинг:
AnonRateThrottle,UserRateThrottle,ScopedRateThrottle; счётчики хранятся в кеше, поэтому нужен общий кеш (Redis) на все процессы.
Где в DRF N+1
Вложенные сериализаторы и поля с source='customer.name' обращаются к связям для каждого объекта списка. Решение в get_queryset: select_related и prefetch_related под те поля, что отдаёт сериализатор. SerializerMethodField, который делает запрос внутри, тоже источник N+1.
Частые ошибки
fields = '__all__'и утечка служебных полей.- Проверка прав только в
has_object_permissionпри незащищённом списке. queryset = Order.objects.all()без фильтра по пользователю: IDOR.- Вложенные сериализаторы без
prefetch_related. - Запись во вложенные сериализаторы без переопределения
createиupdate(DRF по умолчанию это не поддерживает).
Что спросят на собесе
- Что делает сериализатор, как устроена валидация.
- Чем
APIView, generic-вьюхи иViewSetотличаются. - Как работают permission classes, в чём разница
has_permissionиhas_object_permission. - Какие бывают пагинации, чем хороша курсорная.
- Как найти и убрать N+1 в DRF.