Десериализовать POST-запрос с подмножеством полей сериализатора в DRF - PullRequest
0 голосов
/ 11 октября 2018

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

У меня есть (упрощенная) модель и сериализатор, подобные этому:

class CartProduct(models.Model):
    cart = models.ForeignKey('Cart', on_delete=models.CASCADE)
    product = models.ForeignKey('Product', on_delete=models.CASCADE)

class CartProductSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.ReadOnlyField()
    product = ProductSerializer()

    class Meta:
        model = CartProduct
        fields = ('id', 'url', 'product')

, который выдает ответ GET, подобный следующему:

"url": "http://localhost:8000/appUsers/1/cart/products/16/",
"id": 16,
"product": {
    "url": "http://localhost:8000/products/1/",
    "id": 1,
    "name": "Tomatoes",
},
"cart": "http://localhost:8000/carts/1/"

Однако при создании нового CartProduct сейчас в этом сценарии по умолчанию мне нужно будет передать словарь вложенных продуктов, такой каквыше, чтобы создать / десериализовать новый CartProduct из запроса POST.

Вместо этого я хотел бы отправить запрос POST с телом, используя только первичные ключи или URL-адреса для создания нового продукта корзины, например, так:

"product": 1,
"cart": 1

или

"product": "http://localhost:8000/products/1/"
"cart": "http://localhost:8000/carts/1/"

Так что теперь мне стало интересно, как лучше всего это сделать?Я подумал:

  • Написание двух отдельных сериализаторов (но мне не нравится идея иметь два сериализатора для почти каждой модели, подобной этой)
  • Добавление дополнительных полей в каждый сериализаторубедившись, что вложенные / связанные модели всегда представлены в виде url и / или id, и только обязательные поля этих идентификаторов обязательны
  • Переопределение функции проверки / создания, чтобы сделать требуемый ввод действительным форматом
  • Переопределение функций создания ModelViewSet и решение проблемы там

Что было бы наиболее подходящим местом для работы в таком случае?

Ответы [ 3 ]

0 голосов
/ 12 октября 2018

Вы можете изменить поведение сериализатора, переопределив to_representation метод

class CartProduct(models.Model):
    cart = models.ForeignKey('Cart', on_delete=models.CASCADE)
    product = models.ForeignKey('Product', on_delete=models.CASCADE)


class CartProductSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.ReadOnlyField()

    class Meta:
        model = CartProduct
        fields = ('id', 'url', 'product')

    def to_representation(self, instance):
        self.fields['product'] = ProductSerializer(read_only=True)
        return super().to_representation(instance)

Таким образом, ваш сериализатор будет использовать PrimaryKeyRelatedField по умолчанию, и при отображении представления он будет использовать вложенный ProductSerializer

ПРИМЕЧАНИЕ: также вам не нужно явно указывать поле id, если оно по умолчанию AutoField, достаточно просто добавить его в мета-опцию fields.

0 голосов
/ 12 октября 2018

Я предпочитаю использовать следующий подход, где у меня есть 2 поля сериализатора для 1 поля модели (одно поле только для чтения для деталей и одно поле id / url для создания и обновления):

class CartProductSerializer(serializers.HyperlinkedModelSerializer):
    product_detail = ProductSerializer(source='product', read_only=True) 

    class Meta:
        model = CartProduct
        fields = ('url', 'cart', 'product', 'product_detail')

Обратите внимание, что это предполагает, что ProductSerializer уже определено в другом месте.И я опускаю идентификатор, потому что он нам на самом деле не нужен, но вы все равно можете добавить его, если хотите.

Это имеет следующие преимущества:

  • Вы можете использовать то же самоеСериализатор для всех операций CRUD.
  • Вы получаете подробные сведения о вложенных полях в GET, но можете просто предоставить идентификаторы для этих вложенных полей в POST / PUT.
  • Вам не нужно писать никакой собственной логикив ваших представлениях для синтаксического анализа и т. д. - вы можете придерживаться стандартной функции представления по умолчанию, которую вы получаете из коробки

Так что в вашем конкретном случае JSON, который вы получите обратно для GET, будетbe:

{
  "url": "http://localhost:8000/appUsers/1/cart/products/16/",
  "product": "http://localhost:8000/products/1/"
  "product_detail": {
    "url": "http://localhost:8000/products/1/",
    "name": "Tomatoes",
  },
  "cart": "http://localhost:8000/carts/1/"
}

А для POST вам нужно только отправить:

{
  "product": "http://localhost:8000/products/2/"
  "cart": "http://localhost:8000/carts/1/"
}

Для PUT включите собственный url объекта CartProduct в вышеприведенный JSON.

0 голосов
/ 11 октября 2018

То есть вы хотите, чтобы десериализованный CartProductSerializer включал вложенное представление Product, а с другой стороны, при сериализации вы хотите предоставить только идентификатор существующего Product?Вы правы, что создание дополнительного поля - это одно решение, и мне оно нравится больше всего.

  1. Установить product только для чтения, поскольку вы фактически не принимаете вложенный словарь product вваш сериализатор ( вы можете, хотя ).
  2. Создать новое поле, product_id = ModelField(model_field=Product()._meta.get_field('id')).Это позволит вам передать product_id при сериализации.Если вы хотите исключить это при десериализации, вы можете установить его только для записи.См. этот ответ .
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...