Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST Framework nested resource key "id" unaccessible

So I have the following Structure:

A ClientFile belongs to an Owner (class name = Contact). I'm trying to create a Clientfile using the API. The request contains the following data:

{
  name: "Hello!"
  owner: {
    id: 1,
    first_name: "Charlie",
    last_name: "Watson"
  }
}

I created the serializer according to my structure. Hoping that this API call would create a clientfile with the name "Hello!" and Contact id 1 as the owner:

class ContactSerializer(serializers.ModelSerializer):
  class Meta:
    model = Contact
    fields = (
      'id',
      'first_name',
      'last_name',
    )

class ClientfileSerializer(serializers.ModelSerializer):

  owner = ContactSerializer(read_only=False)

  class Meta():
    model = Clientfile
    fields = (
      'id',
      'name',
      'owner',
    )

  def create(self, validated_data):

    owner = Contact.objects.get(pk=validated_data['owner']['id'])

I do get into the create method. However, the only field I need (['owner']['id']) is not accessible. If I do print ['owner']['first_name'] it does return 'Charlie'. But the ID for some reasons doesn't seem to be accessible...

Any reasons why this can be happening? Am i missing something? (I'm new to Django)


SOLUTION: Just found out that the reason why ID didn't show in the first place was because I had to declare it in the fields like so: Hope this helps.

class ContactSerializer(serializers.ModelSerializer):

  id = serializers.IntegerField() # ← Here

  class Meta:
    model = Contact
    fields = (
      'id',
      'first_name',
      'last_name',
    )
like image 392
gkpo Avatar asked Mar 13 '15 14:03

gkpo


3 Answers

In Django REST Framework AutoField fields (those that are automatically generated) are defaulted to read-only. From the docs:

read_only

Set this to True to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization.

Defaults to False

You can see this by inspecting your serializer by printing the representation in your shell:

serializer = ClientfileSerializer()
print repr(serializer)

You can override this by setting read_only=False against the id field in the extra_kwargs:

class ContactSerializer(serializers.ModelSerializer):
  class Meta:
    model = Contact
    fields = (
      'id',
      'first_name',
      'last_name',
    )
    extra_kwargs = {'id': {'read_only': False}}

class ClientfileSerializer(serializers.ModelSerializer):

  owner = ContactSerializer(read_only=False)

  class Meta():
    model = Clientfile
    fields = (
      'id',
      'name',
      'owner',
    )
    extra_kwargs = {'id': {'read_only': False}}
like image 157
br3w5 Avatar answered Oct 31 '22 21:10

br3w5


Alright so I found a different approach that works. I added an IntegerField serializer for the owner relation. I also had to set the owner relation to read_only=True.

This is the json I am sending via POST:

{
  name: "Hello!"
  owner_id: 1
}

This is my serializer:

class ClientfileSerializer(serializers.ModelSerializer):

  owner_id = serializers.IntegerField()
  owner = ContactSerializer(read_only=True)

  class Meta():
    model = Clientfile
    fields = (
      'id',
      'owner_id',
      'owner',
    )

It seems less cool than the first way, but it does the job. Plus I don't want to create a new owner, but just select one that is already in the database. So maybe it's more semantic to only have the ID and not the full set of information posted via Json.

like image 41
gkpo Avatar answered Oct 31 '22 22:10

gkpo


You can try something like this:

class YourModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = YourModel
        fields = ('id', 'field1', 'field2')

    def to_internal_value(self, data):
        """
        Dict of native values <- Dict of primitive datatypes.
        Add instance key to values if `id` present in primitive dict
        :param data:
        """
        obj = super(YourModelSerializer, self).to_internal_value(data)
        instance_id = data.get('id', None)
        if instance_id:
            obj['instance'] = YourModel.objects.get(id=instance_id)
        return obj

Then in serializer validated data you should have "instance" key if request.data has "id" key.

Also You can add just "id" instead of full instance/object.

like image 3
user3900778 Avatar answered Oct 31 '22 21:10

user3900778