Matt
Matt

Reputation: 3642

Append "_id" to foreign key fields in Django Rest Framework

I have a model like so:

class MyModel(models.Model):
    thing = models.ForeignKey('Thing')

Serializers and ViewSet like so:

class ThingSerializer(serializers.ModelSerializer):

    class Meta:
        model = Thing


class MyModelSerializer(serializers.ModelSerializer):

    class Meta:
        model = MyModel

class MyModelViewSet(viewsets.ModelViewSet):

    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

For MyModel list endpoint, DRF return objects like:

[ 
    { id: 1, thing: 1 },
    { id: 2, thing: 1 },
    { id: 3, thing: 2 },
    { id: 4, thing: 4 }
]

Is there a way to tell DRF to automatically include "_id" on the end of ForeignKey fields that are just IDs and not the actual related object? e.g.

[ 
    { id: 1, thing_id: 1 },
    { id: 2, thing_id: 1 },
    { id: 3, thing_id: 2 },
    { id: 4, thing_id: 4 }
]

Upvotes: 12

Views: 3616

Answers (5)

Alex
Alex

Reputation: 177

This has worked for me as of today in July 2022.

def ParentModel():
  ...

def ChildModel():
  parent = models.ForeignKey(ParentModel)

serializers.py

def ChildSerializer():
  parent_id = serializers.PrimaryKeyRelatedField(
      source="parent", 
      queryset=ParentModel.objects.all(), 
  )
  class Meta:
    exclude = ["parent"]
    fields = "__all__"

The source="parent" in the serializer field, is what allows this rename to happen; it needs to match the ForeignKey field name in ChildModel.

Note: The exclude = ["parent"] is required if you are using fields = "__all__" so that DRF doesn't require or return the default field name.

Note 2: If using UUID for your ID field then you'll need to add pk_field=UUIDField(format="hex_verbose"), to the serializer field you're renaming to {field}_id

Upvotes: 3

Ajeet Shah
Ajeet Shah

Reputation: 19813

You can use db_column model field option in your model:

class MyModel(models.Model):
    thing = models.ForeignKey('Thing', db_column='thing_id')

Or if you don't want to change your model, you can do so by changing source serializer field in your serializer:

class ThingSerializer(serializers.ModelSerializer):
    thing_id = serializers.IntegerField(source='thing')
    class Meta:
        model = Thing
        fields = ('thing_id','other_field', 'another_field')

Upvotes: 1

Matt
Matt

Reputation: 3642

Found same request and solution here:

https://github.com/tomchristie/django-rest-framework/issues/3121

https://gist.github.com/ostcar/eb78515a41ab41d1755b

The AppendIdSerializerMixin.get_fields override suffices for output of JSON objects (with _id appended) but when writing back to the API, it's a little more complicated and the logic in IdPrimaryKeyRelatedField and IdManyRelatedField handles that.

class IdManyRelatedField(relations.ManyRelatedField):
    field_name_suffix = '_ids'

    def bind(self, field_name, parent):
        self.source = field_name[:-len(self.field_name_suffix)]
        super(IdManyRelatedField, self).bind(field_name, parent)


class IdPrimaryKeyRelatedField(relations.PrimaryKeyRelatedField):
    """
    Field that  the field name to FIELD_NAME_id.
    Only works together the our ModelSerializer.
    """
    many_related_field_class = IdManyRelatedField
    field_name_suffix = '_id'

    def bind(self, field_name, parent):
        """
        Called when the field is bound to the serializer.
        Changes the source  so that the original field name is used (removes
        the _id suffix).
        """
        if field_name:
            self.source = field_name[:-len(self.field_name_suffix)]
        super(IdPrimaryKeyRelatedField, self).bind(field_name, parent)


class AppendIdSerializerMixin(object):
    '''
    Append '_id' to FK field names
    https://gist.github.com/ostcar/eb78515a41ab41d1755b
    '''
    serializer_related_field = IdPrimaryKeyRelatedField

    def get_fields(self):
        fields = super(AppendIdSerializerMixin, self).get_fields()
        new_fields = type(fields)()
        for field_name, field in fields.items():
            if getattr(field, 'field_name_suffix', None):
                field_name += field.field_name_suffix
            new_fields[field_name] = field
        return new_fields


class MyModelSerializer(AppendIdSerializerMixin, serializers.ModelSerializer):

    class Meta:
        model = MyModel

class MyModelViewSet(viewsets.ModelViewSet):

    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

Upvotes: 11

Guillaume Thomas
Guillaume Thomas

Reputation: 2310

It appears to be rather complicated since it's not something DRF allows to configure. But like always, you can override things.

Everything seems to happens in the model_meta.py file. In this file, you can replace

forward_relations[field.name] = RelationInfo(

by

forward_relations[field.name + '_id'] = RelationInfo(

Careful, you need to do it twice in that function.

Once you've done that, you have still work to do as the ModelSeralizer depends on the real model_meta. It appears you need to replace those three lines:

Then, you need to implement a MyModelSerializer which overrides ModelSerializer and the three methods: create, get_fields, get_unique_together_validators. I tested it on GET requests and it works.

As you can see, it's a significant amount of code rewriting which implies difficulties for maintaining upgrades. Then, I would strongly recommend to think twice before doing so. In the mean time, you can still open an issue on the DRF project for making it more configurable (and maintainable).

Upvotes: 0

Hamed Rostami
Hamed Rostami

Reputation: 1678

ok matt simply add that parameter to your model class:

thing = models.ForeignKey(Thing, related_name='thing_id')

Upvotes: 0

Related Questions