公租房系统(包含房源、租户、维修等多表关联场景),依然是以ModelSerializer为核心和主流 ,普通Serializer仅作为补充用于特殊场景,多表联合查询完全可以基于ModelSerializer优雅实现,无需依赖普通Serializer。
下面我们来理清多表关联场景下的序列化器选型与实现逻辑:
一、核心结论:多表联合查询 ≠ 必须用普通Serializer
公租房系统的多表关联(比如「房源-租户」一对多关联、「维修单-房源-租户」多对多/一对一关联),是DRF开发中的常见场景,ModelSerializer对此提供了完善的支持,而且相比普通Serializer,能更少地编写重复代码,更贴合Django模型的关联逻辑,因此多表场景下依然优先选择ModelSerializer ,普通Serializer仅在极少数特殊自定义场景下使用。
二、ModelSerializer实现多表联合查询序列化的3种核心方式(公租房场景示例)
首先我们先定义公租房系统的3个核心关联模型(为后续示例铺垫):
python
# app/models.py
from django.db import models
class House(models.Model):
"""房源模型(核心模型1)"""
house_no = models.CharField(max_length=20, verbose_name="房源编号", unique=True)
address = models.CharField(max_length=200, verbose_name="房源地址")
area = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="房屋面积")
status = models.SmallIntegerField(verbose_name="房源状态(0-空置,1-已出租,2-维修中)")
class Meta:
verbose_name = "房源"
verbose_name_plural = verbose_name
def __str__(self):
return self.house_no
class Tenant(models.Model):
"""租户模型(核心模型2,与房源一对多关联)"""
name = models.CharField(max_length=50, verbose_name="租户姓名")
id_card = models.CharField(max_length=18, verbose_name="身份证号")
phone = models.CharField(max_length=11, verbose_name="联系电话")
house = models.ForeignKey(House, on_delete=models.CASCADE, related_name="tenants", verbose_name="关联房源")
rent_start = models.DateField(verbose_name="租房起始日期")
rent_end = models.DateField(verbose_name="租房结束日期")
class Meta:
verbose_name = "租户"
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Repair(models.Model):
"""维修单模型(核心模型3,与房源一对一关联)"""
repair_no = models.CharField(max_length=30, verbose_name="维修单号", unique=True)
house = models.OneToOneField(House, on_delete=models.CASCADE, related_name="repair", verbose_name="关联房源")
content = models.TextField(verbose_name="维修内容")
create_time = models.DateTimeField(auto_now_add=True, verbose_name="提交时间")
handle_status = models.SmallIntegerField(verbose_name="处理状态(0-未处理,1-处理中,2-已完成)")
class Meta:
verbose_name = "维修单"
verbose_name_plural = verbose_name
def __str__(self):
return self.repair_no
基于上述关联模型,ModelSerializer可通过以下3种方式实现多表联合数据返回,覆盖公租房系统的大部分多表查询场景:
方式1:嵌套序列化(直接关联其他Model的Serializer)
这是最常用的多表序列化方式,适合需要返回「主表数据+关联子表数据」的场景(比如返回房源信息时,同时返回该房源的租户信息、维修单信息)。
实现思路:在主表的ModelSerializer中,直接嵌套关联表的ModelSerializer,利用Django模型的related_name关联数据。
示例(返回房源+关联租户+关联维修单的嵌套序列化器):
python
# app/serializers.py
from rest_framework import serializers
from .models import House, Tenant, Repair
# 1. 先定义关联表的ModelSerializer
class TenantSerializer(serializers.ModelSerializer):
class Meta:
model = Tenant
fields = ["name", "id_card", "phone", "rent_start", "rent_end"]
class RepairSerializer(serializers.ModelSerializer):
class Meta:
model = Repair
fields = ["repair_no", "content", "handle_status", "create_time"]
# 2. 定义主表House的ModelSerializer,嵌套关联表的Serializer(实现多表联合返回)
class HouseWithRelationSerializer(serializers.ModelSerializer):
# 嵌套租户序列化器(many=True 表示一对多关联,单个房源对应多个租户)
tenants = TenantSerializer(many=True, read_only=True)
# 嵌套维修单序列化器(一对一关联,无需many=True)
repair = RepairSerializer(read_only=True)
class Meta:
model = House
fields = ["house_no", "address", "area", "status", "tenants", "repair"]
对应的视图(测试多表数据返回):
python
# app/views.py
from rest_framework.viewsets import ModelViewSet
from .models import House
from .serializers import HouseWithRelationSerializer
class HouseViewSet(ModelViewSet):
queryset = House.objects.all() # DRF会自动根据related_name查询关联数据
serializer_class = HouseWithRelationSerializer
此时访问DRF接口,会返回嵌套格式的JSON数据(包含房源、租户、维修单的联合数据),完全满足前端Vue的多表数据展示需求,且全程基于ModelSerializer实现,无重复代码。
方式2:使用SerializerMethodField()(自定义多表查询逻辑)
适合关联数据需要额外处理、过滤或复杂查询的场景(比如返回房源信息时,仅返回该房源「未到期」的租户,或计算该房源的维修单处理时长)。
实现思路:通过serializers.SerializerMethodField()定义自定义字段,在get_xxx()方法中编写复杂的多表查询逻辑,返回处理后的关联数据。
示例(返回房源+未到期租户的自定义多表序列化器):
python
# app/serializers.py
from rest_framework import serializers
from django.utils import timezone
from .models import House, Tenant, Repair
class TenantSerializer(serializers.ModelSerializer):
class Meta:
model = Tenant
fields = ["name", "id_card", "phone", "rent_start", "rent_end"]
class HouseWithValidTenantSerializer(serializers.ModelSerializer):
# 定义自定义字段,用于返回未到期租户
valid_tenants = serializers.SerializerMethodField()
class Meta:
model = House
fields = ["house_no", "address", "area", "status", "valid_tenants"]
# 实现get_自定义字段名()方法,编写复杂多表查询逻辑
def get_valid_tenants(self, obj):
"""
obj:当前序列化的House实例
自定义查询:仅返回该房源下,租房结束日期晚于当前日期的未到期租户
"""
now = timezone.now().date()
# 多表过滤查询(房源→租户的关联查询)
valid_tenants = Tenant.objects.filter(house=obj, rent_end__gte=now)
# 调用租户序列化器,将查询结果序列化为JSON格式返回
return TenantSerializer(valid_tenants, many=True).data
这种方式灵活性更高,可满足公租房系统中复杂的多表查询需求(比如统计、过滤、数据格式化),且依然基于ModelSerializer,保留了其自动生成字段、自动校验的优势。
方式3:修改queryset进行select_related/prefetch_related优化(关联查询性能优化)
这不是序列化器的语法变更,而是多表查询的性能优化手段 ,配合ModelSerializer使用,适合数据量较大的场景(比如公租房系统中有上千套房源、上万条租户记录)。
实现思路:在DRF视图的queryset中,使用select_related(优化一对一、多对一关联查询)或prefetch_related(优化一对多、多对多关联查询),提前预加载关联数据,避免序列化时出现「N+1查询」的性能问题,序列化器依然使用常规的ModelSerializer或嵌套ModelSerializer。
示例(优化房源+维修单+租户的多表查询性能):
python
# app/views.py
from rest_framework.viewsets import ModelViewSet
from .models import House
from .serializers import HouseWithRelationSerializer
class HouseViewSet(ModelViewSet):
# select_related:预加载一对一关联的维修单数据(立即查询)
# prefetch_related:预加载一对多关联的租户数据(批量查询)
queryset = House.objects.all().select_related("repair").prefetch_related("tenants")
serializer_class = HouseWithRelationSerializer
这种方式不改变序列化器的编写逻辑,仅优化查询性能,是公租房系统后期迭代优化的重要手段,核心依然依赖ModelSerializer。
三、普通Serializer在公租房系统中的适用场景(仅补充,非主流)
即使是公租房系统,普通Serializer也不是多表查询的首选,仅在以下无对应Model或高度自定义数据的场景下使用:
- 租户登录请求(仅校验用户名/身份证号、密码,无对应入库Model,无需关联多表);
- 公租房数据统计报表(数据来自房源、租户、维修单3张表的聚合计算,无单一对应Model,返回自定义格式报表数据);
- 维修单提交的附加参数校验(比如上传维修图片的格式、大小校验,不涉及数据库Model字段)。
示例(公租房数据统计的普通Serializer):
python
# app/serializers.py
from rest_framework import serializers
class HouseStatisticSerializer(serializers.Serializer):
"""公租房统计报表序列化器(普通Serializer,无对应Model)"""
total_house = serializers.IntegerField(verbose_name="房源总数")
rented_house = serializers.IntegerField(verbose_name="已出租房源数")
repairing_house = serializers.IntegerField(verbose_name="维修中房源数")
total_tenant = serializers.IntegerField(verbose_name="租户总数")
overdue_tenant = serializers.IntegerField(verbose_name="租房到期未搬离租户数")
总结
- 公租房系统(多表联合查询场景)依然以
ModelSerializer为主流 ,多表查询无需大量使用普通Serializer; ModelSerializer可通过「嵌套序列化」「SerializerMethodField()」「select_related/prefetch_related优化」三种方式,满足绝大部分多表关联需求;- 普通
Serializer仅作为补充,用于无对应Model或高度自定义数据的场景(如登录校验、统计报表); - 核心优势:
ModelSerializer贴合Django模型,减少重复代码,同时支持多表关联,是公租房系统这类多模型项目的最优选择。