如何在DRF中序列化含GameDetail的多对多模型并生成指定API响应
问题描述
模型结构
game表
| id | name | description |
|---|
platform表
| id | name |
|---|
game_detail表
| id | release_date |
|---|
game_platform关联表(game_detail_id为game_detail表外键)
| id | game_id | platform_id | game_detail_id |
|---|
对应models.py代码
class Game(models.Model): name = models.CharField(max_length=50) description = models.TextField(blank=True, null=True) genre = models.ForeignKey(Genre, related_name="games", on_delete=models.CASCADE) platforms = models.ManyToManyField( "Platform", related_name="games", through="GamePlatform" ) class Platform(models.Model): name = models.CharField(max_length=50) class GameDetail(models.Model): release_date = models.DateField() class GamePlatform(models.Model): game = models.ForeignKey(Game, on_delete=models.CASCADE) platform = models.ForeignKey(Platform, on_delete=models.CASCADE) game_detail = models.ForeignKey(GameDetail, on_delete=models.CASCADE) class Meta: constraints = [ models.UniqueConstraint( fields=["game", "platform"], name="unique_game_platform" ) ]
需要编写serializers.py的序列化逻辑和ModelViewSet代码,使游戏API返回如下格式的响应:
[ { "id": 33, "name": "Crysis Remastered", "description": "", "genre": { "id": 1, "name": "Fps" }, "platforms": [ { "id": 1, "name": "PS5", "game_detail": { "release_date": "2020-11-03" } }, { "id": 2, "name": "PC", "game_detail": { "release_date": "2020-11-13" } } ] } ]
解决方案
1. 编写序列化器(serializers.py)
通过嵌套序列化器处理关联数据,具体代码如下:
Genre序列化器
from rest_framework import serializers from .models import Game, Platform, GameDetail, GamePlatform, Genre class GenreSerializer(serializers.ModelSerializer): class Meta: model = Genre fields = ["id", "name"]
GameDetail序列化器
class GameDetailSerializer(serializers.ModelSerializer): class Meta: model = GameDetail fields = ["release_date"]
带GameDetail的Platform序列化器
通过SerializerMethodField从关联表中获取对应游戏的详情数据:
class PlatformWithDetailSerializer(serializers.ModelSerializer): game_detail = serializers.SerializerMethodField() class Meta: model = Platform fields = ["id", "name", "game_detail"] def get_game_detail(self, obj): # 从上下文获取当前游戏实例 game = self.context.get("game") if game: # 查询当前游戏与平台对应的关联记录 game_platform = GamePlatform.objects.filter(game=game, platform=obj).first() if game_platform: return GameDetailSerializer(game_platform.game_detail).data return None
最终Game序列化器
class GameSerializer(serializers.ModelSerializer): genre = GenreSerializer() platforms = serializers.SerializerMethodField() class Meta: model = Game fields = ["id", "name", "description", "genre", "platforms"] def get_platforms(self, obj): # 传入当前游戏实例到子序列化器的上下文 queryset = obj.platforms.all() return PlatformWithDetailSerializer(queryset, many=True, context={"game": obj}).data
2. 编写ModelViewSet(views.py)
指定序列化器并优化查询性能:
from rest_framework import viewsets from .models import Game from .serializers import GameSerializer class GameViewSet(viewsets.ModelViewSet): queryset = Game.objects.all().prefetch_related( "genre", "gameplatform_set__platform", "gameplatform_set__game_detail" ) serializer_class = GameSerializer
关键说明
- 用
SerializerMethodField处理多对多关联表中的额外字段(game_detail),默认多对多序列化器无法直接读取through表数据。 prefetch_related提前加载所有关联数据,避免N+1查询问题,提升API响应速度。- 通过上下文传递当前游戏实例,让子序列化器能准确找到对应平台的游戏详情记录。
内容的提问来源于stack exchange,提问作者Apoorv
相关产品推荐
相关产品推荐

