
Django ORM、请求生命周期与动态路由本章将三部分知识串联起来先用 Django ORM 定义和操作数据库表再理解一次请求在 Django 中如何流转最后学习通过动态路由接收 URL 参数并使用反向解析生成地址。这些能力共同构成 Django 处理业务请求的基础。一、ORM用 Python 模型描述数据表ORMObject-Relational Mapping对象关系映射将关系型数据库的表、字段和记录分别映射为 Python 的模型类、模型字段和实例对象。开发者通过模型 API 操作数据Django 再生成参数化 SQL 并交给数据库执行。关系型数据库Django ORM数据表继承models.Model的类数据表字段模型字段如CharField、IntegerField一行记录模型实例数据库操作objects管理器和QuerySet定义模型模型通常定义在应用的models.py中。字段类型应贴合数据语义例如年龄应使用整数类型而不是字符串类型。# user/models.pyfromdjango.dbimportmodelsclassStudent(models.Model):namemodels.CharField(max_length32)agemodels.PositiveIntegerField()gendermodels.CharField(max_length16,default)classMeta:db_tablestudentordering[id]def__str__(self):returnf{self.name}({self.age})代码说明若未设置db_tableDjango 默认使用应用名_模型名作为表名显式指定表名后应避免在后期随意修改以免产生不必要的迁移。模型未声明主键时Django 会自动添加id主键。字段约束与默认值模型字段既描述数据库结构也提供数据校验信息。null和blank的作用范围不同null控制数据库是否允许NULLblank控制 Django 表单和模型校验是否允许空值。classProfile(models.Model):nicknamemodels.CharField(max_length32,uniqueTrue)phonemodels.CharField(max_length20,blankTrue,default)birthdaymodels.DateField(nullTrue,blankTrue)is_activemodels.BooleanField(defaultTrue)代码说明对于CharField、TextField等字符串字段通常用空字符串表示缺失值常见搭配是blankTrue, default对于日期、数字等没有合理“空字符串”含义的字段常用nullTrue, blankTrue。uniqueTrue会创建数据库唯一约束不只是表单层面的提示。通过迁移更新表结构定义或修改模型后必须执行迁移。迁移文件记录的是表结构变更历史应与代码一同提交到版本控制。# 根据 models.py 的变化生成迁移文件python manage.py makemigrations# 将迁移应用到当前配置的数据库python manage.py migrate# 查看迁移状态python manage.py showmigrations代码说明makemigrations只生成迁移计划不会修改数据库migrate才会真正创建或修改表。向已有数据表增加非空字段时Django 会要求提供默认值因为数据库需要为历史记录补齐新字段值。应优先在模型中设计合理的default或允许nullTrue而不是随意输入一次性默认值。二、在 Django 环境中操作模型操作模型不一定只能写在视图函数中。最常用的调试入口是 Django Shell它会自动加载项目设置和应用注册信息。python manage.py shell# 在 Django Shell 中执行fromuser.modelsimportStudent studentStudent.objects.create(namedream,age18)print(student.id)代码说明视图函数适合处理真实请求manage.py shell适合临时调试、查询和维护数据。不要把一次性数据修复逻辑长期留在视图中正式的数据迁移应使用迁移文件或管理命令。在独立脚本中初始化 Django如果确实需要从独立 Python 脚本访问模型必须先设置DJANGO_SETTINGS_MODULE并调用django.setup()。模型导入应在初始化之后进行。# scripts/import_students.pyimportos os.environ.setdefault(DJANGO_SETTINGS_MODULE,demo01.settings)importdjango django.setup()fromuser.modelsimportStudent Student.objects.get_or_create(namedream,defaults{age:18})代码说明该脚本只初始化 Django 的配置和应用注册不会启动开发服务器也不会让浏览器路由可访问。将demo01.settings替换为实际项目的设置模块路径。三、ORM 的新增、查询、修改与删除Model.objects是模型默认管理器。查询操作通常返回QuerySet它是可继续组合、并且具有惰性执行特性的查询对象并不等同于 Pythonlist。当遍历、切片、转换为列表或调用特定方法时查询才会访问数据库。新增数据create()会创建并立即保存对象实例化后再调用save()则适合需要先补充或处理字段的情况。fromuser.modelsimportStudent# 方式一创建并保存studentStudent.objects.create(namedream,age18)# 方式二先创建实例再显式保存another_studentStudent(namehope,age20)another_student.save()代码说明create()返回已保存的模型实例save()默认返回None。数据库字段类型最终由数据库约束决定虽然 Django 可能对部分输入进行转换但不要依赖“把数字传给字符串字段”这类隐式行为。查询数据all()获取全部记录filter()获取零到多条匹配记录get()只适用于预期恰好一条记录的场景exclude()返回排除匹配条件后的记录。fromuser.modelsimportStudent all_studentsStudent.objects.all()adultsStudent.objects.filter(age__gte18)non_dream_studentsStudent.objects.exclude(namedream)studentStudent.objects.get(pk1)first_dreamStudent.objects.filter(namedream).first()foriteminadults:print(item.name,item.age)代码说明get()找不到数据时会抛出Student.DoesNotExist匹配多条时会抛出Student.MultipleObjectsReturnedfilter().first()在没有匹配记录时返回None。因此面向用户输入查询时通常先使用filter()或捕获get()的异常。fromdjango.httpimportHttp404fromdjango.shortcutsimportget_object_or_404defstudent_detail(request,student_id):studentget_object_or_404(Student,pkstudent_id)returnrender(request,user/student_detail.html,{student:student})代码说明在视图中查询“必须存在”的对象时get_object_or_404()能在未找到记录时返回 HTTP 404比未处理的 ORM 异常更符合 Web 请求语义。修改数据批量update()直接在数据库层执行修改单个实例后调用save()则会经过模型实例的保存流程。两种方式的适用场景不同。# 更新所有匹配记录返回受影响行数updated_countStudent.objects.filter(namedream).update(age19)# 修改单个对象并保存指定字段studentStudent.objects.get(pk1)student.namedream_2student.save(update_fields[name])代码说明update()不会调用模型实例的save()方法也不会触发基于save()的自定义逻辑和信号需要这些行为时应读取实例并调用save()。批量更新前必须确认过滤条件避免误更新整张表。删除数据QuerySet.delete()会删除所有匹配记录实例的.delete()则删除该实例。涉及外键时实际结果还会受on_delete配置影响。# 删除所有名为 dream 的记录Student.objects.filter(namedream).delete()# 删除单条记录studentStudent.objects.get(pk3)student.delete()代码说明filter(...).delete()是批量操作条件过宽会删除多条数据。生产环境中的删除操作通常需要权限校验、确认步骤和审计记录重要数据更适合采用软删除策略。四、一次请求在 Django 中的生命周期当用户在浏览器中访问一个 Django 地址时请求大致经历以下过程浏览器通过 DNS 等机制解析域名并向目标主机和端口建立连接、发送 HTTP 请求。Web 服务器或应用服务器例如 Gunicorn、uWSGI、Uvicorn接收请求并通过 WSGI 或 ASGI 接口交给 Django。Django 创建HttpRequest对象请求依次经过中间件的请求阶段。URLconf 根据路径解析到对应视图函数或视图类。视图读取请求数据、调用模型或其他服务返回HttpResponse、模板渲染结果、重定向或异常。响应依次经过中间件的响应阶段若发生异常也可能进入异常处理链路。服务器将响应转换为 HTTP 报文返回给浏览器浏览器再解析并渲染 HTML、CSS、JavaScript 等资源。浏览器 - Web / 应用服务器 - WSGI 或 ASGI - Django 中间件请求阶段 - URL 路由 - 视图与业务逻辑 - 模板 / ORM / 其他服务 - Django 中间件响应阶段 - Web / 应用服务器 - 浏览器渲染说明开发环境的runserver使用 Django 内置开发服务器适合学习和调试生产环境通常由专业服务器处理并发、进程管理、TLS 和静态文件。中间件不是简单的“保安”它是一套包裹请求响应过程的钩子机制常用于会话、认证、CSRF、防护、日志与跨域等横切关注点。五、动态路由与路径参数路径参数用于把资源标识直接放到 URL 中例如/students/18/表示编号为 18 的学生。现代 Django 优先使用path()的转换器只有需要复杂匹配规则时再使用re_path()。使用 path() 命名参数# demo01/urls.pyfromdjango.urlsimportpathfromuserimportviews urlpatterns[path(students/int:student_id/,views.student_detail,namestudent-detail),path(pages/int:page/,views.page,namepage),]# user/views.pyfromdjango.httpimportHttpResponsedefpage(request,page):returnHttpResponse(f当前是第{page}页)代码说明int:page会匹配整数并将其转换为 Pythonint再以关键字参数传入视图。可选转换器还包括str、slug、uuid和path。参数名称必须与视图函数形参一致除非视图通过**kwargs接收。使用 re_path() 的命名与非命名分组正则路由可使用位置捕获组或命名捕获组。命名分组可读性更好也更利于后续调整参数顺序因此通常优先使用。# demo01/urls.pyfromdjango.urlsimportre_pathfromuserimportviews urlpatterns[re_path(r^legacy-pages/(\d)/$,views.legacy_page),re_path(r^named-pages/(?Ppage\d)/$,views.named_page),]# user/views.pyfromdjango.httpimportHttpResponsedeflegacy_page(request,page_number):returnHttpResponse(f旧路由第{page_number}页)defnamed_page(request,page):returnHttpResponse(f命名路由第{page}页)代码说明无名捕获组按位置参数传给视图参数名可以自定义命名捕获组按关键字参数传入视图形参必须使用相同名称。不要在同一条路由中混用无名和命名捕获组以免调用约定难以理解。六、路由命名与反向解析将 URL 地址硬编码在模板或视图中会使修改路径变得困难。为路由指定name后可根据名称和参数反向生成 URL这就是路由反向解析。# demo01/urls.pyfromdjango.urlsimportpathfromuserimportviews urlpatterns[path(accounts/sign-in/,views.login,namelogin),path(students/int:student_id/,views.student_detail,namestudent-detail),]!-- 模板中使用 --ahref{% url login %}登录/aahref{% url student-detail student_idstudent.id %}查看学生/a# 视图中使用fromdjango.shortcutsimportredirectfromdjango.urlsimportreversedefafter_login(request):returnredirect(student-detail,student_id1)defanother_view(request):detail_urlreverse(student-detail,kwargs{student_id:1})returnredirect(detail_url)代码说明redirect()可以直接接收路由名称与关键字参数reverse()则返回 URL 字符串适合需要先构造地址再使用的场景。以后即使将accounts/sign-in/改为其他路径只要路由名称不变模板和视图通常无需修改。七、实践要点模型修改后坚持执行“makemigrations再migrate”的两步流程并提交迁移文件。把get()用在唯一记录场景不确定结果数量时优先使用filter()。批量update()、delete()前务必确认过滤条件与影响范围。新项目优先使用path()和类型转换器复杂规则才使用re_path()。所有跨页面跳转和模板链接优先使用命名路由与反向解析避免硬编码 URL。掌握这些规则后模型层的数据操作和视图层的路由处理就能形成稳定边界为后续学习关联关系、表单、认证和 REST API 奠定基础。