学Django有一阵子的人,大概率会有这种感受:网上教程一抓一大把,但很多都是照着敲一遍就过去了,回头自己写查询时,filter、get、all 用得倒是挺熟,可真碰到“查出来的数据怎么变了”、“为什么这么写能跑但性能崩了”这种问题,就卡住了。这篇文章不打算做那种大而全的 ORM 手册,就围绕一个单表演示项目,把我们日常开发里最常用的 Django ORM 操作从头到尾捋一遍。核心目标是让看完的人能照着写、照着查,并且知道每一步背后的逻辑。
我会用一个用户(User)信息表作为贯穿全文的例子,逐步覆盖模型定义、数据迁移、增删改查、进阶查询、表单联动这几个环节。因为是单表实例,不涉及外键关联,反而更容易看清 ORM 本身的执行逻辑和边界行为。凡是理解上有坑、调用方式有歧义、性能上有隐患的地方,我都会明确指出来,这些基本都是实际项目里反复踩过的。
1. 为什么ORM值得认真学:它到底解决了什么问题
1.1 从原生SQL到ORM的思维转变
接触 Django 之前,不少人是先写的原生 SQL。那时候的操作方式无非是:连数据库、拼 SQL 字符串、拿游标、一行行取数据,然后手动映射成 Python 对象。这套流程本身没毛病,问题出在几个地方。
第一,拼接 SQL 容易出注入风险。第二,表结构一改,所有 SQL 都要跟着改,牵一发动全身。第三,不同数据库方言有差异,MySQL 里能跑的语句换到 PostgreSQL 可能要调整。ORM(Object-Relational Mapping,对象关系映射)就是来解决这些痛点的:把表映射成类,把行映射成实例,把列映射成属性,把 SQL 查询封装成方法调用。
放在单表演示这个场景里,ORM 最大的价值是让你完全不用关心底层 SQL 长什么样,而是以“操作 Python 对象”的方式完成数据读写。上一行代码,ORM 帮你翻译成 INSERT,下一行代码,帮你翻译成 SELECT WHERE。这不是说底层 SQL 不重要,遇到复杂查询时你仍然需要理解 SQL 才能写出高效的 ORM 调用,但在绝大多数业务场景里,ORM 的抽象已经足够好用。
我见过不少刚转 Django 的同事,遇到查询需求第一反应是 connection.cursor() 写原生 SQL。这本身没错,但如果只是单表查询,用 ORM 能少写很多样板代码,而且迁移表结构时不用翻历史 SQL。理解 ORM 的核心思想,是掌握 Django 数据操作的底色。
1.2 Django ORM在MVT架构里的位置
Django 的 MVT 模式里,M(Model)就是数据层。很多教程会把 MTV 和 MVC 对比着讲,说 MVT 里的 View 对应 MVC 里的 Controller,Template 对应 View。这种说法能帮助理解,但我更倾向于这样解释:Model 是数据的最终来源,Template 负责展示,View 是连接两者的逻辑控制点。
在一套 Django 请求流程里,请求先进入 URLconf 路由,然后转到 View 视图函数,视图里调用 ORM 查询拿到数据,把数据交给 Template 渲染,最终返回给浏览器。这个过程里,ORM 层是 View 和数据库之间的桥梁。
单表实例的学习意义就在于,它能让你在不受外键、多表关联干扰的情况下,把这条链路彻底吃透:从模型定义,到数据库迁移,到视图查询,再到模板展示。把这套流程跑顺了,之后再加 ForeignKey、ManyToManyField,不过是给这个骨架添砖加瓦。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先搭一个能跑的单表演示项目
2.1 环境准备与项目初始化
要动手跑 Demo,第一步是准备环境。Django 目前主流的版本是 4.x 系列,Python 建议用 3.8 以上。如果你电脑上之前装过老版本 Django,建议在虚拟环境里重新安装,避免全局环境的依赖冲突。我习惯用 venv 创建虚拟环境,简单直接。
bash复制python -m venv venv
source venv/bin/activate # Windows系统执行 venv\Scripts\activate
pip install django
装好之后,创建项目和 app。项目名我通常取一个与业务相关的名字,app 名一般用复数形式,这也是 Django 官方文档里的惯例。
bash复制django-admin startproject mysite
cd mysite
python manage.py startapp users
这里注意,startapp 生成的 users 目录需要手动注册到项目配置里。打开 mysite/settings.py,找到 INSTALLED_APPS,把 'users' 加进去。漏掉这一步是新手最常见的低级错误,现象是执行迁移时报“No installed app with label”之类的错误。
2.2 用模型定义“用户表”:字段选择的门道
单表实例的模型设计虽然简单,但字段类型和参数选择仍然有讲究。假设我们要做一个用户信息表,包含以下字段:姓名、年龄、邮箱、手机号、注册时间、最后登录时间、是否激活。
在 users/models.py 里写:
python复制from django.db import models
class UserInfo(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True, null=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'user_info'
ordering = ['-created_at']
几个容易忽略的细节:
CharField必须指定max_length,这是 CharField 在底层变成varchar的硬性要求。不写会报错。EmailField实际上继承自CharField,底层也是字符串,只是在表单验证和模型校验时多了邮箱格式检查。存在数据库里仍然是varchar。auto_now_add=True表示第一次创建对象时自动写入当前时间,之后更新不会改变。auto_now=True表示每次save()时自动更新为当前时间。db_table用来指定数据库里的真实表名,不写的话默认是app名_类名小写,也就是users_userinfo。项目里如果对表名有规范要求,建议显式指定。ordering是 Meta 里的一个常用选项,会直接影响不带order_by的查询结果的排序方式。这里设成按创建时间倒序,符合“最新注册的排前面”的业务习惯。
模型定义的每一个字段类型,背后都对应着数据库层面的存储策略。比如 DateTimeField 在 MySQL
