django + drf 多表关联场景下的序列化器选型与实现逻辑

公租房系统(包含房源、租户、维修等多表关联场景),依然是以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或高度自定义数据的场景下使用:

  1. 租户登录请求(仅校验用户名/身份证号、密码,无对应入库Model,无需关联多表);
  2. 公租房数据统计报表(数据来自房源、租户、维修单3张表的聚合计算,无单一对应Model,返回自定义格式报表数据);
  3. 维修单提交的附加参数校验(比如上传维修图片的格式、大小校验,不涉及数据库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="租房到期未搬离租户数")

总结

  1. 公租房系统(多表联合查询场景)依然以ModelSerializer为主流 ,多表查询无需大量使用普通Serializer
  2. ModelSerializer可通过「嵌套序列化」「SerializerMethodField()」「select_related/prefetch_related优化」三种方式,满足绝大部分多表关联需求;
  3. 普通Serializer仅作为补充,用于无对应Model或高度自定义数据的场景(如登录校验、统计报表);
  4. 核心优势:ModelSerializer贴合Django模型,减少重复代码,同时支持多表关联,是公租房系统这类多模型项目的最优选择。
相关推荐
运维行者_5 小时前
企业无线网络监控的挑战与智能化演进趋势
大数据·运维·服务器·网络·数据库
国强_dev6 小时前
技术探讨:使用 stunnel 加密转发数据库连接时,如何获取客户端真实 IP?
数据库·网络协议·tcp/ip
@insist1236 小时前
系统规划与管理师-信息系统规划核心工作要点解析
数据库·软考·系统规划与管理师·软件水平考试·系统规划与管理工程师
超级数据查看器6 小时前
超级数据查看器 v10.0 发布
java·大数据·数据库·sqlite·安卓
数安3000天7 小时前
增量数据如何自动分类分级,避免目录“过期“?
大数据·数据库
南墙上的石头8 小时前
麒麟 V10 重装人大金仓 V8R6 踩坑实录(含 MySQL 兼容模式)
数据库·mysql
画中有画9 小时前
论向量数据库在项目中的应用
数据库
spider_xcxc9 小时前
Redis 数据库高质量实践指南(一)
运维·数据库·redis·oracle·云计算
l1t10 小时前
在linux和windows中解决duckdb 1.6dev版本输出执行计划报错问题
linux·运维·数据库·windows·duckdb
执子手 吹散苍茫茫烟波10 小时前
RC 隔离级别下 MySQL InnoDB 死锁典型案例
数据库·mysql