сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · FastAPI, Django и API

Django и DRF

Django/DRF: MTV, сериализаторы и ленивый ORM

Django всё ещё держит огромную долю Python-вакансий, и собес по нему крутится вокруг трёх узлов: как устроен MTV, что делает сериализатор DRF и почему QuerySet ленив. Последнее - источник самого частого продового бага, N+1, и его любят давать кодом.

Типовые формулировки: «чем select_related отличается от prefetch_related?», «когда QuerySet реально идёт в БД?», «ModelViewSet или APIView».

MTV и двусторонний сериализатор

Django это MTV (model-template-view): Model (данные, ORM), Template (представление), View (обработка запроса и логика). Роль «контроллера» из MVC (model-view-controller) по сути играет сам фреймворк - маршрутизация и диспетчеризация встроены.

DRF (Django REST Framework) Serializer - двусторонний мост: на входе валидирует и превращает JSON в объекты Python, на выходе сериализует модель в JSON. ModelViewSet собирает CRUD из модели плюс сериализатора (роутер строит URL), а APIView - ручной уровень с полным контролем, когда логика нестандартная.

// MIDDLEWARE - луковица: запрос идёт сверху вниз, ответ снизу вверх; Security и Authentication ставят раньше слоёв, что от них зависят.

MTV
Model-Template-View - Django-вариант MVC
Serializer
DRF: валидация и конвертация модель ↔ JSON

Ленивый QuerySet и N+1

QuerySet ленив: filter лишь строит запрос, а SQL уходит в БД только при итерации, len, list или срезе. Отсюда две вещи: удобный чейнинг фильтров, и случайный N+1, когда в цикле по объектам ты на каждой итерации дёргаешь связь, и это отдельный запрос на каждый объект.

Лечение зависит от типа связи. select_related делает JOIN для ForeignKey/OneToOne («многие к одному»); prefetch_related шлёт отдельный запрос и джойнит в Python для обратной FK (foreign key) и ManyToMany. Перепутал - N+1 останется недобитым: select_related на ManyToMany не сработает.

// get() на пустом результате бросает DoesNotExist, а не возвращает None; «объект или None» это filter().first().

for book in Book.objects.all():      # 1 запрос
    print(book.author.name)          # +N запросов -> N+1
Book.objects.select_related('author')  # 1 JOIN -> починено
QuerySet
ленивый набор строк; SQL выполняется при обращении к данным
select/prefetch_related
жадная загрузка связей против N+1

Как отвечать: «Чем select_related отличается от prefetch_related?»

Оба бьют N+1, но по-разному и для разных связей. select_related делает SQL JOIN и подтягивает связанное одним запросом - годится для ForeignKey и OneToOne, то есть отношения «многие к одному», где на объект приходится ровно одна связанная строка. prefetch_related шлёт отдельный второй запрос и склеивает результаты уже в Python это для обратных ForeignKey и ManyToMany, где на объект много связанных. Если перепутать и повесить select_related на ManyToMany, оно просто не сработает, и N+1 останется. Поэтому я смотрю на кардинальность связи и выбираю по ней.

Разведены и механика (JOIN против второго запроса), и область применения по кардинальности связи, назван конкретный провал при путанице - видно, что человек чинил N+1 руками, а не читал доку.

На чём валят

  • QuerySet в цикле по объектам: обращение к связи на каждой итерации даёт N+1 запросов.
  • select_related на ManyToMany не поможет - для «многих» нужен prefetch_related.
  • get() на пусто бросает DoesNotExist, а не возвращает None; «объект или None» это filter().first().
  • Забыть, что QuerySet ленив: тяжёлый запрос сработает не там, где написан filter, а где к нему обратятся.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 14, остальные разбираются в тренажёре.

  1. #django_drf1 / 5
    Что означает MTV в Django?
    A)Model — Template — View: данные, представление, логика
    B)Method — Template — View: методы контроллера приложения
    C)Model — Test — View: выделенный слой тестов в архитектуре
    D)Model — Transport — View: транспортный слой между ними
    показать ответ и разбор
    +A)Model — Template — View: данные, представление, логика

    // разбор: Django зовёт свою архитектуру MTV: Model — данные и ORM, Template — рендер представления, View — обработка запроса и логика. Это тот же MVC, но «контроллером» по сути выступает сам фреймворк (маршрутизация), а View играет роль обработчика-контроллера.

  2. #django_drf2 / 5
    Какова роль сериализатора в DRF?
    A)Кэшировать ответы вьюх между повторяющимися запросами
    B)Валидировать вход и конвертировать модель в JSON и обратно
    C)Хранить схему миграций приложения между релизами
    D)Маршрутизировать запросы к нужному обработчику по URL
    показать ответ и разбор
    +B)Валидировать вход и конвертировать модель в JSON и обратно

    // разбор: Serializer в DRF — двусторонний мост: на входе валидирует и превращает JSON в объекты Python и инстансы модели, на выходе сериализует модель в JSON. ModelSerializer выводит поля из модели. По сути аналог Pydantic-модели в экосистеме Django.

  3. #django_drf3 / 5
    qs = User.objects.filter(active=True). Когда Django ORM пошлёт SQL?
    A)При первой итерации или материализации QuerySet
    B)Только при вызове .save() на полученном результате
    C)При импорте модели, один раз на запуск процесса
    D)Сразу в строке с filter — запрос выполнится немедленно
    показать ответ и разбор
    +A)При первой итерации или материализации QuerySet

    // разбор: QuerySet ленив: filter лишь строит запрос, SQL уходит при итерации, len(), list(), срезе или обращении к элементу. Это позволяет чейнить фильтры без лишних запросов. Обратная сторона — случайно дёрнуть QuerySet в цикле и получить N запросов.

  4. #django_drf4 / 5
    Чем ModelViewSet отличается от APIView в DRF?
    A)APIView сам собирает CRUD, а ViewSet требует расписать методы вручную
    B)APIView нужен исключительно для GraphQL-эндпоинтов
    C)ViewSet работает только на чтение, без операций записи
    D)ViewSet даёт готовый CRUD из модели и сериализатора
    показать ответ и разбор
    +D)ViewSet даёт готовый CRUD из модели и сериализатора

    // разбор: ModelViewSet собирает стандартный CRUD (list/create/retrieve/update/destroy) из модели и сериализатора, а роутер строит URL. APIView — ручной уровень: сам описываешь get/post. ViewSet экономит бойлерплейт на типовом REST, APIView даёт контроль на нестандартном.

  5. #django_drf5 / 5
    Важен ли порядок в списке MIDDLEWARE Django?
    A)Да: запрос идёт сверху вниз, ответ — снизу вверх
    B)Нет, Django сам сортирует middleware по важности задач
    C)Важен только для CSRF, остальные независимы друг от друга
    D)Порядок влияет лишь на скорость, но не на поведение
    показать ответ и разбор
    +A)Да: запрос идёт сверху вниз, ответ — снизу вверх

    // разбор: Middleware в Django — луковица: на входе запрос идёт списком сверху вниз, на выходе ответ — в обратном порядке. Поэтому Security/Authentication ставят раньше, чем то, что на них опирается. Перепутанный порядок ломает аутентификацию или пропускает проверки.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.