Код-ревью Middle Django REST Framework спрашивают часто обновлено

Код-ревью DRF-вьюсета

Вопрос

Найдите проблемы в сериализаторе и вьюсете заказов.

from rest_framework import serializers, viewsets
from .models import Order


class OrderSerializer(serializers.ModelSerializer):
    customer_name = serializers.CharField(source='customer.name')
    items_count = serializers.SerializerMethodField()

    class Meta:
        model = Order
        fields = '__all__'

    def get_items_count(self, obj):
        return obj.items.count()


class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    pagination_class = None

    def perform_create(self, serializer):
        serializer.save(owner_id=self.request.data['owner_id'])

Коротко

Исправленный вариант: Пагинация берётся из настроек проекта (DEFAULTPAGINATIONCLASS).

Что должен найти ревьюер

  1. Безопасностьстрока 11
    fields = "__all__" отдаёт и принимает на запись все поля модели, включая служебные (статус, скидка); нужен явный список и read_only_fields
  2. Производительностьстрока 14
    obj.items.count() делает запрос на каждый заказ (N+1); использовать annotate(items_count=Count("items"))
  3. Безопасностьстрока 18
    Нет фильтра по пользователю и прав: любой видит и меняет все заказы (IDOR); get_queryset с фильтром и permission_classes
  4. Производительностьстрока 18
    Нет select_related("customer") для customer_name: N+1 на каждый заказ
  5. Производительностьстрока 20
    Пагинация отключена: список отдаст все заказы разом
  6. Безопасностьстрока 23
    Владелец берётся из тела запроса: клиент создаст заказ от чужого имени; использовать owner=self.request.user

Разбор

Исправленный вариант:

class OrderSerializer(serializers.ModelSerializer):
    customer_name = serializers.CharField(source='customer.name', read_only=True)
    items_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Order
        fields = ['id', 'status', 'total', 'customer_name', 'items_count', 'created_at']
        read_only_fields = ['status', 'created_at']


class OrderViewSet(viewsets.ModelViewSet):
    serializer_class = OrderSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        return (Order.objects.filter(owner=self.request.user)
                .select_related('customer')
                .annotate(items_count=Count('items')))

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

Пагинация берётся из настроек проекта (DEFAULT_PAGINATION_CLASS).

Ответьте сами, а не прочитайте

В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.