How to serialize Django model with 2 or more foreign keys?

I have two models in models.py:

from django.db import models
from django.contrib.auth.models import User

class Course(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField()
    cost = models.IntegerField()
    course_image = models.ImageField()

    def __str__(self):
        return self.name

class PurchasedCourse(models.Model):
    purchased_by = models.ForeignKey(User, on_delete=models.CASCADE)
    purchased_course = models.ForeignKey(Course, on_delete=models.CASCADE)
    purchased_time = models.DateField(auto_now_add=True, blank=True)
    
    def __str__(self):
        return str(self.purchased_by) + ' => ' + str(self.purchased_course)

My serializers.py so far:

from rest_framework import serializers
from .models import *

class CourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = '__all__'

class PurchasedCourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = PurchasedCourse
        fields = '__all__'

(it is just simple serializing)

My views.py:

from django.http import HttpResponse
from requests import Response
from .models import *
from .serializers import CourseSerializer, PurchasedCourseSerializer

from rest_framework import generics, viewsets
from rest_framework.authentication import BasicAuthentication, TokenAuthentication 
from rest_framework.permissions import IsAuthenticated

class CourseViewSet(viewsets.ModelViewSet):
    authentication_classes = [TokenAuthentication]
    permission_classes = (IsAuthenticated,)
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

class UserRelatedCourseViewSet(viewsets.ModelViewSet):
    queryset = PurchasedCourse.objects.all()
    serializer_class = PurchasedCourseSerializer

    def get_queryset(self):
        return PurchasedCourse.objects.filter(purchased_by__username=self.request.query_params['username'])

What I am getting so far is like { "purchased_by": 5, "purchased_course": 1 } in JSON format.

So, question is how to get all Course objects(with all fields) that depends only on logged in user using PurchasedCourseSerializer?

Something like:

[{
  "id": 1;
  "username": "testUsername";
      course_set
               [
                  {
                     "id": 2;
                     "name": "Computer Science";
                     "description": "Test description text...";
                  }
                  {
                     "id": 5;
                     "name": "History";
                     "description": "Test description text...";
                  }
               ]
}]

You can override the to_represention method in the serializer to change behavior of the field during reading, like this:

class PurchasedCourseSerializer(serializers.ModelSerializer):
    username = serializers.CharField(source="purchased_by.username")

    def to_representation(self, obj):
       self.fields['purchased_course'] = CourseSerializer(obj.purchased_course )
       return super().to_representation(obj)
    class Meta:
        model = PurchasedCourse
        fields = '__all__'

FYI, the PurchasedCourse has a FK relation to Course, so one PurchasedCouse will have only one Course object attached to it, hence you can not show the list like representation.

But, if you are serializing from UserSerializer, then you can use the following implementation (through SerializerMethodField):

class UserSerializer(...):
    courses = serializer.SerializerMethodField()

    def get_courses(self, obj):
      return CourseSerializer(Course.objects.filter(purchased_course__purchased_by=obj), many=True).data

You have basically two ways to have all attributes of a foreign key in your serialized JSON.

First solution

Using a to_representation function in your PurchasedCourse serializer, it would look something like this:

class PurchasedCourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = PurchasedCourse
        fields = "__all__"

    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep["purchased_course"] = CourseSerializer(instance.purchased_course).data
        rep["purchased_by"] = UserSerializer(instance.purchased_by).data

        return rep

and this basically uses the serializers of your foreign keys to represent those fields with their full data or whatever fields you specify in those serializers.


Second solution

Using nested serializers is basically the same as the first solution but in a more clanky way - at least for me -

class PurchasedCourseSerializer(serializers.ModelSerializer):
    purchased_course = CourseSerializer()
    purchased_by = UserSerializer()

    class Meta:
        model = PurchasedCourse
        fields = '__all__'

because the first solution gives you an easier and more clear way to manage which fields you particularly need this serializer to return.

You can also use Nested Serializer. This allows you to explicitly define how the foreign key models should be serialized, and it keeps the logic in one place.

Try this:

class CourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = "__all__"

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = "__all__"

class PurchasedCourseSerializer(serializers.ModelSerializer):
    purchased_course = CourseSerializer()
    purchased_by = UserSerializer()

    class Meta:
        model = PurchasedCourse
        fields = "__all__"

Now, you can define the serialization of each foreign key model in its own serializer, and keep the logic organized and maintainable.

Back to Top