Как изменить конечную точку Django REST для автоматического создания зависимой модели при создании представленной модели? - PullRequest
1 голос
/ 20 февраля 2020

Я использую модуль djangorestframework для настройки API для обновления / чтения моих моделей. У меня есть эти модели ...

from django.db import models

from address.models import AddressField
from phonenumber_field.modelfields import PhoneNumberField
from address.models import State
from address.models import Country


class CoopTypeManager(models.Manager):

    def get_by_natural_key(self, name):
        return self.get_or_create(name=name)[0]


class CoopType(models.Model):
    name = models.CharField(max_length=200, null=False)

    objects = CoopTypeManager()

    class Meta:
        unique_together = ("name",)


class Coop(models.Model):
    name = models.CharField(max_length=250, null=False)
    type = models.ForeignKey(CoopType, on_delete=None)
    address = AddressField(on_delete=models.CASCADE)
    enabled = models.BooleanField(default=True, null=False)
    phone = PhoneNumberField(null=True)
    email = models.EmailField(null=True)
    web_site = models.TextField()

, и у меня есть эти классы представления ...

class CoopList(APIView):
    """
    List all coops, or create a new coop.
    """
    def get(self, request, format=None):
        coops = Coop.objects.all()
        serializer = CoopSerializer(coops, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = CoopSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class CoopDetail(APIView):
    """
    Retrieve, update or delete a coop instance.
    """
    def get_object(self, pk):
        try:
            return Coop.objects.get(pk=pk)
            raise Http404

    def get(self, request, pk, format=None):
        coop = self.get_object(pk)
        serializer = CoopSerializer(coop)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        coop = self.get_object(pk)
        serializer = CoopSerializer(coop, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        coop = self.get_object(pk)
        coop.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Проблема в том, что я хотел бы представить JSON вот так

{
        "name": "1872",
        "type": {
            "name": "Coworking Space"
        },

с намерением заключить, что для зависимой модели CoopType я либо создаю одну из существующих, либо использую модель до Coop. Тем не менее, прямо сейчас, отправка вышеупомянутых результатов в ответе 400 ...

{"type":["Incorrect type. Expected pk value, received dict."]

Как мне изменить мой класс представления, чтобы соответствовать тому, что я пытаюсь сделать?

Редактировать: сериализаторы ...

from rest_framework import serializers
from maps.models import Coop, CoopType
from address.models import Address, AddressField, Locality, State, Country


class CoopSerializer(serializers.ModelSerializer):
    class Meta:
        model = Coop
        fields = ['id', 'name', 'type', 'address', 'enabled', 'phone', 'email', 'web_site']

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['type'] = CoopTypeSerializer(instance.type).data
        rep['address'] = AddressSerializer(instance.address).data
        return rep

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Coop.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Coop` instance, given the validated data.
        """
        instance.name = validated_data.get('name', instance.name)
        instance.type = validated_data.get('type', instance.type)
        instance.address = validated_data.get('address', instance.address)
        instance.enabled = validated_data.get('enabled', instance.enabled)
        instance.phone = validated_data.get('phone', instance.phone)
        instance.email = validated_data.get('email', instance.email)
        instance.web_site = validated_data.get('web_site', instance.web_site)
        instance.web_site = validated_data.get('web_site', instance.web_site)
        instance.save()
        return instance


class CoopTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = CoopType
        fields = ['id', 'name']

    def create(self, validated_data):
        """
        Create and return a new `CoopType` instance, given the validated data.
        """
        return CoopType.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `CoopType` instance, given the validated data.
        """
        instance.name = validated_data.get('name', instance.name)
        instance.save()
        return instance

Редактировать 2: Запрос скручивания ...

#!/bin/bash

read -d '' req << EOF
{
        "name": "1872",
        "type": {
            "name": "Coworking Space"
        },
        "address": {
            "id": 1,
            "street_number": "222",
            "route": "1212",
            "raw": "222 W. Merchandise Mart Plaza, Suite 1212",
            "formatted": "222 W. Merchandise Mart Plaza, Suite 1212",
            "latitude": 41.88802611,
            "longitude": -87.63612199,
            "locality": {
                "id": 29,
                "name": "Chicago",
                "postal_code": "60654",
                "state": {
                    "id": 1,
                    "name": "IL",
                    "code": "",
                    "country": {
                        "id": 484,
                        "name": "United States",
                        "code": "US"
                    }
                }
            }
        },
        "enabled": true,
        "phone": null,
        "email": null,
        "web_site": "http://www.1871.com/"
}
EOF

echo $req

curl -v --header "Content-type: application/json" --data "$req" --request POST "http://127.0.0.1/coops/"

Ответы [ 3 ]

1 голос
/ 22 февраля 2020

Это должно работать:

class CoopTypeField(serializers.PrimaryKeyRelatedField):

    queryset = CoopType.objects

    def to_internal_value(self, data):
        if type(data) == dict:
            cooptype, created = CoopType.objects.get_or_create(**data)
            # Replace the dict with the ID of the newly obtained object
            data = cooptype.pk
        return super().to_internal_value(data)

class CoopSerializer(serializers.ModelSerializer):
    type = CoopTypeField()
    # ... the rest of this class is unchanged

Где изменения:

  1. Определить пользовательский CoopTypeField(). Метод to_internal_value() обычно ожидал бы идентификатор - поэтому мы переопределяем его, чтобы принимать данные в форме словаря и преобразовывать их в идентификатор (путем получения или создания CoopType), а затем передавать этот идентификатор родительскому классу. method.

  2. Определите type для CoopSerializer, который использует этот новый CoopTypeField().

Теперь ваш CoopSerializer будет принимать данные в двух формах:

{'name': 'Coop 1', 'web_site': 'http://example.com', 'type': {'name': 'Coop Type 1'}}

или

{'name': 'Coop 1', 'web_site': 'http://example.com', 'type': 1}

(другие поля, которые Coop для краткости опущены).

0 голосов
/ 23 февраля 2020

О, мальчик ... Здесь так много всего, что мы могли бы улучшить. Итак, давайте начнем с вашего вопроса в первую очередь.

@ tredzko ({ ссылка }) был прав, и вы должны посмотреть на https://www.django-rest-framework.org/api-guide/relations/#writable -nested-сериализаторы

По внешнему виду CoopType модель Я вижу, что поле name равно unique.

from rest_framework import serializers
from maps.models import Coop, CoopType
from address.models import Address, AddressField, Locality, State, Country


class CoopTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = CoopType
        fields = ['id', 'name']


class CoopSerializer(serializers.ModelSerializer):
    # type field should be defined here instead of `to_representation`
    type = CoopTypeSerializer()

    class Meta:
        model = Coop
        fields = ['id', 'name', 'type', 'address', 'enabled', 'phone', 'email', 'web_site']

    def to_representation(self, instance):
        # this is the correct way of extending to_representation
        # we set update self.fields and after that
        # Serializer class handles everything automatically
        self.fields['address'] = AddressSerializer(read_only=True)
        return super().to_representation(instance)

    def validate_type(self, value):
        coop_type, __ = CoopType.objects.get_or_create(**coop_type_data)
        return coop_type

Что-то в этом духе должно работать.

Теперь давайте углубимся в другие улучшения

Я предлагаю использовать ModelViewSet (https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset)

Чтобы вы могли это сделать (см. Ниже) вместо кода ваших просмотров

from rest_framework import viewsets


class CoopViewSet(viewsets.ModelViewSet):
    serializer_class = CoopSerializer
    queryset = Coop.objects.all().select_related('type')

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

class CoopType(models.Model):
    # https://docs.djangoproject.com/en/3.0/ref/models/fields/#unique
    name = models.CharField(max_length=200, null=False, unique=True)

    class Meta:
        pass
0 голосов
/ 20 февраля 2020

Исходя из текущего сериализатора, CoopType должен быть сделан заранее (или извлечен заранее, если он существует), чтобы можно было передавать идентификатор. Если вы все еще хотите создать CoopType с вложенными данными в сериализаторе Coop (или использовать его для поиска CoopType для использования), Django Rest Framework расскажет о том, как в их документах в Writable nested сериализаторы .

...