Why does PATCH on Django Rest Framework wipe an unchanged ManyToMany field?

3,435 views
Skip to first unread message

Andrew Widgery

unread,
Feb 17, 2015, 9:32:23 AM2/17/15
to django-res...@googlegroups.com

Cross-posted from SO - apologies if that's bad form!

I am PATCHing a model using DRF, and the call is, for some reason, wiping a ManyToMany field. Why is this?

I have a Feature model:

class Feature(CommonInfo):
    user = models.ForeignKey(User)
    name = models.CharField(max_length=50)
    parent = models.ForeignKey("Feature", blank=True, null=True, related_name='children')
    tags = models.ManyToManyField("Tag")
    level = models.IntegerField()
    box_image = ImageField()
    background_image = ImageField()

...and a serializer:

class FeatureSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.ReadOnlyField(source='user.username') 
    children = serializers.HyperlinkedRelatedField(read_only=True, view_name='feature-detail', many=True)
    level = serializers.ReadOnlyField()

    def get_fields(self, *args, **kwargs):
        user = self.context['request'].user
        fields = super(FeatureSerializer, self).get_fields(*args, **kwargs)
        fields['parent'].queryset = fields['parent'].queryset.filter(user=user)
        return fields

    class Meta:
        model = Feature

...and a viewset:

class FeatureViewSet(viewsets.ModelViewSet):
    serializer_class = FeatureSerializer

    def get_queryset(self):
        return self.request.user.feature_set.order_by('level', 'name')

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

This gives an output for GET /api/features/5/:

{
    "url": "http://localhost:8001/api/features/5/", 
    "user": "andrew", 
    "children": [], 
    "level": 1, 
    "created_at": "2015-02-03T15:11:00.191909Z", 
    "modified_at": "2015-02-03T15:20:02.038402Z", 
    "name": "My Astrantia major 'Claret' plant", 
    "box_image": "http://localhost:8001/media/Common_Knapweed13315723294f5e2e6906593_PF07bKi.jpg", 
    "background_image": null, 
    "parent": "http://localhost:8001/api/features/1/", 
    "tags": [
        "http://localhost:8001/api/tags/256/"
    ]
}

Suppose I want to run a PATCH call to update name:

import requests
r = requests.patch("http://localhost:8001/api/features/5/", 
                data={'name':'New name'}, 
                auth=('user', 'password'))
r.json()

This successfully updates the object, but the result also wipes the tags from the object:

{
    "url": "http://localhost:8001/api/features/5/", 
    "user": "andrew", 
    "children": [], 
    "level": 1, 
    "created_at": "2015-02-03T15:11:00.191909Z", 
    "modified_at": "2015-02-03T16:12:48.055527Z", 
    "name": "New name", 
    "box_image": "http://localhost:8001/media/Common_Knapweed13315723294f5e2e6906593_PF07bKi.jpg", 
    "background_image": null, 
    "parent": "http://localhost:8001/api/features/1/", 
    "tags": []
}

litewaitt

unread,
Feb 25, 2015, 9:02:04 AM2/25/15
to django-res...@googlegroups.com
Andrew,
Are you using DRF 3?  I am having a similar issue https://groups.google.com/forum/#!topic/django-rest-framework/dCPmMuR_CD4
I have a "through" model, but no matter what I do or how I define the Serializer, when I try to create the a Group model using {'name': 'my group', 'persons': [1, 2]}, when I pass the Person (ids) they are never considered and I never get the relationship records created.
What is strange is I do override the .create() method and even at that point it seems the Person data has been ripped out of the serialized data.
Please keep me posted and I will you as well.

Andrew Widgery

unread,
Feb 25, 2015, 9:13:32 AM2/25/15
to django-res...@googlegroups.com
Thanks - I’ve got a workaround that’s working so I’m using that at the moment.
I’m sure it’s a bug though…
I’m on djangorestframework==3.0.4

--
You received this message because you are subscribed to a topic in the Google Groups "Django REST framework" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-rest-framework/GeAHrgJ-YoI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-rest-fram...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

litewaitt

unread,
Feb 25, 2015, 10:01:17 AM2/25/15
to django-res...@googlegroups.com
I'd love to know your workaround, I am up against a deadline.
To unsubscribe from this group and all its topics, send an email to django-rest-framework+unsub...@googlegroups.com.

litewaitt

unread,
Feb 25, 2015, 1:34:32 PM2/25/15
to django-res...@googlegroups.com
I am just miffed, how can a serializer that returns:

{
        "id": 1, 
        "name": "sample name 1", 
        "persons": [
            1
        ]
    }, 
When POSTed at the same serializer, in the .create() method, validated_data is:

{
        "id": 1, 
        "name": "sample name 1", 
        "persons": [
        ]
    }, 
It seems it is just refusing to validate the "persons" list.
Any ideas would help I am stuck.

Tom Christie

unread,
Feb 25, 2015, 3:10:51 PM2/25/15
to django-res...@googlegroups.com
Any chance you're using `prefetch_related`, and running into this: https://github.com/tomchristie/django-rest-framework/issues/2442

litewaitt

unread,
Feb 25, 2015, 3:27:01 PM2/25/15
to django-res...@googlegroups.com
Tom,
No I am trying to create a simple bare-bones case where I can (effectively) update the "through" model with the related id's from M2M table.
Thanks.

Tom Christie

unread,
Feb 26, 2015, 6:05:21 AM2/26/15
to django-res...@googlegroups.com
By default 'through' models are read-only in the API.
They can't be writable (without custom work) because we don't have any of the 'through' information to associated with any newly created relationships.
Very happy to accept any documentation pull requests expanding on that.

Tom Christie

unread,
Feb 26, 2015, 6:05:49 AM2/26/15
to django-res...@googlegroups.com
Sorry, I should have written "By default 'through' *relationships* are read-only in the API."

Andrew Widgery

unread,
Feb 26, 2015, 7:52:18 AM2/26/15
to django-rest-framework
My workaround is simply to exclude the ManyToManyField from my serialiser:

    class Meta:

        model = Feature

        exclude = ('tags', 'box_image', 'banner_image')


It’s OK for me in that I don’t need to access the “tags” element at the moment in my API.
If I do in the future, it’s going to cause problems though because as soon as I add it back in (so, for example, I can GET it) then I’ll have the wiping problem again when I PATCH the model.

To unsubscribe from this group and all its topics, send an email to django-rest-fram...@googlegroups.com.

Tom Christie

unread,
Feb 26, 2015, 8:03:45 AM2/26/15
to django-res...@googlegroups.com
I haven't had the time to look into validating this myself, but if someone wants to create a ticket, or even better, a failing test case, then it'll get addressed.
Reply all
Reply to author
Forward
0 new messages