Django模型关系:从一对多到多对多全解析

简介: 本文详解Django模型关系:一对多(ForeignKey)及多对多(ManyToManyField)关系的定义、操作与优化技巧。同时探讨外键约束的使用场景与权衡策略。

一、一对多关系: ForeignKey

一对多是最常见的模型关系,例如 "作者 - 书籍" 场景:假设一个作者可以写多本书,但每本书只能属于一个作者。

定义关系

核心参数说明:

  • on_delete=models.CASCADE:当作者被删除时,关联的书籍也会被自动删除
  • related_name='books':定义反向查询名称,可通过author.books.all()获取作者的所有书籍
from django.db import models

class Author(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

class Book(models.Model):
    title = models.CharField(max_length=100)
    publication_date = models.DateField()
    # 外键关联Author,级联删除,反向查询名为books
    author = models.ForeignKey(
        Author, 
        on_delete=models.CASCADE, 
        related_name='books'
    )

    def __str__(self):
        return self.title

数据操作示例

创建数据

# 创建作者
author1 = Author.objects.create(first_name='J.K.', last_name='Rowling')
author2 = Author.objects.create(first_name='George', last_name='Orwell')

# 创建书籍并关联作者
book1 = Book.objects.create(
    title='Harry Potter', 
    publication_date='1997-06-26', 
    author=author1
)
book2 = Book.objects.create(
    title='1984', 
    publication_date='1949-06-08', 
    author=author2
)

查询操作

# 正向查询:通过书籍找作者
book = Book.objects.get(title='1984')
print(book.author)  # 输出: George Orwell

# 反向查询:通过作者找书籍
author = Author.objects.get(last_name='Rowling')
for book in author.books.all():
    print(book.title)  # 输出: Harry Potter

高级配置

禁用外键约束:当需要灵活管理关联关系(如允许删除存在关联数据的主表记录)时,可关闭数据库级约束

author = models.ForeignKey(
    Author, 
    on_delete=models.SET_NULL,
    related_name='books',
    db_constraint=False,  # 不创建数据库外键约束
    null=True
)

自定义数据库列名:默认会生成<ClassName>_id列,可通过db_column修改

dept_id = models.ForeignKey(
    "SystemDept",
    on_delete=models.SET_NULL,
    db_column="dept_id",  # 显式指定数据库列名
    null=True
)

二、多对多关系: ManyToManyField

多对多关系适用于 "作者 - 书籍" 的另一种场景:假设一个作者可以写多本书,一本书也可以有多个作者。

定义关系

Django 会自动创建中间表(默认名为appname_book_authors)存储关联关系,无需手动定义。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    publication_date = models.DateField()
    # 多对多关联Author
    authors = models.ManyToManyField(Author, related_name='books')

    def __str__(self):
        return self.title

数据操作示例

添加 / 移除关联

# 创建实例
author1 = Author.objects.create(name='Alice', email='alice@example.com')
author2 = Author.objects.create(name='Bob', email='bob@example.com')
book = Book.objects.create(title='Example Book', publication_date='2023-01-01')

# 添加关联
book.authors.add(author1, author2)

# 移除关联
book.authors.remove(author1)

查询操作

# 正向查询:书籍的所有作者
book = Book.objects.get(title='Example Book')
for author in book.authors.all():
    print(author.name)

# 反向查询:作者的所有书籍
author = Author.objects.get(name='Bob')
for book in author.books.all():  # related_name='books'
    print(book.title)

自定义中间表

当需要存储关联关系的额外信息(如邀请原因、加入时间)时,可自定义中间表

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(Person, related_name="invites", on_delete=models.CASCADE)
    invite_reason = models.CharField(max_length=64)  # 额外信息

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through="Membership",  # 指定中间表
        through_fields=("group", "person"),  # 关联字段
    )

三、性能优化技巧

select_related:用于一对多关系,提前加载关联对象,减少数据库查询

# 普通查询(N+1问题)
entries = Entry.objects.all()
for entry in entries:
    print(entry.blog.name)  # 每次循环都会触发新查询

# 优化后(仅1次查询)
entries = Entry.objects.select_related('blog').all()
for entry in entries:
    print(entry.blog.name)  # 使用缓存数据

批量操作:利用update()进行批量更新,避免循环操作

# 批量标记站内信为已读
SystemNotifyMessage.objects.filter(
    id__in=ids.split(",")
).update(
    read_status=True, 
    read_time=timezone.now()
)

四、关于是否使用外键约束

在实际项目中,是否使用数据库外键约束需要权衡利弊

使用外键的优势

  • 数据完整性:数据库级别的约束保证关联数据一致性
  • 开发效率:ORM 自动处理关联查询和级联操作
  • 查询便捷:支持select_related等优化方法,简化多表查询

禁用外键的场景

  • 高并发系统:外键会增加数据库锁竞争,影响写入性能
  • 分布式架构:分库分表环境下,跨库外键无法生效
  • 复杂迁移:避免循环依赖导致的迁移失败问题

折中方案:使用db_constraint=False 参数

  • 数据库层面:无外键约束,数据库不会强制校验关联数据的存在性
  • Django ORM 层面:保留逻辑关联,ORM仍将字段视为外键关系(逻辑关联),支持 ORM 查询、操作语法
特性 db_constraint=True (默认) db_constraint=False
数据库外键约束 创建,强制数据一致性 不创建
级联操作 数据库自动处理 仅由 Django ORM 处理
关联数据存在性校验 数据库强制校验 不校验(需应用层保障)
ORM 查询支持 完整支持 完整支持(逻辑外键保留)
性能影响 外键约束带来额外开销 无约束开销
适用场景 强数据一致性需求 高频写入/跨库/历史数据迁移

五、多对多关系实战

实战场景:在一个后台管理系统中,用户与角色往往是多对多关系。一个用户可以分配多个角色,一个角色也可以属于多个用户。

image-20250730171424143.png

模型定义:点击查看完整代码

class SystemUsers(BaseModel, AbstractBaseUser):
    id = models.BigAutoField(primary_key=True, db_comment="用户ID", help_text="用户ID")
    username = models.CharField(
        max_length=30, unique=True, db_comment="用户账号", help_text="用户账号"
    )
    # ...
    # 与角色多对多关系
    roles = models.ManyToManyField(
        "SystemRole",
        through="SystemUserRole",
        through_fields=("user_id", "role_id"),
        related_name="users",
    )
    # ...    

class SystemUserRole(BaseModel):
    """用户和角色关联中间表"""
    id = models.BigAutoField(primary_key=True, db_comment="id")
    user_id = models.ForeignKey(
        "SystemUsers",
        on_delete=models.CASCADE,
        db_constraint=False,
        db_column="user_id",
        db_comment="用户ID",
    )
    role_id = models.ForeignKey(
        "SystemRole",
        on_delete=models.CASCADE,
        db_constraint=False,
        db_column="role_id",
        db_comment="角色ID",
    )

    class Meta:
        managed = True
        db_table = "system_user_role"
        db_table_comment = "用户和角色关联表"
        ordering = ["-id"]

system_user_role数据库生成的中间表

image-20250730172028007.png


您正在阅读的是《Django从入门到实战》专栏!关注不迷路~

相关文章
|
4月前
|
监控 NoSQL 网络协议
Django 实时通信实战:WebSocket 与 ASGI 全解析(上)
WebSocket 是一种全双工通信协议,支持实时数据传输,适用于聊天、协作、监控等场景。ASGI 是异步 Web 标准,配合 Uvicorn 服务器和 Django Channels,可实现 Django 的 WebSocket 功能,提升实时应用性能。
218 0
|
3月前
|
缓存 监控 中间件
Django中间件自定义开发指南:从原理到实战的深度解析
Django中间件是Web应用的“交通警察”,在请求与响应过程中进行全局处理,适用于身份验证、日志记录、性能监控等功能。本文详解中间件的工作原理、开发步骤及实战案例,帮助开发者掌握自定义中间件的构建方法,提升Django应用的可维护性与扩展性。
201 0
|
3月前
|
存储 缓存 数据库
Django模型开发全解析:字段、元数据与继承的实战指南
Django模型是业务逻辑与数据库的核心桥梁,本文详解模型开发三大核心:字段类型选择、元数据配置与继承模式应用,涵盖实战技巧与常见问题解决方案,助你构建高效可维护的数据模型。
108 0
|
3月前
|
SQL 存储 数据库
Django模型查询与性能调优:告别N+1问题
本文详解Django数据库查询基础与优化技巧,涵盖QuerySet使用、关联查询(一对多/多对多)、N+1查询问题及解决方案(select_related、prefetch_related)、高级查询方法及项目实战中的数据权限控制实现。
155 0
|
4月前
|
存储 关系型数据库 MySQL
Django模型开发:模型字段、元数据与继承全方位讲解
本文将全面介绍 Django 模型的关键知识点,包括模型字段类型、映射、常用配置选项以及模型继承等高级特性,帮助开发者快速掌握模型设计与使用技巧。
127 0
|
4月前
|
缓存 JSON 应用服务中间件
Django实时通信实战:WebSocket与ASGI全解析(下)
本文将使用 Django Channels 构建一个多用户实时聊天室,并详细介绍如何在生产环境中部署 WebSocket 应用。
144 0
|
存储 开发框架 JSON
【查漏补缺】Django模型字段类型及其应用
【查漏补缺】Django模型字段类型及其应用
137 0
|
5月前
|
Linux 数据库 数据安全/隐私保护
Python web Django快速入门手册全栈版,共2590字,短小精悍
本教程涵盖Django从安装到数据库模型创建的全流程。第一章介绍Windows、Linux及macOS下虚拟环境搭建与Django安装验证;第二章讲解项目创建、迁移与运行;第三章演示应用APP创建及项目汉化;第四章说明超级用户创建与后台登录;第五章深入数据库模型设计,包括类与表的对应关系及模型创建步骤。内容精炼实用,适合快速入门Django全栈开发。
162 1
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
653 45
|
机器学习/深度学习 人工智能 算法
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
植物病害识别系统。本系统使用Python作为主要编程语言,通过收集水稻常见的四种叶片病害图片('细菌性叶枯病', '稻瘟病', '褐斑病', '稻瘟条纹病毒病')作为后面模型训练用到的数据集。然后使用TensorFlow搭建卷积神经网络算法模型,并进行多轮迭代训练,最后得到一个识别精度较高的算法模型,然后将其保存为h5格式的本地模型文件。再使用Django搭建Web网页平台操作界面,实现用户上传一张测试图片识别其名称。
455 22
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面