2.1 模型字段与 Meta 设计


文档摘要

2.1 模型字段与 Meta 设计 本节摘要:模型类是数据库表在 Python 世界的对应物,字段是列,属性是约束。本节以墨迹博客的文章模型为例,讲字段类型的选择逻辑、高频参数的真实含义、自定义管理器的价值,以及 Meta 类如何表达"表级规则"。选错字段类型是最贵的建模错误,因为它要到数据堆积之后才发作。 从业务事实出发 设计模型前先回答问题,而不是先想字段。墨迹博客要管理"文章",关于文章我们知道什么事实? 每篇有标题、正文、发布状态、创建时间 标题必填且不宜过长;正文可能很长;状态只有几种取值 创建时间由系统记录,不该手工填 把这些事实逐条翻译成字段,模型就基本成形: 字段类型的选择逻辑 Django 提供二三十种字段类型,常用的就那几类。

2.1 模型字段与 Meta 设计

本节摘要:模型类是数据库表在 Python 世界的对应物,字段是列,属性是约束。本节以墨迹博客的文章模型为例,讲字段类型的选择逻辑、高频参数的真实含义、自定义管理器的价值,以及 Meta 类如何表达"表级规则"。选错字段类型是最贵的建模错误,因为它要到数据堆积之后才发作。

从业务事实出发

设计模型前先回答问题,而不是先想字段。墨迹博客要管理"文章",关于文章我们知道什么事实?

  • 每篇有标题、正文、发布状态、创建时间
  • 标题必填且不宜过长;正文可能很长;状态只有几种取值
  • 创建时间由系统记录,不该手工填

把这些事实逐条翻译成字段,模型就基本成形:

from django.db import models from django.contrib.auth.models import User class Article(models.Model): DRAFT, PUBLISHED = 0, 1 STATUS_CHOICES = [ (DRAFT, "草稿"), (PUBLISHED, "已发布"), ] title = models.CharField("标题", max_length=200) body = models.TextField("正文") status = models.SmallIntegerField( "状态", choices=STATUS_CHOICES, default=DRAFT ) author = models.ForeignKey( User, on_delete=models.CASCADE, related_name="articles", verbose_name="作者", ) created_at = models.DateTimeField("创建时间", auto_now_add=True) updated_at = models.DateTimeField("更新时间", auto_now=True) class Meta: ordering = ["-created_at"] verbose_name = "文章" verbose_name_plural = verbose_name indexes = [models.Index(fields=["status", "-created_at"])] def __str__(self): return self.title

字段类型的选择逻辑

Django 提供二三十种字段类型,常用的就那几类。选择依据不是"名字像不像",而是三个问题:数据有没有长度上限?要不要参与数值运算?取值是不是有限集合?

业务事实 应选字段 常见误用 误用的代价
有上限的短文本 CharField 加 max_length TextField 一把梭 丧失数据库层约束,脏数据长驱直入
大段正文 TextField CharField 部分数据库直接报错或截断
有限取值 choices 配 SmallIntegerField 自由 CharField "已发布/发布/yifabu"满天飞,统计失真
金额 DecimalField 定精度 FloatField 浮点误差,对账时两分钱查一晚上
时间戳 DateTimeField 存字符串 无法按时间排序过滤,时区灾难
布尔 BooleanField SmallIntegerField 存 0/1 多一层心算,查询条件易写错

两个"自动时间"参数最容易混:auto_now_add 在首次创建时记录一次,适合"创建时间";auto_now 在每次保存时刷新,适合"更新时间"。想要用户可手工指定的时间,两个都别用,加 default 并允许为空。

💡 一个被低估的习惯:给每个字段写第一个位置参数(人类可读名)。Admin 后台、表单标签、第 9 章测试的报错信息都会用它,一次书写处处受益。

Meta:表级规则的归属地

字段表达"列"的规则,Meta 表达"表"的规则。上面模型用到了四处:

  • ordering:默认排序。写在这里,所有查询不指定排序时都按创建时间倒序。注意这是把双刃剑——第 3 章会讲,无序需求时它反而增加数据库排序开销,大型项目有时会刻意去掉它改在查询处显式排序。
  • verbose_name:单复数人类名,Admin 里直接受益。
  • indexes:复合索引。墨迹博客首页永远查"已发布的最新文章",索引就该贴着查询形状设计,status 在前 created_at 在后。
  • 还常用 constraints:比如 unique_together 保证"同作者同标题"不重复,比业务层判重可靠——并发场景下两个请求同时判重都通过,只有数据库约束能兜底。
class Meta: constraints = [ models.UniqueConstraint( fields=["author", "title"], name="unique_author_title" ), ]

模型方法与自定义管理器

模型上除了字段还能挂方法和"管理器"。管理器是模型通往数据库的默认入口——你每天写的 objects 就是它。默认管理器只有基础能力,为高频查询定制管理器能让语义聚拢在模型层:

class PublishedManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(status=Article.PUBLISHED) class Article(models.Model): # ... 字段同上 ... objects = models.Manager() published = PublishedManager()

此后 Article.published.all() 天然只含已发布文章,视图与模板无须再关心状态过滤——这正是第 1 章说的"过滤逻辑属于模型层"的落地。第 8 章做 Admin、第 9 章写测试时,这个入口会被反复复用。

__str__ 方法也值得一题:它决定模型实例在 Admin 下拉框、日志、调试输出里的显示名。不写的话,你看到的将是一串"Article object (3)",排查问题相当难受。

最后说说空值与缺省的取舍,这是字段设计里被问得最多的细节。null 参数控制数据库层面可否为空,blank 控制表单校验可否留空,两者相互独立——最常见的组合误区是对字符串字段随手写上 null=True,而在关系型数据库的约定里,字符列的"没有值"应该用空字符串表达,null 留给日期、外键这类确实需要"尚未确定"语义的字段。墨迹博客的实践是:标题正文一律不许 null,发布时间允许 null 表达"尚未发布"。这类约定单个看无关痛痒,成千上万行数据之后再统一,就是一次伤筋动骨的迁移——又是那句话,建模期的一切决定都伴随项目终身。

本节要点回顾

  • 字段类型回答三个问题:长度上限、数值运算、有限取值,而不是名字相似度。
  • auto_now_add 与 auto_now 分别对应创建与更新时间,语义不可互换。
  • Meta 是表级规则的家:排序、索引、唯一约束写在这里,比散在视图里可靠。
  • 定制管理器把查询语义收进模型层,是后面多章复用的基础设施。

单个模型的骨架立起来了,下一节处理模型与模型之间的关系——外键、多对多,以及删数据时的连锁反应。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U