New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to GET a full object but POST/PUT/DELETE with only an id on related fields #5206

Closed
sametmax opened this Issue Jun 8, 2017 · 4 comments

Comments

Projects
None yet
5 participants
@sametmax

sametmax commented Jun 8, 2017

Checklist

  • [x ] I have verified that that issue exists against the master branch of Django REST framework.
  • [x ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
  • [x ] This is not a usage question. (Those should be directed to the discussion group instead.)
  • [x ] This cannot be dealt with as a third party library. (We prefer new functionality to be in the form of third party libraries where possible.)
  • [x ] I have reduced the issue to the simplest possible case.
  • I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

Steps to reproduce

Create a Serializer with a link to another serializer. You can have the relation be symbolized by an id, a hyperlink or a full object. But it must be either. This means you have to choose between hard to read (only a reference you have to fetch) but easy to write (only a reference to send) or easy to read (full object) but hard to write (you have to recreate the full chain of nested objects to create a new entry).

Expected behavior

There should be a way to ask for a serializer to accept only an reference (id or link) as an input, but always return the full object.

Actual behavior

Currently you have to do all the work manually or create 2 serializers.

Current workaround

I'm currently using something like this to work around this issue:

from collections import OrderedDict

from rest_framework import serializers


class AsymetricRelatedField(serializers.PrimaryKeyRelatedField):

    def to_representation(self, value):
        return self.serializer_class(value).data

    def get_queryset(self):
        if self.queryset:
            return self.queryset
        return self.serializer_class.Meta.model.objects.all()

    def get_choices(self, cutoff=None):
        queryset = self.get_queryset()
        if queryset is None:
            return {}

        if cutoff is not None:
            queryset = queryset[:cutoff]

        return OrderedDict([
            (
                item.pk,
                self.display_value(item)
            )
            for item in queryset
        ])

    def use_pk_only_optimization(self):
        return False

    @classmethod
    def from_serializer(cls, serializer, name=None, args=(), kwargs={}):
        if name is None:
            name = f"{serializer.__class__.name}AsymetricAutoField"

        return type(name, [cls], {"serializer_class": serializer})

This let me write:

class FooSerializer(serilizers.ModelSerializer):

    bar = AsymetricRelatedField(BarSerializer)

    class Meta:
        model = Foo

Et I can send an id for bar in a POST, or get a a full bar object in my GET. I wish something like this would officially exist in DRF.

@xordoquy

This comment has been minimized.

Show comment
Hide comment
@xordoquy

xordoquy Jun 8, 2017

Collaborator

This tends to pop up on a regular basis. I thought someone made a 3rd party for it but can't find it. I'd be happy to add it to the documentation if anyone implements it.

However I think it's unlikely to go into core as I do believe the result of a GET should be able to be sent through a PUT and keep the operation idempotent. I would like to avoid the asymmetry as much as possible.

To achieve this, one needs to mark the pk or url field as read/write on the nested serializer while every other nested serializer's fields are set to read only. Therefore, you can set the relation through the primary key while sending the full object in return.

Collaborator

xordoquy commented Jun 8, 2017

This tends to pop up on a regular basis. I thought someone made a 3rd party for it but can't find it. I'd be happy to add it to the documentation if anyone implements it.

However I think it's unlikely to go into core as I do believe the result of a GET should be able to be sent through a PUT and keep the operation idempotent. I would like to avoid the asymmetry as much as possible.

To achieve this, one needs to mark the pk or url field as read/write on the nested serializer while every other nested serializer's fields are set to read only. Therefore, you can set the relation through the primary key while sending the full object in return.

@xordoquy xordoquy closed this Jun 8, 2017

@fabien-michel

This comment has been minimized.

Show comment
Hide comment
@fabien-michel

fabien-michel Jun 8, 2017

Just to share, our workaround for this problem is to have two fields:

  • bar to get serialized related field
  • bar_id to get and write it PK
class FooSerializer(serilizers.ModelSerializer):
    bar = BarSerializer(read_only=True)
    bar_id = serializers.PrimaryKeyRelatedField(source='bar',  queryset=Bar.objects.all(), )

    class Meta:
        model = Foo
        fields = ('bar', 'bar_id', )

fabien-michel commented Jun 8, 2017

Just to share, our workaround for this problem is to have two fields:

  • bar to get serialized related field
  • bar_id to get and write it PK
class FooSerializer(serilizers.ModelSerializer):
    bar = BarSerializer(read_only=True)
    bar_id = serializers.PrimaryKeyRelatedField(source='bar',  queryset=Bar.objects.all(), )

    class Meta:
        model = Foo
        fields = ('bar', 'bar_id', )
@BigChief45

This comment has been minimized.

Show comment
Hide comment
@BigChief45

BigChief45 Feb 22, 2018

@fabien-michel Thanks for the workaround. However, this also includes the bar_id field in the response. Is there any way to not include this in the response but still use it for creation? It seems that exclude option does not work...

BigChief45 commented Feb 22, 2018

@fabien-michel Thanks for the workaround. However, this also includes the bar_id field in the response. Is there any way to not include this in the response but still use it for creation? It seems that exclude option does not work...

@czarcismok

This comment has been minimized.

Show comment
Hide comment
@czarcismok

czarcismok Feb 23, 2018

@fabien-michel thanks, I've been struggling with that concept for a while now
@BigChief45 just add write_only=True argument to bar_id field ;)

@fabien-michel thanks, I've been struggling with that concept for a while now
@BigChief45 just add write_only=True argument to bar_id field ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment