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.