尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django基础入门:从MTV架构到ORM实战搭建Web应用

Django基础入门:从MTV架构到ORM实战搭建Web应用 第一次听说“Django”是在一次要给团队内部做小工具的时候。需求很简单一个网页让同事提交问题管理员能看到列表并标记处理状态。当时我的 Python 刚学完基础语法在 Flask 和 FastAPI 之间来回摇摆最后选了 Django理由非常朴素——我想要一个自带后台、自带用户认证、自带数据库迁移的系统而不是从零去拼一堆第三方库。这篇是“Django 基础入门教程”的第一篇目标读者是刚学完 Python、想往 Web 开发走的新手也适合在其他框架里绕了一圈、想换个更“成套”的框架体验一下的朋友。我会拿一个很小但完整的待办事项应用做主线从环境准备一路讲到模型、视图、模板、查询和删除每一步的“为什么这么做”也会交代清楚。你不需要任何 Django 基础只要装好 Python再带一点耐心就够了。1. Django 项目是怎么运转的先看懂 MTV 骨架1.1 从一次内部小需求说起当时的需求是要处理“同事报障”这个小流程数据模型其实非常浅一条记录、一个状态、一个提交时间。但真正写起来才发现报障系统它需要的功能一点都不少——后台要能方便地增删改查、前端要能展示列表、用户提交后最好还能有权限控制。我先试了 Flask。Flask 确实轻巧一个文件就能跑起来但一旦出现用户登录、后台管理、数据库迁移这些需求就得自己安装 Flask-Login、Flask-Admin、Alembic还要操心它们之间版本兼容的问题。后来试了 FastAPI异步性能确实好写 API 也舒服但当时团队需要的并不是纯 API而是一个有页面的管理系统FastAPI 在模板渲染和传统后台管理这块又得自己拼。Django 在这类“内部管理系统”场景里几乎是为所欲为。它自带 Admin 后台、ORM、表单处理、认证系统迁移功能也是一等公民。对比一下你就明白特性DjangoFlaskFastAPI管理后台自带 Admin需要 Flask-Admin没有官方方案ORM 与迁移自带并深度集成SQLAlchemy Alembic 组合SQLAlchemy / Tortoise用户认证自带 User 模型和权限体系第三方扩展第三方扩展学习曲线偏陡但体系完整平坦但容易“自建轮子”API 友好但场景偏窄所以我的第一个建议是如果目标明确是“做一个带页面的业务系统”Django 会帮你省掉大量重复劳动。这也是这一篇系列里反复强调的核心判断标准——先看场景再选框架。1.2 Django 的请求处理流程从浏览器到页面我第一次看 Django 文档时被 MVT 这个概念绕了一下。后来我用一个“点餐”的例子给自己讲明白了。用户访问一个 URL相当于你走进餐厅坐下。餐厅里有个服务员负责看你写了什么菜这个角色在 Django 里叫 URLconf也就是urls.py。她看清楚之后会把订单转给后厨后厨就是视图View。视图拿到订单后会去仓库拿食材——食材就是数据库里的数据拿食材的工具是模型Model。东西备齐了后厨开始装盘装盘的过程是模板渲染Template。最后服务员端着菜到顾客面前这就是 HttpResponse 响应。对应下来就是下面这条链浏览器请求 - urls.py 匹配路由 - views.py 处理逻辑 - models.py 操作数据库 - templates/*.html 渲染页面 - 返回 HttpResponseDjango 文档喜欢把这种结构叫 MVTModel、View、Template。它本质上和我们常说的 MVCModel、View、Controller是一回事只是 Django 把 Controller 的角色拆给了 URL 配置加视图函数。理解这个流程比背概念重要得多因为后面你不管写什么功能都是在这条链上加东西。还有一件事我特别想跟新手说清楚当你新建一个 Django 项目运行python manage.py runserver之后其实已经在本地跑了一个完整的 Web 服务了。它不是静态页面预览而是真的服务器程序。理解到这一点你再去看后面所有操作心里会踏实很多。2. 环境准备把 Python 和 Django 一次装对2.1 版本选择别走在 Django 支持路线图前面入门遇到的最初一坑不是写代码而是版本。很多新手一上来就装 Python 最新版然后发现某个第三方库还不支持报错报得怀疑人生。我目前的建议分两种情况。如果你用的是 Python 3.10 或 3.11直接装 Django 5.0 或 5.1 都没问题5.x 是当前主流版本官方支持力度好。如果你的服务器环境比较保守那就选 Django 4.2 LTS它是长期支持版本维护时间很长适合部署到生产环境。Python 3.12 也可以用但先确认你打算用的其他库是否跟上再决定主线版本。这里有一个判断技巧打开终端输入python --version先确认自己的解释器版本再去 Django 官网看支持的 Python 版本范围。不要盲目追求“最新”稳定组合才是关键。我自己在本地用的组合是 Python 3.11 Django 5.0跑下来的体验很稳。如果你用的 Python 比较老比如 3.8 以下那第一件事是先升级 Python 本身而不是跟版本报错较劲。2.2 虚拟环境给每个项目一个独立小房间很多新手最开始会直接在系统环境里pip install django装完就开干。短期内没问题但你多写几个项目后会痛苦不堪项目 A 要 Django 4.2项目 B 要 Django 5.0全装在一个环境里要么互相打架要么升级一个就弄坏另一个。虚拟环境就是用来解决这个问题的。它相当于给每个项目准备了一间独立的小房间房间里的 Python 版本、第三方库都跟外面隔离。你在这个房间里怎么折腾都不会影响别的项目。Python 自带的venv模块已经是完全够用的方案。用下面的命令创建并激活# 创建虚拟环境在当前目录下生成 .venv 文件夹 python -m venv .venv # 激活虚拟环境 # Windows: .venv\Scripts\activate # macOS / Linux: source .venv/bin/activate激活之后你会发现命令行前面多了个(.venv)前缀这就说明你已经在虚拟环境里了。之后所有pip install都会装进这个房间。如果你喜欢更快的工具可以试试uv。它是个用 Rust 写的 Python 包管理器创建虚拟环境和安装依赖都快到离谱。我的常用组合是uv venv uv pip install djangouv会自动创建一个.venv文件夹你用source .venv/bin/activate激活后面的操作和传统方式一模一样。第一次用的时候你会被它的速度惊到反正我现在本地写教程都用 uv 了。2.3 安装 Django 并验证进入虚拟环境后安装 Django 只是两行命令的事pip install Django5.0,5.2这里指定版本范围而不是直接pip install django是为了避免将来某天pip自作主张装了一个刚发布的新大版本跟你的项目代码出现兼容性问题。锁一个合理范围既保证能获得小版本更新又不会突然跨大版本。安装完之后一定要验证一下安装结果python -c import django; print(django.get_version())如果你看到类似5.0.6的输出说明 Django 已经安装成功。还可以再执行一下django-admin --version这个命令是后面创建项目要用的工具。如果提示“命令不存在”多半是虚拟环境没激活或者激活后django-admin所在的 Scripts 目录不在 PATH 里重新激活环境试试。到这里环境准备就结束了。整体上不复杂但相信我这一步值得认真做后面所有操作都会顺畅很多。3. 创建项目和应用理解 Django 的分层3.1 startproject 之后目录里都有什么激活虚拟环境、装好 Django 之后下一步就是创建项目。打开终端进入你想放代码的目录执行django-admin startproject todo_project cd todo_project用tree或者文件管理器看一下目录结构你会发现生成了这样一堆东西todo_project/ manage.py todo_project/ __init__.py settings.py urls.py asgi.py wsgi.py新手看到嵌套的项目名大概率会愣一下我当时也很懵为什么外面一个todo_project里面还有一个todo_project解释一下。最外层的todo_project/是整个工程的根目录里面那个同名子目录是“项目配置目录”。真正的全局配置都放在内层settings.py存所有配置项urls.py管全局路由入口wsgi.py和asgi.py是部署时给服务器用的入口文件。manage.py则是你日常开发中最常用的命令工具启动服务、创建应用、执行迁移全都要靠它。一个项目下面可以挂很多应用模块这种结构设计跟公司类似总公司定制度、管预算下面的各个部门各干各的业务。项目就是公司应用就是部门。3.2 创建 app 并注册为什么必须加这一行项目建好后我们来建“部门”。一个待办事项功能在 Django 里就是单独的一个 app。执行python manage.py startapp todo这一步会生成一个todo/文件夹里面有models.py、views.py、admin.py、migrations/等文件。你可能迫不及待想写代码了但注意这时候 Django 还不知道这个 app 存在。你得先去settings.py里的INSTALLED_APPS列表中登记INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, todo, # 这里 ]刚开始我也觉得这一步很啰嗦“我明明已经创建了 app为什么还要手动注册”后来才明白INSTALLED_APPS就像公司的花名册只有被登记在册的部门框架才会去加载它的模型、管理后台、模板等资源。如果不加这一行后面执行迁移时Django 根本不会理会这个 app 里的模型。3.3 定义第一个模型迁移到底在干什么Django 的模型Model是用 Python 类来描述的每一个类对应数据库中的一张表类属性对应表的字段。现在我们来写待办事项的模型。打开todo/models.py输入from django.db import models class Todo(models.Model): title models.CharField(标题, max_length200) description models.TextField(描述, blankTrue) done models.BooleanField(是否完成, defaultFalse) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return self.title这里有几个类型要注意。CharField必须指定max_length这是数据库层面的限制不填会报错。DateTimeField的auto_now_addTrue表示创建记录时自动写入当前时间你不需要手动赋值这个设计在几乎所有业务表里都会用到。写完之后就要把模型变成数据库表用到两条命令python manage.py makemigrations python manage.py migrate这两条命令的区别我用做饭来打比方makemigrations是“备菜”——根据模型的变化生成一份迁移记录文件它不改动数据库migrate是“下锅”——真正执行迁移记录把表建出来。第一次执行migrate时Django 会顺便把自带的用户、会话、权限等系统表也建好。有个排查技巧很有用想查看某次迁移到底会执行什么 SQL可以运行python manage.py sqlmigrate todo 0001。这个命令会打印出 Django 即将执行的 SQL 语句。新手第一次看这个输出会特别有收获你能直观看到CharField变成了varcharBooleanField变成了bool前后端的数据类型映射一下子就不神秘了。3.4 把模型注册进 Admin 后台Django 自带的 Admin 后台是这个框架的王牌功能很多企业用 Django 就是冲着它去的。把模型注册进去你就能得到一个完整的可视化增删改查界面。打开todo/admin.pyfrom django.contrib import admin from .models import Todo admin.register(Todo) class TodoAdmin(admin.ModelAdmin): list_display [title, done, created_at] list_filter [done] search_fields [title]这些是什么意思呢list_display决定列表页展示哪些列list_filter在侧边栏生成一个按“是否完成”筛选的按钮search_fields给标题加一个搜索框。这三行代码看起来不起眼实际上帮你把一套后台管理界面直接做完了。启动服务看看效果python manage.py runserver浏览器打开http://127.0.0.1:8000/admin/登录账号是createsuperuser创建的。如果你还没创建管理员先在终端跑一下python manage.py createsuperuser按提示输入用户名、邮箱、密码。登录进去后你会看到一个能增删改查 Todo 的管理界面。到这里第一个完整的“项目骨架 模型 后台”已经跑通了。很多人走到这一步会特别兴奋因为“有界面了”。但我建议你先别急着继续把模型和迁移的机制再理一遍后面写业务代码时你会受益很多。4. 让数据跑到页面上View、URL、Template 三件套4.1 一次请求的完整旅程在 Django 里让一个页面显示数据永远是同一个套路配置 URL、写视图、渲染模板。你掌握的其实是整个框架最核心的调用链。假设用户访问/todos/这个地址。Django 会按下面的流程处理请求进来后先到项目根路由todo_project/urls.py。根路由通过include()把请求转交给todo/urls.py如果我们配置了 app 级路由。todo/urls.py里的path(todos/, ...)匹配到对应的视图函数。视图函数执行逻辑比如从数据库读取 Todo 列表。视图把数据塞进模板上下文调用render()生成 HTML。返回 HttpResponse 给浏览器。这条链路我建议你默写到条件反射的程度。因为不管是后面写登录注册、写 API 接口还是写一个小工具站本质都是在这条链上做替换。4.2 写一个列表视图函数视图先讲清楚打开todo/views.py我们来写一个最简单的列表视图from django.shortcuts import render from .models import Todo def todo_list(request): todos Todo.objects.all().order_by(-created_at) return render(request, todo/todo_list.html, {todos: todos})Todo.objects.all()拿到所有记录order_by(-created_at)按创建时间倒序排列负号就是 DESC不加负号是 ASC。render()的第三个参数是 Python 字典这个字典就是模板里能用的环境变量。这个函数视图的逻辑很清楚拿数据、交数据。你可能会看到网上很多人推荐直接用 Django 内置的类视图ListView写起来更省事from django.views.generic import ListView from .models import Todo class TodoListView(ListView): model Todo template_name todo/todo_list.html类视图确实代码更少但我主张新手先写函数视图。原因很简单类视图把很多行为封装在“魔法方法”里你能很快做出页面却不知道数据到底是从哪个方法来的。一旦需要自定义行为比如过滤当前用户的数据你会一头雾水。等函数视图写熟了再切到类视图会非常轻松因为底层概念是通的。4.3 用模板把数据渲染出来视图返回的render(request, todo/todo_list.html, ...)会去找模板文件。为了不让模板乱糟糟我建议在todo/应用目录下建立templates/todo/这样的层级目录注意templates下面还要再放一层以应用名命名的目录。这样做是为了避免多个应用之间模板文件名冲突。创建todo/templates/todo/base.html!doctype html html head meta charsetutf-8 title{% block title %}待办事项{% endblock %}/title /head body main {% block content %}{% endblock %} /main /body /html再创建todo/templates/todo/todo_list.html{% extends todo/base.html %} {% block title %}待办列表{% endblock %} {% block content %} h1待办事项/h1 ul {% for todo in todos %} li span{{ todo.title }}/span {% if todo.done %} span已完成/span {% else %} span未完成/span {% endif %} /li {% else %} li暂无待办事项/li {% endfor %} /ul {% endblock %}模板语法入门只需要抓住两点{{ variable }}用来输出变量{% tag %}用来写逻辑比如for、if、extends、block。{% extends todo/base.html %}表示这个页面继承base.html然后通过{% block content %}填充自己的内容。模板继承的意义非常大否则你每个页面都要复制一遍 HTML 骨架改导航栏时就要改几十个文件那场面很痛苦。注意for ... else的写法。这个else是 Django 模板的特色它表示当迭代的对象为空时执行的内容。不需要写if todos去判断非常省事。4.4 URL 路由先学会path别急着上正则视图和模板写好了还差最后一环——URL 映射。Django 的分层设计里每个 app 都可以有自己的urls.py然后通过根路由挂载。先新建todo/urls.pyfrom django.urls import path from . import views urlpatterns [ path(todos/, views.todo_list, nametodo_list), ]然后在项目根路由todo_project/urls.py里挂载from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(todo.urls)), ]这里path(todos/, views.todo_list, nametodo_list)的name参数值得养成习惯。有了它你在模板里可以直接用{% url todo_list %}来生成 URL而不是手写/todos/。以后路由地址改了模板会自动跟着变不用全局替换。path()里还能用转换器比如path(todo/int:pk/delete/, ...)中的int:pk会匹配数字并转成整数传给视图。对于新手先掌握int和str两个转换器就够用 80% 的场景了。正则表达式re_path()虽然功能更强大但上手成本高等你在实际项目里真正遇到复杂匹配需求时再学也不迟。5. 查询与删除ORM 里最常见的操作姿势和坑5.1 QuerySet 是惰性的像“点外卖”下单不等于开做Django ORM 最核心的一个概念就是 QuerySet。新手最容易迷惑的是为什么我明明写了查询数据库却没有执行Todo.objects.all()返回的是一个 QuerySet 对象它并不会立刻去数据库拿数据。这个设计是故意的叫“惰性求值”。它的价值在于链式操作你可以在all()之后继续挂filter()、exclude()、order_by()这些操作追加的是查询条件而不是真的查了多次数据库。QuerySet 什么时候真正执行查询呢大概在这些时刻第一次遍历它for todo in todos调用list()把它转成列表调用len()求长度调用bool()判断是否存在调用first()/last()/get()等取值方法我用点外卖来类比你对着菜单点了好几个菜这只是“下单”厨房还没开始做当你第一次看到外卖小哥敲门时那才是真的执行了。filter()就是在菜单上不断加菜直到你“吃”迭代才开始烹饪。理解惰性有什么实际意义举个例子如果你先todos Todo.objects.all()再if todos:判断再for todo in todos:遍历看起来写了两遍其实数据库只执行了一次查询。但如果你不小心在判断和遍历之间打印了两次todos那就会触发两次查询。真实项目中的性能问题很多就是从这种不经意的重复求值开始的。5.2 增删改的基本写法创建对象最常见的三种写法# 方式一create 一步到位 Todo.objects.create(title写周报, description周一上午完成) # 方式二先实例化再 save todo Todo(title写周报, description周一上午完成) todo.save() # 方式三get_or_create避免重复创建 obj, created Todo.objects.get_or_create(title写周报)方式一和方式二在大部分场景下等价但方式二的好处是可以在save()之前修改更多字段。方式三适合处理“存在就复用不存在就新建”的场景比如用户点了一个关注按钮你要保证数据库里只有一条关注关系。更新数据也有两种常见姿势# 姿势一先取出对象改字段再 save todo Todo.objects.get(pk1) todo.done True todo.save() # 姿势二直接对 QuerySet 执行 update Todo.objects.filter(pk1).update(doneTrue)第一种会先查一次再保存适合需要处理对象上下文的场景第二种只执行一条 UPDATE 语句效率更高适合批量处理。但要注意update()是直接写在数据库层面的不会经过模型的save()方法所以某些需要自定义逻辑的字段不要用这种方式。删除数据相对简单todo Todo.objects.get(pk1) todo.delete()delete()返回的是一个元组(删除总数, {app_label.ModelName: 删除数量})。这个返回值在调试时很有用可以确认到底删掉了多少条。另外还可以批量删除Todo.objects.filter(doneTrue).delete()5.3 delete() 背后的级联逻辑on_delete 你真的懂了吗删除操作在 Django 里最值得注意的不是怎么删而是“删了这一条会不会连带把别的数据也删了”。这跟外键的on_delete参数直接相关。比如你要给每个待办事项加一个“分类”class Category(models.Model): name models.CharField(max_length50) class Todo(models.Model): category models.ForeignKey(Category, on_deletemodels.CASCADE)这里on_deletemodels.CASCADE的意思是如果某个分类被删了那所有属于这个分类的待办事项也会被一起删掉。这是 Django 对外键关系最常用的处理方式。新手常遇到的情况是删一个分类结果大量待办事项悄悄没了。这不是 BUG而是你选择的级联策略的结果。on_delete的常见选项我整理成了一个速查表选项行为适用场景CASCADE删除关联对象时连同引用它的对象一起删订单明细跟着订单删正合适PROTECT如果有引用存在禁止删除关联对象分类下面有文章时不允许删分类SET_NULL关联对象删除后把外键字段设为 NULL需要保留数据但解除关联关系要求该字段设置nullTrueSET_DEFAULT关联对象删除后设为默认值需要兜底到一个默认分类DO_NOTHING什么都不做数据库自己会有约束或者你想在应用层处理这个选择的本质是在“数据完整性”和“操作便利性”之间做权衡。写代码之前先问自己一句这个分类删了它下面的待办事项应该跟着消失还是应该留下来变成“未分类”答案不同选型就不同。5.4 查询写法速查filter、get、exclude、Q 对象接下来整理一些最常用到的查询写法我尽量按“从简单到复杂”排列# 取所有 Todo.objects.all() # 过滤 Todo.objects.filter(doneFalse) Todo.objects.filter(title__contains周报) # 模糊匹配 Todo.objects.filter(created_at__date2025-01-01) # 日期匹配 # 排除 Todo.objects.exclude(doneFalse) # 排序和切片 Todo.objects.order_by(-created_at)[:10] # 取单条 Todo.objects.get(pk1) # 取某个值 Todo.objects.values_list(title, flatTrue) # 判断是否存在 Todo.objects.filter(title写周报).exists()有两个地方新手很容易进坑。第一个是get()。当查询结果多于一条或少于一条时get()会抛异常MultipleObjectsReturned或DoesNotExist。所以在不确定数据唯一性的时候更稳妥的做法是用filter(...).first()这样即使查不到也不会抛异常最多返回None。第二个是“或”逻辑。默认的filter(A, B)是 AND 关系如果你想表达“标题包含 A 或 标题包含 B”就需要用到Q对象from django.db.models import Q todos Todo.objects.filter(Q(title__contains周报) | Q(description__contains周报))Q对象支持|和操作对应 SQL 里的 OR 和 AND。这个语法的出现频率比想象中高得多建议入门就记住后面做搜索功能时几乎是绕不开的。5.5 实例给 Todo 增加“完成”和“删除”功能理论说了一堆现在把它们串起来。我们来给待办事项加两个动作标记完成、删除。修改todo/views.pyfrom django.shortcuts import render, redirect, get_object_or_404 from .models import Todo def todo_list(request): todos Todo.objects.all().order_by(-created_at) return render(request, todo/todo_list.html, {todos: todos}) def toggle_todo(request, pk): todo get_object_or_404(Todo, pkpk) todo.done not todo.done todo.save() return redirect(todo_list) def delete_todo(request, pk): if request.method POST: todo get_object_or_404(Todo, pkpk) todo.delete() return redirect(todo_list)注意两个细节。get_object_or_404是get()的增强版查到就返回对象查不到直接返回 404 页面。为什么用它因为用户可能手抖访问了一个不存在的数据这时候让页面显示 404 比报一个DoesNotExist异常要友好得多。删除操作我写了if request.method POST这个判断。这是绝对必须的安全习惯。用 GET 请求触发删除意味着用户只要在浏览器里打开一个链接就能把数据删掉搜索引擎爬虫、浏览器预加载都可能触发这种请求而且删除 URL 会留在历史记录里。所以删除操作必须用 POST并且通常在模板里以表单形式提交。更新模板todo/todo_list.html{% extends todo/base.html %} {% block content %} h1待办事项/h1 ul {% for todo in todos %} li form action{% url toggle_todo todo.pk %} methodpost styledisplay:inline; {% csrf_token %} button typesubmit {% if todo.done %}恢复{% else %}完成{% endif %} /button /form span{{ todo.title }}/span {% if todo.done %}span已完成/span{% endif %} form action{% url delete_todo todo.pk %} methodpost styledisplay:inline; {% csrf_token %} button typesubmit onclickreturn confirm(确定删除吗);删除/button /form /li {% empty %} li暂无待办事项/li {% endfor %} /ul {% endblock %}模板里有两处{% csrf_token %}这是 Django 的 CSRF 防护机制。它的作用是为每个表单生成一个随机的令牌服务器验证这个令牌来确保请求是来自你站点的页面而不是第三方伪造的。如果你在 POST 表单里漏掉这行Django 会直接拒绝请求返回 403 页面。很多新手第一次遇到 CSRF 报错都会懵其实就是少了这个东西。对应更新todo/urls.pyfrom django.urls import path from . import views urlpatterns [ path(todos/, views.todo_list, nametodo_list), path(todo/int:pk/toggle/, views.toggle_todo, nametoggle_todo), path(todo/int:pk/delete/, views.delete_todo, namedelete_todo), ]int:pk会从 URL 里抓取数字比如/todo/3/delete/会把pk3传给视图函数。这套流程走下来你会明显感受到 Django 的套路感很强URL 配视图视图用 ORM模板渲染数据。套路熟了就快了。6. 管理后台再进一步认识 Django Unfold6.1 默认 Admin 已经很能打Django 的 Admin 后台到目前已经帮我们省了很大力气。只要在admin.py里注册一下模型你就拥有了增删改查、分页、搜索、筛选等完整功能。对内部系统来说这几乎可以被直接拿来当业务后台用。不过默认 Admin 的界面风格比较“直男”中规中矩谈不上好看。如果项目是给客户演示用或者内部团队对体验有要求那界面观感就成为一个实际需求。这也是“Django Unfold”这类主题库能火起来的原因。6.2 Django Unfold又一个让后台变好看的选择如果你关注 Django 生态近几年应该看到过 django-unfold 这个名字。它是一个基于现代设计语言开发的后台主题安装很直接pip install django-unfold然后在settings.py里把unfold加在django.contrib.admin前面INSTALLED_APPS [ unfold, django.contrib.admin, # 其他应用 ]再把admin.py里的导入改成from unfold.admin import ModelAdmin from django.contrib import admin from .models import Todo admin.register(Todo) class TodoAdmin(ModelAdmin): list_display [title, done, created_at]重启服务刷新后台界面会立刻变成另一种风格。Unfold 相比默认后台的差异主要在侧边栏布局、卡片式卡片、深色模式支持等体验细节上。对于想要“后台看起来像现代化产品”的团队来说投入很小观感提升巨大。同类的方案还有 django-jazzmin、django-simpleui 等。选哪个并不重要关键是你知道后台的可视化层是可以替换的。Admin 底层的权限体系、数据操作能力都没变换的只是皮。顺带提一句现在行业里的另一个趋势很多人开始用 AI Agent 辅助生成 Django 代码。比如描述一个需求AI 帮你生成模型和视图的骨架。我的态度是可以用但前提是你自己已经能看懂生成出来的代码。很多来找我请教的新手用 AI 生成了一个能跑的项目出了问题却完全没有排查思路因为根本不知道代码在干什么。工具能帮你省时间但替代不了理解。6.3 关于后台安全的一个提醒后台界面做漂亮了数据操作也要管住。Django 默认只允许is_staffTrue的用户访问 Admin普通注册用户是进不去的。这是最基本的一道闸门。另外默认的 admin 地址是/admin/如果有人想暴力破解这个路径等于开门迎客。一个可行的做法是在根路由里把 admin 路径改成一个不显眼的地址path(manage/, admin.site.urls),不过别神化这种“隐藏”的作用。真正的安全靠的是强密码、权限最小化、登录限流这些实打实的机制。改个路径顶多挡住一些扫描器解决不了弱密码和权限过大的问题。7. 新手最容易踩的坑与排查速查7.1 环境类问题命令找不到、版本对不上新手最常见的报错往往不是代码问题而是环境问题。我把高频情况整理一下现象原因解决方案django-admin提示“命令不存在”虚拟环境未激活激活虚拟环境检查.venv/Scripts是否在 PATHpython不是内部或外部命令Windows 未正确配置 Python安装时勾选 Add to PATH或使用py命令pip安装后import django失败pip 和 python 不属于同一个环境确认当前在虚拟环境里用python -m pip而不是裸pip项目能跑但django-admin版本和项目对不上多个环境串了尽量只用一个虚拟环境不要在系统级全局乱装一个通用排查思路先执行which python或where python看看当前解释器路径是不是在你激活的虚拟环境里。这一步能解决一半以上的环境类报错。7.2 迁移类问题No changes detected、冲突、丢失makemigrations常见的一个坑是你改了models.py执行python manage.py makemigrations结果提示No changes detected。这时候先检查INSTALLED_APPS里有没有注册这个 app。之前说过没注册就不会被扫描到。另一个情况是多人在同一个项目里开发各自新增了字段生成了多个迁移文件合并时出现依赖冲突。报错会提示你解决冲突常规手段是运行python manage.py makemigrations --merge让 Django 帮你生成一个合并迁移。这个不是灵丹妙药但能处理大多数简单冲突。还有一类问题是“我改了模型忘了 makemigrations直接 migrate”。Django 不会自动追踪你的模型改动迁移记录的生成永远需要你显式执行命令。我的习惯是每次改完模型立刻makemigrations看到生成了新迁移文件再继续写别的代码绝不攒到后面。7.3 URL 和视图类问题404、500、路由顺序新手在浏览器看到 404 页面第一反应是代码写错了其实经常是路由没匹配上。排查步骤很简单看请求的 URL 是什么比如/todos/。看todo/urls.py里是否写了path(todos/, ...)。确认根路由todo_project/urls.py没有把todo.urls注释掉。还有一个隐蔽问题路由顺序。如果两条路由的 pattern 都能匹配同一个 URLDjango 会按urlpatterns列表里的顺序从上到下依次匹配一旦命中就停止。所以更具体的路由要写在更泛用路由的前面比如path(todo/int:pk/delete/, ...)要放在path(todos/, ...)后面还是前面其实影响不大但如果遇到path(todo/str:name/, ...)这种泛用写法就一定要把更具体的路由放在前面。500 错误在开发阶段看到时记住一句话打开settings.py确认DEBUGTrue。DEBUG 模式下页面会显示完整的异常类型、堆栈和上下文对排查帮助极大。7.4 模板和静态文件问题路径不对、静态 404模板加载不到时Django 会报TemplateDoesNotExist。这时最有效的排查是看设置的模板搜索路径。我用 Django 5 之后默认采用 app 目录下的templates/自动发现机制只要目录结构是todo/templates/todo/todo_list.html并且 app 在INSTALLED_APPS里注册过一般都能找到。如果自定义了DIRS那就要确保你手动配置的路径真实存在。静态文件CSS、JS、图片加载不出来是另一个高频问题。开发阶段你需要在模板里先写{% load static %}然后用{% static css/style.css %}这种方式引用同时在项目根目录创建static/文件夹并在settings.py里确认STATICFILES_DIRS [ BASE_DIR / static, ]你自己的静态文件会跟 Django 自带的管理后台静态文件合并。如果改了但还是 404先运行python manage.py findstatic css/style.css看一下 Django 实际搜索到了哪个目录。这个命令是排查静态文件问题的神器。7.5 时区、语言和 CSRF三个“小”问题settings.py里LANGUAGE_CODE我一般设成zh-hansTIME_ZONE设成Asia/Shanghai。特别要注意USE_TZTrue默认开启时Django 会把时间按带时区的格式存入数据库显示时要转成当前时区。新手常见的现象是创建的记录比本地时间慢了 8 小时或者反之。这通常是时区处理不一致导致的排查思路是统一用 Django 的时区工具django.utils.timezone.now()不要手动调datetime.datetime.now()去跟时区较劲。CSRF 报错CSRF verification failed绝大多数情况是 POST 表单里没有{% csrf_token %}。少数情况是 AJAX 请求需要额外在请求头携带 CSRF token这个入门阶段先不用管普通表单带好模板标签即可。8. 新手心态把第一篇的“小项目”跑起来比看十遍文档重要最后说点我个人的体会。我最早学 Django 的时候掉进过一个“文档黑洞”的陷阱总觉得应该把官方 Tutorial 从头到尾读一遍再动手结果读完一半就没了激情。后来我换了个方式强迫自己在三天内做一个能用的待办事项应用不要它多完善只要能新建、能展示、能删除、能标记状态。做完之后再回头翻文档很多概念不需要背就自然贯通了。如果你把这一篇的内容跟着敲完了现在手里已经有一个能跑的小项目有后台、有列表页、能新增、能删除、能标记完成。这个体量作为入门第一篇已经很够用了。接下来我建议你试着给它加一个“分类”功能给每个 Todo 加一个外键关联然后测试一下on_delete不同选项下删除分类的后果。这个练习能把外键、级联、迁移这些概念一次串起来比单纯看文档有用得多。另一个小技巧想分享给你们尽早把settings.py里的配置拆成development.py和production.py两个文件。入门教程里一般不会提这个因为单文件在开发阶段最省事。但一旦项目要部署上线你会发现生产环境的DEBUGFalse、数据库连接、静态文件收集配置跟本地开发完全不同。第一篇阶段不用做得很复杂只需要知道这个方向等写第二篇、第三篇时我们可以一起把这套结构搭出来。
返回列表