with this already exist” on PUT c

2019-04-26 13:13发布

问题:

I'm doing a HTTP PUT call to update the data of an object with a nested relationship, and I'm met by the following error:

HTTP 400 Bad Request

"AttributeChoice with this slug already exists."

The reason why this is confusing is because I'm doing a HTTP PUT call and I expect it to treat it as an UPDATE and not a CREATE.

My Models look like this:

class Attribute(models.Model):
    name        = models.CharField(max_length=100)
    text_input  = models.BooleanField(default=False)
    slug        = models.SlugField(unique=True)

class AttributeChoice(models.Model):
    attribute   = models.ForeignKey(Attribute)
    value       = models.CharField(max_length=100)
    slug        = models.SlugField(unique=True)

My Serializers look like this:

class AttributeChoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = AttributeChoice
        fields = '__all__'
        extra_kwargs = {'id': {'read_only': False}}

class AttributeSerializer(serializers.ModelSerializer):
    attributechoice_set = AttributeChoiceSerializer(many=True)
    class Meta:
        model = Attribute
        fields = ('id', 'name', 'text_input', 'slug', 'attributechoice_set')

    def update(self, instance, validated_data):
        choice_data = validated_data.pop('attributechoice_set') 
        for choice in choice_data:

            # If id is within the call, then update the object with matching id
            if 'id' in choice:
                try:
                    choice_obj = AttributeChoice.objects.get(pk=choice['id'])
                    choice_obj.value = choice['value']
                    choice_obj.slug = choice['slug']
                    choice_obj.attribute = instance
                # If ID is not found, then create a new object
                except AttributeChoice.DoesNotExist:
                    choice_obj = AttributeChoice(**choice)
            # If no ID within the call, create a new object.
            else:
                choice_obj = AttributeChoice(**choice)

            choice_obj.save()

        return instance

Debug: Even if I remove the update() function, I still get the same error. I believe the error is reported from when .is_valid() is called in the ViewSet. So it's not the update() that causes it.

Also, if I remove attributechoice_set = AttributeChoiceSerializer(many=True) and just include the attributechoice_set in the fields = (), the error disappears, but I need that line for the rest of the code to work.

回答1:

Even through you're doing an update, it doesn't mean the nested data will just be updated.

You're simply saying that you want to update the top most object.

In some cases, you'll be removing or creating new nested objects while updating the top most one.

Therefore DRF considers by default that nested objects are for creation. You can work around this by explicitly removing the unique constraint on the nested serializer:

class AttributeChoiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = AttributeChoice
        fields = '__all__'
        extra_kwargs = {
            'id': {'read_only': False},
            'slug': {'validators': []},
        }


回答2:

I think it is because of the validators.

Like:

Django rest serializer Breaks when data exists

As my solution, I mark this nested field to read_only=True, And do my own update, create function to access self.initial_data to handle myself.