skip to Main Content

What I need

I’d like to get a single json field in the API response which lists identifiers for related objects based on two reverse foreign key relationships. Simple example what I mean by that is presented below. I’d highly prefer it to be handled on the Django REST Framework serializer level rather than having to change the model in some way, but I have very little DRF experience and I can’t for the life of me figure out how to actually do it.

Example models.py:

class Person(models.Model):

    id = models.AutoField(primary_key=True)

    first_name = models.CharField(max_length=50, blank=True, null=True)

    last_name = models.CharField(max_length=50, blank=True, null=True)

    father = models.ForeignKey(
        "self",
        related_name="children_as_father",
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
    )

    mother = models.ForeignKey(
        "self",
        related_name="children_as_mother",
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
    )

Example database data:

id first_name last_name mother father
1 Jane Smith
2 John Smith
3 Clarence Smith 1 2
4 Thomas Smith 1 2

Example serialized json I would like to get:

[
    {
        "pk": 1,
        "first_name": "Jane",
        "last_name": "Smith",
        "mother": null,
        "father": null,
        "children": [
            3,4
        ],
    },
    {
        "pk": 2,
        "first_name": "John",
        "last_name": "Smith",
        "mother": null,
        "father": null,
        "children": [
            3,4
        ],
    },
    {
        "pk": 3,
        "first_name": "Clarence",
        "last_name": "Smith",
        "mother": 1,
        "father": 2,
        "children": [],
    },
    {
        "pk": 4,
        "first_name": "Thomas",
        "last_name": "Smith",
        "mother": 1,
        "father": 2,
        "children": [],
    }
]

What I tried

That’s as far as my experiments managed to get:

serializers.py

from rest_framework.serializers import ModelSerializer, PrimaryKeyRelatedField
from .models import Person

class FamilySerializer(ModelSerializer):

    children_as_mother = PrimaryKeyRelatedField(
        many=True, read_only=True, allow_null=True
    )

    children_as_father = PrimaryKeyRelatedField(
        many=True, read_only=True, allow_null=True
    )

    class Meta:
        model = Person
        fields = [
            "pk",
            "first_name",
            "last_name",
            "mother",
            "father",
            "children_as_mother",
            "children_as_father",
        ]

views.py

from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from .models import Person
from .serializers import FamilySerializer

class FamilyViewSet(ModelViewSet):
    """
    API endpoint with family data
    """

    queryset = Person.objects.all()
    serializer_class = FamilySerializer
    permission_classes = [IsAuthenticated]

Serialized json:

[
    {
        "pk": 1,
        "first_name": "Jane",
        "last_name": "Smith",
        "mother": null,
        "father": null,
        "children_as_mother": [
            3,4
        ],
        "children_as_father": [],
    },
    {
        "pk": 2,
        "first_name": "John",
        "last_name": "Smith",
        "mother": null,
        "father": null,
        "children_as_mother": [],
        "children_as_father": [
            3,4
        ],
    },
    {
        "pk": 3,
        "first_name": "Clarence",
        "last_name": "Smith",
        "mother": 1,
        "father": 2,
        "children_as_mother": [],
        "children_as_father": [],
    },
    {
        "pk": 4,
        "first_name": "Thomas",
        "last_name": "Smith",
        "mother": 1,
        "father": 2,
        "children_as_mother": [],
        "children_as_father": [],
    }
]

As you can see compared to the first json example, it’s not quite what I want – I’d like a single json field for listing a person’s children identifiers ("children") and not two ("children_as_mother", "children_as_father").

2

Answers


  1. Chosen as BEST ANSWER

    I managed to find an acceptable solution. Here's how I modified the serializer by overriding the to_representation function:

    class FamilySerializer(ModelSerializer):
    
        children_as_mother = PrimaryKeyRelatedField(
            many=True, read_only=True, allow_null=True
        )
    
        children_as_father = PrimaryKeyRelatedField(
            many=True, read_only=True, allow_null=True
        )
    
        class Meta:
            model = Person
            fields = [
                "pk",
                "first_name",
                "last_name",
                "mother",
                "father",
                "children_as_mother",
                "children_as_father",
            ]
    
        def to_representation(self, instance):
            data = super(FamilySerializer, self).to_representation(instance)
            data["children"] = data["children_as_father"]+data["children_as_mother"]
            del data["children_as_mother"]
            del data["children_as_father"]        
            return data     
    

    Alternatively to_representation can also be overriden like so to get the same effect for my specific dataset:

        def to_representation(self, instance):
            data = super(FamilySerializer, self).to_representation(instance)
            if data["children_as_father"]:
                data["children"] = data["children_as_father"]
            elif data["children_as_mother"]:
                data["children"] = data["children_as_mother"]
            else:
                data["children"]=[]
            del data["children_as_mother"]
            del data["children_as_father"]        
            return data
    

    I'm leaving the question up for a few days in case anybody has another solution in mind, after that I'll mark this as the correct answer.


  2. class PersonSerializer(serializers.ModelSerializer):
        children = serializers.SerializerMethodField()
    
        class Meta:
            model = Person
            fields = ['id', 'first_name', 'last_name', 'mother', 'father', 'children']
    
        def get_children(self, obj):
            # Get all children of the demo
            children_as_father = obj.children_as_father.values_list('id', flat=True)
            children_as_mother = obj.children_as_mother.values_list('id', flat=True)
            return list(children_as_father) + list(children_as_mother)
    
    

    https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search