Несколько сериализаторов против нескольких представлений в Django Rest Framework

Когда вы создаете простые представления CRUD API в Django Rest Framework, я предполагаю, что большую часть времени вы создаете только две конечные точки: одну для действий list / create и вторую для retrieve / update / destroy действия.

Реализация только одного представления для нескольких действий означает наличие для них только одного сериализатора. Меньше кода, больше «СУХОГО», не так ли? Однако иногда это не так хорошо, потому что нашему API могут потребоваться разные форматы данных для каждого действия. Так что же делать, если с нами такое случится?

Есть много решений и разных подходов к этой проблеме, и я постараюсь представить некоторые из них.

Краткое введение

Предположим, у нас есть пиццерия (кто не любит пиццу?), И нам нужно реализовать приложение, в котором наши клиенты могли бы делать заказы.

Модели выглядят следующим образом:

Итак, у нас естьPizza модель с полями:

  • name ,
  • price (в данном случае не важно),
  • ingredients (ManyToManyField до Ingredient, потому что в каждой пицце может быть много ингредиентов),
  • sauce (ForeignKey) к модели Sauce, потому что у каждой пиццы может быть только один соус).

Давайте реализуем очень простое PizzaAPIView наследование от generics.ListCreateAPIView, которое будет обрабатывать действия list / create.

И очень простой PizzaSerializer унаследованный от ModelSerializer:

Еще одна строка кода в urls.py:

И загляните в наши Ingredients и Sauces столы, чтобы узнать, что есть на кухне нашего ресторана :).

Хорошо, давай создадим лучшую пиццу на свете :).
Вот как должен выглядеть JSON (для запроса POST):

{
    "name": "Hawaiian",
    "price": 29.99,
    "sauce": 1,
    "ingredients": [1,2,5]
}

И отправьте запрос GET, чтобы получить список. Посмотрим на ответ:

Хм, все работает, но… где мой ананас ?! Я хочу видеть в нашем списке не идентификаторы соусов и ингредиентов, а их названия.

Как это сделать без особых усилий и с чистым кодом?

Несколько представлений - несколько сериализаторов

Первое решение - разделить наше представление на два отдельных:

Таким образом, мы можем создать новый сериализатор, который будет обрабатывать PizzaListAPIView:

Я переопределил поле sauce, чтобы вернуть имя соуса (используя свойство source), и добавил depth = 1 в класс Meta, чтобы получить в результате полный объект.

Осталось одно. Нам нужно создать отдельную конечную точку для каждого представления.

Теперь наш результат будет выглядеть так:

Ага, пицца с ананасом! Это то что мне нравится :)

Но этот подход требовал создания дополнительного представления и конечной точки. Может есть решение получше?

Одно представление - один сериализатор

Почему бы не попробовать и смешать все в одном представлении и одном сериализаторе? Вернемся к основному PizzaAPIView и немного изменим PizzaSerializer:

Я добавил два дополнительных поля: selected_sauce и selected_ingredients, чтобы возвращать объекты в желаемом формате. Я также присвоил им свойство read_only, чтобы они были доступны только в ответе метода GET.

И еще кое-что. Назначив полям sauce и ingredients свойство write_only, я уверен, что эти два поля будут обязательными в данных запроса POST, но они не будут отображаться в ответе.

Этот подход позволяет нам обрабатывать два метода в одном представлении и один сериализатор. Но всегда ли это лучшее решение? Ответ зависит от вас.

Если вы спросите мое мнение, я считаю, что это совершенно беспорядочно, когда для одного и того же поля используются разные имена в представлениях list и create. Кроме того, в тот момент, когда мне нужно взглянуть на каждое определение поля и проверить, read_only или write_only, я чувствую себя перегруженным из-за слишком большого количества информации, собранной в одном классе, что делает код недостаточно чистым.

Вот почему я искал другие решения, и вот что я нашел ...

Одно представление - несколько сериализаторов

Давайте вернемся к нашему основному виду и немного его изменим. Django Rest Framework предлагает множество методов, которые мы можем просто переопределить для достижения наших целей. (Если вы хотите узнать о них больше, загляните здесь.

Один из этих методов - get_serializer_class(). Этот метод вызывается, когда мы отправляем запрос, а Django ищет класс сериализатора. По умолчанию он возвращает serializer_class, который определен в представлении. Но если мы переопределим метод, произойдет настоящее волшебство ...

Что тут происходит? Просто проверьте, является ли наш метод запроса POST (это означает, что мы создаем новый объект). Если да, мы говорим Django использовать PizzaCreateSerializer. В любом другом случае будет использоваться PizzaListSerializer.

Затем мы можем создать наши сериализаторы:

И обновите urls.py:

Вау, это потрясающе! Мы только что использовали два сериализатора в одном представлении. И мы можем пойти еще дальше и использовать столько, сколько захотим, просто реализовав любую логику, которую мы можем вообразить в методе get_serializer_class().

Теперь код выглядит намного чище, и каждый сериализатор отвечает только за одно действие.

Вывод

Как видите, почти у каждой проблемы есть множество решений, и каждое решение может быть правильным в определенных контекстах, поэтому действительно полезно знать разные подходы и использовать их поочередно в зависимости от ваших потребностей. Не бойтесь экспериментировать со своим кодом - это лучший способ открыть для себя что-то новое и (надеюсь) что-то лучшее! :)

P.S. Надеюсь, ты не возненавидишь меня за ананас на пицце, но мне он очень нравится;).