ModelViewSet - обновление вложенного поля

В настоящее время я работаю над Django с Django Rest Framwork.

Я не могу обновить свой объект внутри поля вложенных объектов.


serializer.py

class OwnerSerializer(serializers.ModelSerializer):
    class Meta:
        model =  Owner
        fields = ('id', 'name')

class CarSerializer(serializers.ModelSerializer):
    owner = ownerSerializer(many=False, read_only=False) 
    class Meta:
        model =  Car
        fields = ('id', 'name', 'owner')

view.py

class OwnerViewSet(viewsets.ModelViewSet):
    queryset = Owner.objects.all()
    serializer_class = OwnerSerializer

class CarViewSet(viewsets.ModelViewSet):
    serializer_class = CarSerializer
    queryset = Car.objects.all()

    def create(self, request):
        serialized = self.serializer_class(data=request.DATA)
        if serialized.is_valid():
            serialized.save()
            return Response(status=HTTP_202_ACCEPTED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Когда я это сделаю:

Request URL:http://localhost:9000/api/v1/cars/1/?format=json
Request Method:PUT
Request Paylod :
{
    "id":1,
    "name": "TEST", 
    "ower": {
        "id":1,
        "name": "owner_test"
    }
}

Я получаю следующий ответ:

The `.update()` method does not support writable nestedfields by default.
Write an explicit `.update()` method for serializer `app.serializers.CarSerializer`,
or set `read_only=True` on nested serializer fields.

Зная:

  • Я хочу сохранить сериализацию владельца в GET;
  • Мы можем представить себе автомобиль, вложенный другим объектом и ect...

Как я могу сделать, если я хочу изменить владельца, когда я обновляю автомобиль.

Ответ 1

Немного поздно, но, попробуй,

class OwnerSerializer(serializers.ModelSerializer):
    class Meta:
        model =  Owner
        fields = ('id', 'name')
        extra_kwargs = {
            'id': {
                'read_only': False, 
                'required': True
             }
        } #very important

    def create(self, validated_data):
        # As before.
        ...

    def update(self, instance, validated_data):
        # Update the  instance
        instance.some_field = validated_data['some_field']
        instance.save()

        # Delete any detail not included in the request
        owner_ids = [item['owner_id'] for item in validated_data['owners']]
        for owner in cars.owners.all():
            if owner.id not in owner_ids:
                owner.delete()

        # Create or update owner 
        for owner in validated_data['owners']:
            ownerObj = Owner.objects.get(pk=item['id'])
            if ownerObje:
                ownerObj.some_field=item['some_field']
                ....fields...
            else:
               ownerObj = Owner.create(car=instance,**owner)
            ownerObj.save()

        return instance

Ответ 2

В любом случае кто-то спотыкается над этим

имел ту же ошибку в моем случае, но установил read_only, чтобы True зафиксировал это для меня.

    owner = ownerSerializer(many=False, read_only=True) 

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

Ответ 3

Источник ModelSerializer.create() и ModelSerializer.update() запрещает это поведение:

raise_errors_on_nested_writes('create', self, validated_data)

Если мы удалим эту строку, действие может пройти и работать хорошо.

Это означает, документ говорит:

Чтобы использовать записываемую вложенную сериализацию, вы хотите объявить вложенное поле в классе сериализатора и явно написать методы create() и/или update().

Мы можем напрямую использовать исходный код ModelSerializer.save() и ModelSerializer.update() для этого метода "write explicit" с удалением строки raise_errors_on_nested_writes.

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

import traceback
from rest_framework.utils import model_meta
from rest_framework.compat import set_many

class AllowNestedWriteMixin:
    def create(self, validated_data):
        ModelClass = self.Meta.model
        info = model_meta.get_field_info(ModelClass)
        many_to_many = {}
        for field_name, relation_info in info.relations.items():
            if relation_info.to_many and (field_name in validated_data):
                many_to_many[field_name] = validated_data.pop(field_name)

        try:
            instance = ModelClass.objects.create(**validated_data)
        except TypeError:
            tb = traceback.format_exc()
            msg = (
                'Got a `TypeError` when calling `%s.objects.create()`. '
                'This may be because you have a writable field on the '
                'serializer class that is not a valid argument to '
                '`%s.objects.create()`. You may need to make the field '
                'read-only, or override the %s.create() method to handle '
                'this correctly.\nOriginal exception was:\n %s' %
                (
                    ModelClass.__name__,
                    ModelClass.__name__,
                    self.__class__.__name__,
                    tb
                )
            )
            raise TypeError(msg)

        # Save many-to-many relationships after the instance is created.
        if many_to_many:
            for field_name, value in many_to_many.items():
                set_many(instance, field_name, value)

        return instance

    def update(self, instance, validated_data):
        info = model_meta.get_field_info(instance)

        for attr, value in validated_data.items():
            if attr in info.relations and info.relations[attr].to_many:
                set_many(instance, attr, value)
            else:
                setattr(instance, attr, value)
        instance.save()

        return instance

И если мы хотим, чтобы поведение было выполнено, мы наследуем этот класс mixin перед ModelSerializer, что-то вроде приведенного ниже в вашем примере:

class OwnerSerializer(AllowNestedWriteMixin,
                      serializers.ModelSerializer):
    class Meta:
        model =  Owner
        fields = ('id', 'name')

class CarSerializer(AllowNestedWriteMixin,
                    serializers.ModelSerializer):
    owner = ownerSerializer(many=False, read_only=False) 
    class Meta:
        model =  Car
        fields = ('id', 'name', 'owner')