Django序列化多关联表难题:原生SQL查询场景下的实现困惑
解决Django多关联表序列化的几种可行方案
看来你在处理关联表序列化的时候卡壳了,别急,我给你几个实用的方案,都是Django REST Framework序列化器的常规操作,比原生SQL省心多了。
首先先把你给出的模型补全(假设merchant_name是普通字符字段,要是关联其他模型后面再调整):
from django.contrib.auth.models import User class UserDetail(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) mobile_number = models.IntegerField() national_id = models.CharField(max_length=30) date_created = models.DateTimeField(auto_now_add=True) address = models.CharField(max_length=250) merchant_name = models.CharField(null=True, max_length=100)
方案一:嵌套序列化器(最常用)
Django REST Framework支持嵌套序列化,完美适配一对一、一对多这类关联关系。你可以根据需要的输出结构,选择从User出发或者从UserDetail出发嵌套。
示例1:序列化User,包含关联的UserDetail
先给UserDetail写序列化器,再嵌套到User的序列化器里。建议给OneToOneField加个related_name,这样字段名更直观:
首先修改模型的OneToOneField:
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_detail')
然后写序列化器:
from rest_framework import serializers from django.contrib.auth.models import User from .models import UserDetail class UserDetailSerializer(serializers.ModelSerializer): class Meta: model = UserDetail fields = ['mobile_number', 'national_id', 'date_created', 'address', 'merchant_name'] class UserSerializer(serializers.ModelSerializer): # 直接嵌套UserDetail的序列化器,read_only=True表示只用于序列化输出 user_detail = UserDetailSerializer(read_only=True) class Meta: model = User fields = ['id', 'username', 'email', 'first_name', 'last_name', 'user_detail']
查询的时候记得用select_related优化,避免N+1查询问题:
# 一次性查询User和关联的UserDetail users = User.objects.select_related('user_detail').all() serializer = UserSerializer(users, many=True) # serializer.data就是包含关联信息的结构化数据
示例2:序列化UserDetail,包含关联的User
如果你的输出需要以UserDetail为主,反过来嵌套就行:
class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'email'] class UserDetailSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) class Meta: model = UserDetail fields = ['user', 'mobile_number', 'national_id', 'date_created', 'address', 'merchant_name']
查询同样用select_related:
user_details = UserDetail.objects.select_related('user').all() serializer = UserDetailSerializer(user_details, many=True)
方案二:适配原生SQL查询结果
如果你暂时不想放弃原生SQL,也可以用序列化器来处理SQL返回的字典数据。先把原生查询的结果转成字典列表,再用Serializer(注意不是ModelSerializer)来校验和序列化:
from django.db import connection from rest_framework import serializers # 原生SQL查询函数 def fetch_users_with_details(): with connection.cursor() as cursor: cursor.execute(""" SELECT u.id, u.username, u.email, ud.mobile_number, ud.national_id, ud.address FROM auth_user u INNER JOIN your_app_name_userdetail ud ON u.id = ud.user_id """) # 把查询结果转成字典列表 columns = [col[0] for col in cursor.description] return [dict(zip(columns, row)) for row in cursor.fetchall()] # 定义对应字段的序列化器 class UserWithDetailSerializer(serializers.Serializer): id = serializers.IntegerField() username = serializers.CharField() email = serializers.EmailField() mobile_number = serializers.IntegerField() national_id = serializers.CharField() address = serializers.CharField() # 使用方式 raw_data = fetch_users_with_details() serializer = UserWithDetailSerializer(raw_data, many=True) # 输出结构化的序列化数据 print(serializer.data)
额外提醒
- 如果
merchant_name其实是关联到Merchant模型的外键,只需要再写一个MerchantSerializer,然后嵌套到UserDetailSerializer里就行,逻辑和上面的一对一嵌套完全一致。 - 始终记得用
select_related(一对一/外键)或prefetch_related(多对多)优化查询,否则序列化大量数据时会有严重的性能问题。
内容的提问来源于stack exchange,提问作者Wendinoda Washaya
相关产品推荐
相关产品推荐

