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模型,减少重复代码,同时支持多表关联,是公租房系统这类多模型项目的最优选择。
相关推荐
Mr. Cao code2 小时前
MySQL数据卷实战:持久化存储秘籍
数据库·mysql·docker·容器
小北方城市网2 小时前
微服务架构设计实战指南:从拆分到落地,构建高可用分布式系统
java·运维·数据库·分布式·python·微服务
爱喝水的鱼丶2 小时前
SAP-ABAP:SAP性能侦探:STAD事务码的深度解析与应用实战
开发语言·数据库·学习·sap·abap
五阿哥永琪2 小时前
MySQL相关的面试题 MySQL存储引擎与索引机制深度解析
数据库·mysql
Full Stack Developme2 小时前
达梦(DM8)基于 LBS(位置服务)教程
服务器·网络·数据库
小湘西2 小时前
数仓分层架构详解2:ODS、DWD、DWS
大数据·数据库·数据仓库
「、皓子~2 小时前
AI创作系列35 海狸IM桌面版:本地数据库的设计艺术
数据库·golang·毕业设计·开源软件·im·社交软件
谷哥的小弟2 小时前
SQLite MCP服务器安装以及客户端连接配置
服务器·数据库·人工智能·sqlite·大模型·源码·mcp