Hieu Phan
Hieu Phan

Reputation: 621

id field in django rest framework serializer

I'm using django rest framework and just creating a simple serializer like this:

class PackageSerializer(serializers.HyperlinkedModelSerializer):

    class Meta:
        model = Package
        fields = ('id', 'url', 'title','location')

However I get this error:

KeyError at /cerberus/packages/
'id'

How come I can't get the primary key 'id' as part of my serialized data? Thanks for helping!

Upvotes: 30

Views: 59071

Answers (4)

Favorite Right Arm
Favorite Right Arm

Reputation: 40

Background

As of Django Rest Framework 3.14.0 (I'm using it right now), it would seem that none of the solutions provided above work. (Maybe romor's, but this response goes into much more details, IMO)

Input

I have a BaseModel that has Meta property "abstract" set to True. Then I'm creating a serializer for my non-base model.

Some sample code — Base Class:

# _base.py
from uuid import uuid4

from django.db import models

class BaseModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True...)
    updated_at = models.DateTimeField(auto_now=True...)

    class Meta:
        abstract = True

Model I'm making a serializer for:

from django.db import models

from ._base import BaseModel


class Comment(BaseModel):
    file = models.FileField(...)
    text = models.TextField(...)

    author = models.ForeignKey("accounts.BaseUser"...)

    class Meta:
        verbose_name = _("Comment")
        verbose_name_plural = _("Comments")
        ordering = ["-created_at"]

As you can see, I have a field called id, an instance of UUIDField class.

Using a read-only field results in that id field being omitted from the response, even though it's present in the Model definition. I've tried this field as an option of UUIDField, and using ReadOnlyField directly.

Solution

To fix this, I've created a serializer ModelWithUUID, it looks something like this:

from rest_framework import serializers


class ModelWithUUID(serializers.ModelSerializer):
    id = serializers.UUIDField()

So, in a way, this is what romor and Jann (the editor) suggested in their response.

By doing this, my final serializer is pretty simple, and looks like this:

class CommentSerializer(ModelWithUUID):
    author = AdditionalSerializerForAuthor()  # Not the actual name
    file = FileRepresentationField(allow_null=True)  # I need some "weird" conversions, don't judge me

    class Meta:
        model = Comment
        fields = "__all__"

This allows me to use __all__ for fields and have an id field at all times.

Additional background, that you may need to know to understand why I do things this way (you can skip this)

It should be noted that I use Django 5.0.1 and drf_yasg.

Maybe my approach is wrong, but I use the serializer's .data property as data for response. And to be even more specific — I use a combination of 3 serializers for all (or almost all) of my endpoints:

  • Input Serialzier, that is used as a type hint for drf_yasg;
  • Output Serializer, same (CommentSerializer is a part of one);
  • Internal Seraializer, that takes data from Input Serializer, and returns an Output Serializer, or a Validation Error.

This combination seems to be pretty flexible so far, but it may not be the best practice in the long run.

Upvotes: 0

romor
romor

Reputation: 1240

According to the Django Rest Framework Documentation:

The default implicitly-generated id field is marked as read_only. This causes it to be removed on updates.

Thus, if you need the id field in an update method, you must not declare it as read-only but use a read-write field instead:

class PackageSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField()

Upvotes: 18

Tom Christie
Tom Christie

Reputation: 33901

HyperlinkedModelSerializer doesn't include the id by default. In 2.2 and earlier you'll need to add it explicitly as a field...

class PackageSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.Field()

    class Meta:
        model = Package
        fields = ('id', 'url', 'title','location')

From 2.3 onwards, you can simply add 'id' to the fields option...

class PackageSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Package
        fields = ('id', 'url', 'title','location')

From 3.x (< 3.3) onwards, you must use ReadOnlyField() instead of Field() if you want to add it explicitly and not use the fields option...

class PackageSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.ReadOnlyField()

    class Meta:
        model = Package

Upvotes: 67

mlissner
mlissner

Reputation: 18156

I just tweaked this to make it a little more pluggable by creating this class:

class HyperlinkedModelSerializerWithId(serializers.HyperlinkedModelSerializer):
    """Extend the HyperlinkedModelSerializer to add IDs as well for the best of
    both worlds.
    """
    id = serializers.ReadOnlyField()

Then, just use that to make your serializer and you're on your way.

Upvotes: 5

Related Questions