Django ORM 1. 创建模型(Model)

发布于:2025-06-28 ⋅ 阅读:(9) ⋅ 点赞:(0)

1. ORM介绍

什么是ORM?

ORM,全称 Object-Relational Mapping(对象关系映射),一种通过对象操作数据库的技术。

它的核心思想是:我们不直接写 SQL,而是用 Python 对象(类/实例)来操作数据库表和记录。

ORM 就像一个“翻译官”,帮我们把 Python 代码翻译成数据库能听懂的 SQL 命令。

 为什么使用ORM?

Django 中的 ORM 提供了一个高层次、抽象化的接口来操作数据库,它的优势主要包括:

优势 说明
🧠 简单易用 用 Python 操作数据库,无需写 SQL
🛡️ 安全性高 ORM 自动防止 SQL 注入
🛠️ 易于维护 数据模型统一管理,字段变化只需改一处
🔄 数据库无关 可切换 MySQL、PostgreSQL、SQLite 等
🔍 强大查询 提供丰富的链式查询、聚合、子查询等功能

Django中的ORM 

在 Django 中,ORM 是与数据库打交道的核心组件,所有数据库操作的起点都是“模型(Model)”

  • 每一个模型(Python类)对应数据库中的一张

  • 每一个模型的字段(类属性)对应表中的一

  • 每一个模型的实例对象代表表中的一条记录(row)

 ORM 与传统 SQL 的对比:

操作 SQL 写法 Django ORM 写法
查询全部 SELECT * FROM product; Product.objects.all()
插入数据 INSERT INTO product ... Product.objects.create(...)
更新数据 UPDATE product SET ... product.price = 100; product.save()
删除数据 DELETE FROM product ... product.delete()

不用关心连接、游标、关闭这些底层细节,ORM 都帮我们处理好了 。

2. 创建Django项目

使用PyCharm Professional版本能非常便捷的创建django项目:

  • 打开 PyCharm,点击 File > New Project

  • 左侧选择 Django

  • 选择项目路径,例如:~/Documents/DjangoDemo

  • 创建新虚拟环境,选择Python version

  • 点击Advanced settings > 填写Application name如web

Django项目目录结构 

DjangoDemo/
├── DjangoDemo/        ← 项目配置目录(同名目录)
│   ├── __init__.py         ← Python 包初始化文件
│   ├── settings.py         ← 项目的全局配置文件
│   ├── urls.py             ← 全局 URL 路由配置
│   ├── asgi.py             ← 异步部署入口(ASGI 服务器用)
│   └── wsgi.py             ← 同步部署入口(WSGI 服务器用)
├── web/              ← 你创建的 Django 应用(业务模块)
│   ├── __init__.py         ← Python 包初始化文件
│   ├── admin.py            ← 注册模型到后台管理站点
│   ├── apps.py             ← 应用配置类,系统自动识别用
│   ├── migrations/         ← 数据迁移文件夹(记录模型变更)
│   │   └── __init__.py         ← 迁移包初始化
│   ├── models.py           ← 数据模型定义(ORM 相关内容)
│   ├── tests.py            ← 单元测试写在这里
│   └── views.py            ← 视图函数写在这里(处理请求与返回响应)
├── manage.py          ← 项目管理脚本,统一入口
└── db.sqlite3         ← 默认数据库文件(开发环境使用)
 

3. 创建 Django 模型(Model)

什么是模型(Model)?

在 Django 中,模型(Model)就是一个 Python 类,用于定义要存储在数据库中的数据结构。

每一个模型类会被 Django 自动映射为数据库中的一张表,而模型的字段(类属性)就是表中的列(字段)。

写一个模型类 = 定义一张数据库表
添加一个模型字段 = 增加一列字段

项目配置为使用 MySQL 数据库

Django 默认使用 SQLite —— 适合开发,不适合上线。为了保证本地开发环境与线上生产环境一致,避免迁移踩坑,建议学习 ORM 的同时,从一开始就配置 MySQL。

安装 MySQL 驱动

MySql驱动推荐使用 PyMySQL纯 Python 写的,跨平台更好安装,适合初学者

打开Pycharm下方的Terminal(会自动进入python虚拟环境),输入以下命令:

pip install pymysql

在项目启动时替代 MySQLdb 

在你的项目主配置模块下(例如 DjangoDemo/DjangoDemo/__init__.py),添加以下代码:

import pymysql

pymysql.install_as_MySQLdb()

修改 settings.py 中数据库配置 

在项目的settings文件,如DjangoDemo/DjangoDemo/settings.py中修改DATABASES配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 数据库引擎
        'NAME': 'django_demo',          # 数据库
        'USER': 'root',                 # 用户名
        'PASSWORD': 'yourpassword',     # 密码
        'HOST': '127.0.0.1',            # 数据库主机地址
        'PORT': '3306',                 # 数据库端口
        'OPTIONS': {
            'charset': 'utf8mb4',       # 使用支持 Emoji 的字符集
        }
    }
}

确保数据库已创建 

在 MySQL 中创建数据库:

CREATE DATABASE IF NOT EXISTS django_demo CHARSET utf8mb4;

创建一个模型类 

假设我们要开发一个电商网站,需要保存商品信息。在web/models.py文件中添加一个Product类:

from django.db import models

# Create your models here.
class Product(models.Model):
    name = models.CharField(max_length=100, verbose_name="商品名称") # 字符串字段,限制最大长度为 100
    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格") # 精确的金额字段,最大 10 位数,小数点后保留 2 位
    stock = models.IntegerField(default=0, verbose_name="库存数量") # 库存数量,整型,默认值为 0
    is_active = models.BooleanField(default=True, verbose_name="是否上架") # 是否上架,布尔类型
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") # 创建时间,auto_now_add=True 表示创建时自动填充
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间") # 更新时间,auto_now=True 表示每次保存都会自动更新

    # 在 Django 模型中,如果不写 __str__ 方法,显示出来的是一堆没有意义的“对象地址”
    # 加了 __str__ 方法后,增加了信息量,变成更友好的人类可读格式
    def __str__(self):
        return self.name

    class Meta:
        db_table = 'product'                      # 自定义表名,默认表名是 web_product,现在指定为 product
        ordering = ['-created_at']                # 默认按创建时间降序排列,['-created_at'] 表示按 created_at 字段降序排列(最新的在前)
        verbose_name = "商品"                      # 后台显示的单数名称,在 Django admin 中显示为“商品”
        verbose_name_plural = "商品列表"           # 后台显示的复数名称,在 Django admin 列表页显示为“商品列表”

应用模型到数据库

模型写完了,还需要两个步骤:

第一步:创建迁移文件(Django 记录你定义的表结构)

打开Pycharm下方的Terminal,确保当前工作目录在项目根目录,输入以下命令:

python manage.py makemigrations

第二步:执行迁移,真正创建表结构

python manage.py migrate

成功后,Django 会在数据库中创建一张叫 product 的表。如何验证模型是否生效?可以进入 Django shell 测试:

python manage.py shell
from web.models import Product

# 创建一个商品
p = Product(name='苹果手机', price=5999.99, stock=100)
p.save()

# 查询商品
Product.objects.all()

 4. 模型(Model)字段解析

字段类型 

字符串类型字段(文本类)

字段类型 SQL 类型 说明
CharField VARCHAR(n)

可控长度的短文本,必须指定 max_length。例如:用户名、标题等

username = models.CharField(max_length=150)

TextField LONGTEXT

不限长度的大文本,适合存储正文、评论等长内容。例如:文章内容、评论内容

content = models.TextField()

SlugField VARCHAR(n)

URL 友好的短字符串,仅允许英文字母、数字、连字符。例如:博客文章的 URL slug

slug = models.SlugField(max_length=100)

EmailField VARCHAR(n)

自动校验格式的 Email 字符串。例如:用户注册邮箱

email = models.EmailField(max_length=255)

URLField VARCHAR(n)

自动校验格式的 URL 字符串。例如:个人主页链接

website = models.URLField()

UUIDField CHAR(32)

存储 UUID 值,常用于唯一标识,如主键。例如:订单编号、用户唯一ID

uuid = models.UUIDField(default=uuid.uuid4, editable=False)

FilePathField VARCHAR(n)

文件路径字段,从指定目录选择文件。例如:静态日志路径选择

log_file = models.FilePathField(path="/var/logs/")

数值类型字段 

字段类型 SQL 类型 说明
IntegerField INT

整数类型(存储范围:-2147483648 到 2147483647),适合年龄、数量等常规场景。例如:用户年龄

age = models.IntegerField()

SmallIntegerField SMALLINT

小范围整数(存储范围:-32768 到 32767,节省存储空间)。例如:积分等级、等级标识

level = models.SmallIntegerField()

PositiveIntegerField INT UNSIGNED

非负整数,不允许为负。例如:商品库存数量

stock = models.PositiveIntegerField()

PositiveSmallIntegerField SMALLINT UNSIGNED

小范围非负整数。例如:星级评分(1~5)

rating = models.PositiveSmallIntegerField()

BigIntegerField BIGINT

适合较大的整数(存储范围:-9223372036854775808 到 9223372036854775807),如访问量、金额(单位:分)。例如:浏览量统计

views = models.BigIntegerField()

FloatField FLOAT

存储浮点数,精度一般。例如:商品折扣、体重

discount = models.FloatField()

DecimalField DECIMAL(m, d)

高精度小数,适合金额。需指定 max_digitsdecimal_places。例如:订单金额

price = models.DecimalField(max_digits=10, decimal_places=2)

布尔类型字段 

字段类型 SQL 类型 说明
BooleanField TINYINT(1) 布尔值字段,只允许 True/False。例如:是否启用账号is_active = models.BooleanField(default=True)
NullBooleanField(已废弃) TINYINT(1) 可为 True/False/NULL,推荐改用 BooleanField(null=True)。例如:是否已验证,允许空值(旧代码中使用)

时间/日期类型字段 

字段类型 SQL 类型 说明
DateField DATE

日期字段,存储年月日,不含时间。例如:用户生日

birth_date = models.DateField(null=True)

TimeField TIME

时间字段,仅包含时分秒。例如:商店营业时间

open_time = models.TimeField(null=True)

DateTimeField DATETIME

日期+时间,常用于记录时间戳。例如:创建时间、更新时间

created_at = models.DateTimeField(auto_now_add=True)

DurationField BIGINT(秒)

表示时间间隔(Python timedelta 对象)。例如:视频播放时长

duration = models.DurationField()

关系字段(模型之间的关系) 

字段类型 SQL 类型 说明
ForeignKey INT + 外键 一对多关系,当前模型是“多”那一方。例如:一本书属于一个作者author = models.ForeignKey(Author, on_delete=models.CASCADE)
OneToOneField INT + 唯一 一对一关系,每个对象都唯一绑定另一个对象。例如:一个用户对应一个身份证信息id_card = models.OneToOneField(IDCard, on_delete=models.CASCADE)
ManyToManyField 中间表 多对多关系,自动生成中间表。例如:学生选课,一个学生可选多门课courses = models.ManyToManyField(Course)

文件和图片上传字段 

字段类型 SQL 类型 说明
FileField VARCHAR(100) 用于上传文件,必须设置 upload_to。例如:合同上传、附件提交attachment = models.FileField(upload_to='uploads/')
ImageField VARCHAR(100) 上传图片,继承自 FileField,需安装 Pillow 库。例如:用户头像、商品主图avatar = models.ImageField(upload_to='avatars/')

特殊类型字段 

字段类型 SQL 类型 说明
GenericIPAddressField VARCHAR(39) 存储 IP 地址(IPv4 或 IPv6)。例如:登录 IP 记录ip_address = models.GenericIPAddressField()
JSONField JSON(MySQL 5.7+) 存储结构化 JSON 数据。支持字典、列表等对象。例如:动态配置项、表单结构settings = models.JSONField(default=dict)

字段参数

通用字段参数

适用于大多数字段类型(如 CharFieldIntegerFieldBooleanFieldDateTimeField 等),可用于控制数据库行为、表单验证、管理后台展示等。

参数名 说明 使用场景
null 允许数据库字段为 NULL(空),默认为False 比如“用户地址”、“头像”不一定填写,设置 null=True
blank 允许表单验证时为空(用于 admin 和表单),默认为False “备注”、“个人简介”这类字段,设置 blank=True
default 设置默认值
若字段未设置 default且未设置null=True,则必须显式赋值,否则保存时会报错
若设置了 default,即使未设置null=True 也能自动填充默认值
比如 is_active = models.BooleanField(default=True),用户默认启用
choices choices 是一个二元组列表或元组,格式为 (数据库存储值, 人类可读值),用于限制该字段的可选值
多数字段支持(特别是字符串和整数)
设置选项元组(或枚举)性别字段:choices=[('M', '男'), ('F', '女')]
unique 设置字段唯一性约束 用户名、邮箱、身份证号等需唯一,设为 unique=True
db_index 为字段建立数据库索引,加快查询 高频查询字段如手机号、订单号建议加索引
editable 设置字段是否可在 admin 界面或表单中编辑 created_at = models.DateTimeField(auto_now_add=True, editable=False)
verbose_name 设置后台显示的字段名称 改善 admin 可读性,如 email = models.EmailField(verbose_name="电子邮箱")
help_text 设置后台或表单中的提示信息 help_text="请输入真实姓名,最多50个字符"
validators 添加自定义验证器函数或类 限制范围、匹配格式,如电话号码、价格范围验证
error_messages 自定义错误提示 表单验证失败时,提示更友好
db_column 自定义字段在数据库中的列名 数据库字段名需要特殊命名时使用,如旧数据库兼容
primary_key 设置字段为主键 自定义主键如 UUID,一般使用 id = models.UUIDField(primary_key=True)

字符串字段专用

参数 适用字段 说明 使用场景
max_length CharField, EmailField, SlugField 设置最大字符数 用户名最多20字符,邮箱最多100字符等

数值字段专用

参数名 适用字段 说明 使用场景示例
max_digits DecimalField 指定数值总共最多可以有多少位(包括整数位和小数位) 商品价格字段,最大支持 99999999.99max_digits=10
decimal_places DecimalField 指定小数点后保留几位 金额统一保留两位 → decimal_places=2
auto_created 所有数值字段(自动生成时) 通常由 ORM 内部使用,用于自动生成字段标记(开发中无需设置) Django 自动生成 through 模型中的字段会包含该属性(不建议手动设置)

时间日期字段专用 

参数 适用字段 说明 使用场景
auto_now_add DateTimeField, DateField 创建记录时自动填当前时间 created_at 创建时间戳
auto_now DateTimeField, DateField 每次保存自动更新时间 updated_at 更新时间戳

关系字段专用

参数名 适用字段 说明 使用场景(开发示例)
to 所有关系字段 要关联的目标模型,可以是模型类或 'app.ModelName' 字符串形式 ForeignKey('auth.User') 表示关联用户表,跨 app 时常用
on_delete ForeignKey, OneToOneField 被关联对象删除时的处理行为,必须设置 删除用户时是否同时删除其文章,可用 CASCADESET_NULL 等策略
related_name 所有关系字段 反向查询的管理器名 设置为 articles,可用 user.articles.all() 获取该用户文章
related_query_name 所有关系字段 反向查询中的过滤名(用于 .filter() 允许使用 User.objects.filter(articles__title__icontains='Python')
limit_choices_to 所有关系字段 限制外键选择范围,可传 dictQ 对象 仅允许选择 is_active=True 的用户作为客服负责人
to_field 所有关系字段 关联到目标模型的哪个字段,默认是主键 如用用户名建立关联:to_field='username'
db_constraint 所有关系字段 是否在数据库中添加外键约束 某些场景希望逻辑关联但不加数据库约束,如异步同步外部数据
swappable 所有关系字段 是否允许替换模型引用,常用于 AUTH_USER_MODEL 用于 ForeignKey(settings.AUTH_USER_MODEL, swappable=True)
symmetrical ManyToManyField(自关联时) 自关联是否为对称关系,默认是 True 朋友关系:A加了B,B也自动加了A;关注则设置为 False
through ManyToManyField 指定中间表模型以扩展多对多关系 学生选课记录需要额外存储成绩、时间等中间字段
through_fields ManyToManyField 指定中间模型中的源字段和目标字段 中间模型字段不是默认命名时需要指定,如 ('student', 'course')

附:on_delete 常用选项 

选项 含义 应用场景
models.CASCADE 级联删除:删除主对象时连带删除当前对象 订单删除 → 同时删除订单项
models.PROTECT 拒绝删除主对象(抛出 ProtectedError 用户有发布记录,不允许直接删用户
models.SET_NULL 置为 NULL(需要设置 null=True 文章作者删除后,保留文章但作者字段为空
models.SET_DEFAULT 设为默认值(需设置 default= 作者删了 → 改为匿名用户
models.SET(...) 设置为指定值或函数返回值 on_delete=models.SET(get_deleted_user)
models.DO_NOTHING 什么都不做(数据库层面可能报错) 不推荐使用

文件上传字段专用 

适用于FileField, ImageField字段

参数 说明 使用场景
upload_to 设置上传路径(字符串或函数) upload_to="uploads/%Y/%m/%d":按日期分目录存储上传图像
storage 设置自定义文件存储类 使用阿里云 OSS、AWS S3 等云存储时使用

模型字段小技巧与注意事项 

技巧 说明 代码示例
1. 区分 null=Trueblank=True null=True 控制数据库层面是否允许存储 NULL。
blank=True 控制 Django 表单层面是否允许空值。
开发中,可选字段通常两个都设置。
 
notes = models.TextField(null=True, blank=True)

2. 自动时间字段推荐用法

auto_now_add=True 用于记录创建时间,只会设置一次。

auto_now=True 用于记录更新时间,每次保存都会更新。
两者不要写在同一个字段上。

created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
3. 高频查询字段加索引 使用 db_index=True,能显著提升过滤、查询效率。
适合手机号、邮箱、订单号等高频查询字段。

phone = models.CharField(max_length=20, db_index=True)
4. 使用 choices 枚举提升可维护性 Django 3.x+ 支持 TextChoicesIntegerChoices,方便定义枚举。
枚举值和显示文字分离,代码更清晰,方便后台表单选择。
 
from django.db import models

class Status(models.TextChoices):
    PENDING = 'P', '待处理'
    PAID = 'D', '已支付'

status = models.CharField(max_length=1, choices=Status.choices, default=Status.PENDING)
5. 布尔字段不要用 null=True

布尔字段有三态(True、False、NULL)容易引起逻辑错误。

推荐使用 default=True/False 避免空值。

 
is_active = models.BooleanField(default=True)
6. upload_to 支持函数实现动态路径 upload_to 可以传入函数,动态生成路径(按用户ID、时间分目录等)。  
def user_directory_path(instance, filename):
    return f'user_{instance.user.id}/{filename}'

avatar = models.ImageField(upload_to=user_directory_path)
7. 避免在模型中使用可变默认参数

字典、列表等可变对象作为默认值会被所有实例共享,导致数据混乱。

推荐使用函数返回默认值。

 
def default_settings():
    return {}

settings = models.JSONField(default=default_settings)
8. 使用 unique_togetherUniqueConstraint 实现复合唯一 复合唯一保证多个字段组合唯一,防止重复数据。  
class Meta:
    unique_together = [('user', 'date')]
9. 自定义 verbose_namehelp_text 改善后台体验 自定义 verbose_namehelp_text 优化后台体验  
email = models.EmailField(verbose_name='电子邮箱', help_text='请输入有效邮箱地址')
10. 使用 validators 做自定义验证 自定义或内置验证器可以限制格式、范围等,增强数据准确性。  
from django.core.validators import RegexValidator

phone_validator = RegexValidator(r'^\d{10}$', '手机号格式错误')

phone = models.CharField(validators=[phone_validator], max_length=10)

5. 模型(Model)创建示例

下面是一个完整的博客系统模型创建示例,包含各种常用字段类型和关系字段:

from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.urls import reverse

class Category(models.Model):
    """
    博客分类模型
    """
    name = models.CharField('分类名称', max_length=100)
    created = models.DateTimeField('创建时间', auto_now_add=True)
    
    class Meta:
        db_table = 'category'
        verbose_name = '分类'
        verbose_name_plural = verbose_name
        ordering = ['-created']
    
    def __str__(self):
        return self.name

class Tag(models.Model):
    """
    博客标签模型
    """
    name = models.CharField('标签名称', max_length=100)
    created = models.DateTimeField('创建时间', auto_now_add=True)
    
    class Meta:
        db_table = 'tag'
        verbose_name = '标签'
        verbose_name_plural = verbose_name
    
    def __str__(self):
        return self.name

class Post(models.Model):
    """
    博客文章模型
    """
    # 文章状态选项
    STATUS_CHOICES = (
        ('draft', '草稿'),
        ('published', '已发布'),
    )
    
    # 基础字段
    title = models.CharField('标题', max_length=200)
    slug = models.SlugField('URL别名', max_length=200, unique_for_date='publish')
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE, 
        related_name='blog_posts',
        verbose_name='作者'
    )
    body = models.TextField('正文内容')
    publish = models.DateTimeField('发布时间', default=timezone.now)
    created = models.DateTimeField('创建时间', auto_now_add=True)
    updated = models.DateTimeField('更新时间', auto_now=True)
    status = models.CharField(
        '文章状态', 
        max_length=10, 
        choices=STATUS_CHOICES, 
        default='draft'
    )
    
    # 关系字段
    category = models.ForeignKey(
        Category, 
        on_delete=models.CASCADE, 
        related_name='posts',
        verbose_name='分类'
    )
    tags = models.ManyToManyField(
        Tag, 
        related_name='posts',
        blank=True,
        verbose_name='标签'
    )
    
    # 数字字段
    views = models.PositiveIntegerField('浏览量', default=0)
    likes = models.PositiveIntegerField('点赞数', default=0)
    
    # 布尔字段
    is_top = models.BooleanField('置顶文章', default=False)
    is_recommend = models.BooleanField('推荐文章', default=False)
    
    # 文件字段
    cover_image = models.ImageField(
        '封面图片', 
        upload_to='posts/%Y/%m/%d/', 
        blank=True
    )
    attachment = models.FileField(
        '附件', 
        upload_to='attachments/%Y/%m/%d/', 
        blank=True
    )
    
    class Meta:
        db_table = 'post'
        verbose_name = '文章'
        verbose_name_plural = verbose_name
        ordering = ('-publish',)
        indexes = [
            models.Index(fields=['-publish']),
        ]
    
    def __str__(self):
        return self.title
    
    def get_absolute_url(self):
        return reverse('blog:post_detail', args=[
            self.publish.year,
            self.publish.month,
            self.publish.day,
            self.slug
        ])
    
    def increase_views(self):
        self.views += 1
        self.save(update_fields=['views'])

class Comment(models.Model):
    """
    文章评论模型
    """
    post = models.ForeignKey(
        Post,
        on_delete=models.CASCADE,
        related_name='comments',
        verbose_name='所属文章'
    )
    name = models.CharField('评论者', max_length=80)
    email = models.EmailField('邮箱')
    body = models.TextField('评论内容')
    created = models.DateTimeField('创建时间', auto_now_add=True)
    updated = models.DateTimeField('更新时间', auto_now=True)
    active = models.BooleanField('是否显示', default=True)
    
    # 自引用多对多关系,用于实现评论回复
    parent = models.ForeignKey(
        'self',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='replies',
        verbose_name='父评论'
    )
    
    class Meta:
        db_table = 'comment'
        verbose_name = '评论'
        verbose_name_plural = verbose_name
        ordering = ('created',)
    
    def __str__(self):
        return f'由 {self.name} 对 {self.post} 的评论'

一对多关系 (ForeignKey) 

Post 与 Category 的关系

category = models.ForeignKey(
    Category, 
    on_delete=models.CASCADE, 
    related_name='posts',
    verbose_name='分类'
)

关系解析

  • 一个分类(Category)可以对应多篇文章(Post)

  • 一篇文章(Post)只能属于一个分类(Category)

  • on_delete=models.CASCADE 表示当分类被删除时,属于该分类的所有文章也会被删除

  • related_name='posts' 允许通过分类实例访问其所有文章:category.posts.all()

SQL表现

  • 在Post表中会有一个category_id字段存储关联的Category主键

Post 与 User 的关系 

author = models.ForeignKey(
    User, 
    on_delete=models.CASCADE, 
    related_name='blog_posts',
    verbose_name='作者'
)

关系解析

  • 一个用户(User)可以写多篇文章(Post)

  • 一篇文章(Post)只能有一个作者(User)

  • related_name='blog_posts' 允许通过用户实例访问其所有文章:user.blog_posts.all()

多对多关系 (ManyToManyField) 

Post 与 Tag 的关系 

tags = models.ManyToManyField(
    Tag, 
    related_name='posts',
    blank=True,
    verbose_name='标签'
)

关系解析

  • 一篇文章(Post)可以有多个标签(Tag)

  • 一个标签(Tag)可以标记多篇文章(Post)

  • blank=True 表示文章可以不关联任何标签

  • related_name='posts' 允许通过标签实例访问关联的所有文章:tag.posts.all()

SQL表现

  • Django会自动创建一个中间表来维护这种多对多关系,格式为:模型名称_字段名称,此处生成的中间表名为:post_tags

自引用关系 (自关联) 

 Comment 的自引用关系

parent = models.ForeignKey(
    'self',
    null=True,
    blank=True,
    on_delete=models.CASCADE,
    related_name='replies',
    verbose_name='父评论'
)

关系解析

  • 一条评论(Comment)可以回复另一条评论(形成评论树)

  • null=True, blank=True 表示评论可以不回复任何其他评论(顶级评论)

  • related_name='replies' 允许通过评论实例访问其所有回复:comment.replies.all()

  • on_delete=models.CASCADE 表示当父评论被删除时,其所有回复也会被删除

反向关系查询示例 

Django 的反向查询让你可以从被关联的对象出发,反查所有引用它的对象,实现数据关系的双向访问,提高开发效率和代码可读性,是 ORM 系统中非常关键的一部分。

正向关系和反向关系

  • 正向关系:从"多"的一方找"一"的一方(如:文章找作者)

  • 反向关系:从"一"的一方找"多"的一方(如:作者找所有文章)

  • Django自动创建的反向查询名默认是 模型名小写_set,但可以用 related_name 改名

就像快递站里:

  • 正向关系是"通过快递找主人"→ 快递.主人就能找到你(这是直观的)

  • 反向关系是"通过主人找所有快递" → 主人知道他618买的几十个快递在哪,主人.快递就能找到所有快递(这就是反向关系)

示例 

用户(Author)与文章(Post) - 一对多

正向查询:从文章找作者

post = Post.objects.get(id=1)
author = post.author  # 获取这篇文章的作者

反向查询:从用户找他写的所有文章

user = User.objects.get(username='张三')
user_posts = user.blog_posts.all()  # 获取张三写的所有文章
# 如果没有定义related_name,则是 user.post_set.all()

 标签(Tag)与文章(Post) - 多对多

正向查询:从文章找所有标签

post = Post.objects.get(id=1)
tags = post.tags.all()  # 获取这篇文章的所有标签

反向查询:从标签找所有关联的文章

tag = Tag.objects.get(name='Django')
django_posts = tag.posts.all()  # 获取所有标记为Django的文章


网站公告

今日签到

点亮在社区的每一天
去签到