Как сериализовать модель Django с 2 или более внешними ключами?

У меня есть две модели в 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)

Мой serializers.py на данный момент:

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__'

( это простая сериализация)

Мой 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'])

Пока что я получаю следующее {"purchased_by": 5, "purchased_course": 1 } в формате JSON.

Итак, вопрос в том, как получить все объекты курса (со всеми полями), которые зависят только от вошедшего пользователя, используя PurchasedCourseSerializer?

Что-то вроде:

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

Вы можете переопределить метод to_represention в сериализаторе, чтобы изменить поведение поля во время чтения, вот так:

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, PurchasedCourse имеет FK отношение к Course, поэтому один PurchasedCouse будет иметь только один объект Course, присоединенный к нему, следовательно, вы не можете показать список как представление.

Но, если вы сериализуете из UserSerializer, то вы можете использовать следующую реализацию (через SerializerMethodField):

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

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

У вас есть два способа иметь все атрибуты внешнего ключа в сериализованном JSON.

Первое решение

Используя функцию to_representation в вашем PurchasedCourse сериализаторе, это будет выглядеть примерно так:

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

и это в основном использует сериализаторы ваших внешних ключей для представления этих полей с их полными данными или любыми полями, которые вы укажете в этих сериализаторах.


Второе решение

Использование вложенных сериализаторов - это практически то же самое, что и первое решение, но в более неудобном виде - по крайней мере, для меня -

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

    class Meta:
        model = PurchasedCourse
        fields = '__all__'

потому что первое решение дает вам более простой и ясный способ управлять тем, какие поля вам особенно нужно, чтобы этот сериализатор возвращал.

Вы также можете использовать вложенный сериализатор. Это позволяет вам явно определить, как модели внешнего ключа должны быть сериализованы, и сохраняет логику в одном месте.

Попробуйте это:

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__"

Теперь вы можете определить сериализацию каждой модели внешнего ключа в своем собственном сериализаторе и сохранить логику организованной и поддерживаемой.

Вернуться на верх