用途
本技能用于 Django 项目的模型设计与查询优化,目标是按「厚模型、薄视图」的思路组织代码:业务逻辑归属模型与 Manager,视图只负责编排工作流。适用于设计模型、优化查询、封装领域逻辑等场景。
核心思想:厚模型、薄视图
业务逻辑放在模型和 Manager 中,而非视图里。视图负责编排流程,模型实现领域行为。
- 好:模型方法处理业务规则、状态转换、校验。
- 坏:视图中包含领域规则的 if/else 逻辑,或计算派生值。
模型设计
围绕领域概念组织模型:
- 状态字段与枚举使用 TextChoices/IntegerChoices。
- 添加 get_absolute_url() 提供规范对象 URL。
- 包含 str() 以获得可读表示。
- 在 Meta 中设置合适 ordering 以统一默认排序。
- 为频繁过滤/排序的字段添加数据库索引。
- 用抽象基类承载共享字段(时间戳、软删除等)。
字段选择指南
- 可选文本字段用 blank=True, default=""(避免 null)。
- 可选外键用 null=True, blank=True。
- 唯一的可选字段用 null=True 以避免冲突问题。
- 灵活元数据用 JSONField(避免创建大量可选字段)。
- 根据实际数据需要设置合适的 max_length。
在模型方法中封装业务逻辑
- 状态转换:post.publish()、order.cancel()。
- 权限检查:post.is_editable_by(user)。
- 复杂计算:invoice.calculate_total()。
- 计算型只读值使用属性(property)。
- 部分更新保存时指定 update_fields。
QuerySet 模式:组合的力量
自定义 QuerySet 类使查询可复用、可链式、可测试。
- 模式:QuerySet 作为 Manager——定义带领域过滤方法的 QuerySet 子类,通过 objects = YourQuerySet.as_manager() 附加到模型,链式组合查询。
- 收益:查询逻辑可在视图、任务、管理命令中复用;链式方法表达力强、可读;易于单独测试;将查询复杂度从视图中封装出去。
- 常见 QuerySet 方法:按状态过滤;日期范围查询(近期、即将到来、已过期);按用户范围查询(owned_by、visible_to);组合查找(published_and_recent)。
查询优化:避免 N+1
- select_related():用于 ForeignKey 和 OneToOneField(生成 SQL JOIN)。
- prefetch_related():用于 ManyToManyField 和反向 ForeignKey(单独查询 + Python 拼接)。
- only():不需要整个对象时只加载特定字段。
- defer():排除不会用到的大字段(TextField、JSONField)。
- Prefetch() 对象:用过滤器和 select_related 定制预取。
高效计数与存在性检查
- 用 .exists() 替代 if queryset: 或 if len(queryset):。
- 用 .count() 替代 len(queryset.all())。
- 两者都在数据库层执行,不加载对象。
聚合与注解
- annotate():为每个对象添加计算字段(Count、Sum、Avg 等)。
- aggregate():在整个查询集上计算值。
- 用 F() 表达式做数据库层更新(views=F('views') + 1)。
- 将 annotate 与 filter 结合,表达「至少包含 N 个关联对象」。
Manager 与 QuerySet 的取舍
- QuerySet 用于可链式的查询逻辑。
- Manager 用于不返回 queryset 的模型级操作。
- Manager:类似「工厂方法」,如 User.objects.create_user()。
- QuerySet:类似「过滤器与变换」,如 Post.objects.published().recent()。
- 多数情况下应自定义 QuerySet 而非自定义 Manager。
信号:谨慎使用
信号会造成隐式耦合,使代码更难追踪,优先显式方法调用。
- 适合用信号:审计日志(跟踪模型全部变更)、缓存失效(模型变化时清缓存)、应用解耦(第三方应用需要响应你的模型)。
- 应避免用信号:应放在模型方法中的业务逻辑;与调用方紧密耦合的逻辑(直接调用函数即可);复杂工作流(改用显式服务层)。
- 经验法则:如果触发方和响应方都由你控制,就不要用信号。
迁移
- 工作流:模型变更后运行 makemigrations;应用前审查生成的迁移文件;运行 migrate 应用;迁移尽可能可逆。
- 数据迁移:用 makemigrations --empty app_name 创建;用 apps.get_model() 访问模型(不要直接导入);同时编写正向和反向操作。
- 适用场景:填充新字段、转换数据、在字段间迁移。
应避免的反模式
查询反模式:
- 遍历对象并访问关联关系时未使用 select_related()/prefetch_related()。
- 用 if queryset: 替代 .exists()。
- 用 len() 计数替代 .count()。
- 只需要特定字段时却加载整个对象。 设计反模式:
- 业务逻辑放在视图而非模型。
- 视图执行本应属于模型方法的计算。
- 同步操作中过度使用信号。
- 能用 JSONField 时却创建新模型。
- 忘记为过滤/排序字段添加索引。
集成
可与以下模式配合:
- pytest-django-patterns:基于 Factory 的模型测试。
- celery-patterns:模型上的异步操作(传 ID 而非实例)。
- django-forms:ModelForm 校验与保存。