Django基础入门:从零搭建项目、视图、模板与数据库完整链路
Django 这个框架在我个人做 Web 开发的这些年里一直觉得它是把“后端入门”这件事讲得最明白的一个。它不像某些框架一样要你手动拼接一堆路由和数据库驱动而是把项目结构、模板逻辑、数据库操作、后台管理这些基础能力一次性摆在你面前。这篇 Django 基础入门教程第一篇我不打算给你堆概念而是直接带着你从零跑通一条完整链路装好环境、创建项目、写第一个视图、套上模板、把数据库用起来。也就是说只要你能看懂 Python 的基础语法比如函数、类、import剩下的事情跟我敲命令就行。读完这篇你会拥有一个真正能运行、能加数据、能渲染到网页上的小站点而不是停留在“装完就跑了个欢迎页”的阶段。Django 适合什么样的人我觉得三类人最合适一是刚开始学后端、想找个完整框架练手的 Python 初学者二是以前用 Flask 这类微框架写过一些小接口现在想体验“全家桶”式开发的项目型选手三是想快速搭一个内部工具、内容展示或后台管理系统的开发者。想真正掌握 Django第一篇的关键就是把“项目的骨架怎么搭”“一条请求从浏览器到数据库再返回页面经历了什么”这两件事想清楚。后面很多东西都是在这个基础上堆功能的。1. 动手之前先把 Django 的定位和准备工作搞清楚1.1 为什么很多人把 Django 当成后端第一站Django 来自一个在线新闻编辑室项目所以它从诞生起就很强调几个东西内容发布、后台管理、安全防护、快速迭代。这也决定了它的气质——它是一个“自带电池”的全栈框架翻译成人话就是你能想到的 Web 开发常见需求它都帮你内置了一套解决方案。打个比方如果用 Flask 这类微框架做后端就像你拿到一套毛坯房水管、电线、门窗都要自己找人来装而 Django 更像一套精装交付的三居室墙面刷好了、水电通了、衣柜和橱柜也打好了你要做的只是往里面摆自己喜欢的家具。这里说的“衣柜橱柜”就是 Django 自带的这些能力自带 ORM可以把 Python 类直接映射成数据库表不用手写 SQL自带 Admin 后台数据模型写好后一个命令就能获得增删改查的管理界面自带模板系统可以在 HTML 里写变量和逻辑标签自带 URL 路由设计一个入口文件管理所有请求分发内置 CSRF、XSS、SQL 注入等基础安全防护。我经常跟别人说Django 的核心思路是“约定优于配置”。它规定好了项目的目录长什么样、请求怎么走、配置写在哪个文件你不用每天纠结结构问题而是把所有精力放在业务逻辑上。对于新手这种“被安排得明明白白”的感觉非常友好因为你先看到了一个成熟 Web 框架解决问题的标准姿势以后再去看别的框架就有对比、有参照了。1.2 环境准备Python 版本、虚拟环境、安装命令Django 是一个 Python 包所以前提是你电脑上有 Python。我建议用Python 3.10 或更高版本太老的版本跑新版 Django 会直接报错。如果你电脑上装了多个 Python 版本建议在命令行先确认一下默认版本python --version如果显示 3.8 或者更老建议先去官方下载页装一个新版本安装时记得勾选“Add Python to PATH”。这一步非常重要很多人后面提示“python 不是内部或外部命令”基本都是因为安装时没把 Python 加进 PATH。接下来是虚拟环境。虚拟环境的本质就是给当前项目单独开一个独立的 Python 包运行空间。这样你项目 A 用 Django 4.2项目 B 用 Django 5.0互不干扰而且以后换电脑、更新依赖时只要看一个 requirements.txt 就能完整恢复环境。我见过不少初学者图省事直接全局安装一开始没事等换了几个项目后各种版本冲突能把人磨到崩溃。所以从第一篇开始养成用虚拟环境的习惯很值得。mkdir django_basic cd django_basic python -m venv venvWindows 下激活命令venv\Scripts\activatemacOS / Linux 下激活命令source venv/bin/activate激活成功后命令行前面会出现一个(venv)前缀这就代表你已经进入虚拟环境了。之后在这个终端里执行的 pip 安装都会装进这个独立环境里。然后安装 Djangopip install django如果你想固定版本防止未来某个大版本更新后代码不兼容可以这样装pip install django5.0.6装完验证一下python -m django --version能看到版本号说明环境已经打通。到这里基础准备工作就做完了下一步我们来创建第一个项目。注意如果你用的是 conda 之类的环境管理器也可以直接conda create -n django_basic python3.11然后pip install django思路是一样的保证“项目依赖”和“全局环境”隔离就行。2. 创建第一个 Django 项目骨架文件逐个拆解2.1 startproject 与那个容易忽略的“点”Django 提供了一个命令行工具来生成项目骨架这里我们用一个实际项目名来演示比如myblogdjango-admin startproject myblog .注意末尾那个点.。很多人第一次创建项目时会漏掉它结果生成了两层嵌套目录运行命令时总搞不清楚manage.py到底在哪。这个点代表“在当前目录直接生成项目文件”生成的目录结构大概是这样的django_basic/ ├── venv/ ├── manage.py └── myblog/ ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py如果你不加那个点会变成myblog/myblog/...多一层外壳目录。虽然也能用但日常开发中大家更习惯让manage.py就在项目根目录下。先不急着改代码我们把服务跑起来看看python manage.py runserver然后浏览器打开http://127.0.0.1:8000/会看到一个默认的欢迎页火箭图标加“The install worked successfully”。看到这个页面说明你的项目骨架已经能正常工作了。2.2 自动生成的核心文件到底负责什么很多人跑通欢迎页之后面对这一堆文件还是很懵。我建议把这几个文件当成“公司里的不同部门”来理解文件作用日常开发接触频率manage.py项目操作入口相当于命令行管理员所有管理命令都通过它执行每次开发都用settings.py全局配置文件数据库、应用注册、模板、语言时区都在这里改高urls.py全局 URL 分发器相当于前台接线员决定不同地址交给谁处理高wsgi.py/asgi.py部署时用到的服务器接口本地开发很少动它极低__init__.py标记这是一个 Python 包平时不用改极低manage.py是你接触最多的一个文件。比如启动开发服务器是python manage.py runserver创建应用是python manage.py startapp操作数据库是python manage.py makemigrations它就像项目的“总遥控器”。而settings.py则是整个项目的中央配置中心很多让你捉摸不透的怪问题最后追根溯源都跟这里的配置有关。2.3 settings.py 里最关键的几个配置项打开myblog/settings.py不用全看懂先抓住这几个核心配置即可INSTALLED_APPS一个列表里面是当前项目启用的所有应用和内置组件。你每新建一个应用都要手动把应用名加到这里否则 Django 不知道这个应用的存在很多命令都会报错“No changes detected”。DATABASES默认是 SQLite3 的配置。新手阶段用 SQLite 最合适因为它是文件型数据库一个.db文件就能存储全部数据不用额外安装数据库服务端。等以后数据量大、需要高并发写入时再改成 MySQL 或 PostgreSQL代码层面基本不用动改改配置就好了。LANGUAGE_CODE和TIME_ZONE默认分别是en-us和UTC。建议一上来就改成zh-hans和Asia/Shanghai否则以后 Admin 后台是英文界面时间记录也和北京时间对不上。TEMPLATES模板引擎配置。这里有个DIRS列表如果你想让 Django 在全局模板目录、而不是应用各自模板目录里找模板需要把路径加进去。STATIC_URL静态资源CSS、JS、图片的 URL 前缀默认/static/。静态文件是否加载得出来新手阶段最容易在这里和目录结构上出问题。我把常用开头配置给你贴一下作为参考模板LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, books, # 这里放以后自己创建的应用名 ]这里要注意应用注册列表里books必须放在内置应用后面顺序不影响功能但代码整洁性上好看一些。改完配置后重启runserver刷新页面时Django 会自动加载新的配置。3. 从 URL 到视图写第一个自己决定的页面3.1 先理解 MTV 模式的请求链路很多人一上来就被 MVC、MTV 这些缩写搞晕。其实可以把它理解成一次点餐流程你浏览器在菜单上选了一个菜URL 地址服务员URL 路由把订单送到后厨厨师视图函数根据订单开始做菜需要食材就去仓库拿数据库菜做好后端上来渲染成 HTML 页面你直接吃。Django 里的 MTV 其实和传统 MVC 是一一对应的概念只是叫法不同Django 术语传统 MVC 称呼对应职责ModelModel模型负责数据和业务规则对应数据库表TemplateView视图表现层负责展示页面对应 HTML 模板ViewController控制器负责业务逻辑接收请求后决定响应什么所以“视图”这个名字在 Django 里是业务控制器不是页面的视觉设计。这条链路你一定要记住浏览器请求某个 URL → 路由匹配到对应视图函数 → 视图函数从模型取数据 → 把数据交给模板渲染 → 返回给浏览器。3.2 创建应用并写第一个视图一个 Django 项目可以包含多个应用每个应用相当于一个业务模块。比如我们做一个“读书笔记”站点可以先创建一个books应用python manage.py startapp books执行后项目目录中会多出一个books/文件夹里面有models.py、views.py、admin.py等文件。注意此时books还没有被项目“注册”我们需要回到settings.py的INSTALLED_APPS中填入books。然后在books/views.py中写一个最简单的视图函数from django.http import HttpResponse def index(request): return HttpResponse(Hello, Django!)每个视图函数至少接收一个request参数表示当前的 HTTP 请求对象。它必须返回一个HttpResponse对象Django 才会把它作为网页响应发送给浏览器。3.3 配置 URL 路由有了视图函数还需要在 URL 配置里告诉 Django“当用户访问某个地址时请调用这个函数”。先改项目根目录的myblog/urls.pyfrom django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(books.urls)), ]然后新建books/urls.pyfrom django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), ]完成之后访问http://127.0.0.1:8000/浏览器里显示的就是你自己写的字了。这里面有两点值得说明path(, views.index, nameindex)中的空字符串表示网站根路径name参数可以理解为给这个路由起的“别名”后面在模板里用{% url index %}生成链接时靠的就是这个别名。主路由用include(books.urls)把books应用下的子路由拉进来。这样做的好处是以后应用多了每个应用只管自己的路由互不干扰主路由文件始终很清爽。3.4 带参数的 URL让同一个视图处理不同内容实际开发中你会遇到很多类似“查看 id 为 3 的书籍详情”这样的需求。Django 路由支持参数传递非常直观from django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(book/int:book_id/, views.book_detail, namebook_detail), ]其中int:book_id表示这里匹配一个整数并把值作为名为book_id的参数传给视图函数def book_detail(request, book_id): return HttpResponse(f你正在查看书籍编号是{book_id})你可以访问http://127.0.0.1:8000/book/5/来验证效果。int:xxx中的int是路径转换器意思是“要求这里必须是整数”类似的还有str:xxx匹配字符串、slug:xxx匹配短横线文本。合理使用这些转换器能把很多参数校验的工作交给路由层而不用在视图函数里自己写正则判断。4. 模板系统让页面真正变成用户看到的网页4.1 模板放哪里Django 怎么找到它视图函数里直接返回字符串这种写法只适合演示。真实项目里页面长什么样应该由 HTML 模板决定视图只负责把数据塞进模板。Django 在创建项目时默认不会创建一个全局的模板目录所以我习惯在项目根目录下手动建一个templates文件夹。然后把路径告诉 Django# settings.py import os TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [BASE_DIR / templates], APP_DIRS: True, ... }, ]注意BASE_DIR是 Django 自动定义的项目根目录BASE_DIR / templates这种写法在 Python 3.6 里可以直接通过/拼接路径比os.path.join简洁太多。这里如果给DIRS填入路径APP_DIRS保持True也没关系两者的关系是Django 会先到DIRS指定的目录找找不到再去每个应用的templates子目录里找。4.2 模板语法三件套变量、过滤器、标签模板文件本质上是“HTML Django 模板语法”。最常用的是这三类变量用双层花括号包裹比如{{ book.title }}表示输出一个变量的值。如果变量是字典也可以用点号访问键。过滤器对变量做简单处理比如{{ book.publish_date|date:Y-m-d }}把日期格式化成指定样式{{ title|default:未命名 }}在变量为空时显示默认值。过滤器可以链式使用{{ text|truncatechars:20|linebreaks }}先截断 20 个字符再把换行转成p标签。标签用{% %}包裹表示控制逻辑。最典型的就是if和for{% if book.is_read %} p这本书已经读完了/p {% else %} p还在待读清单里/p {% endif %} {% for book in book_list %} li{{ book.title }} —— {{ book.author }}/li {% empty %} li暂无书籍/li {% endfor %}{% empty %}是 Django 模板里一个很贴心的设计当循环对象为空时执行不用像 Python 里那样先判断再循环。4.3 写一个带数据的页面我们继续在books/views.py里改造先用一个 Python 列表充当数据后面第五部分再换成数据库里的真实数据。from django.shortcuts import render def index(request): books [ {title: Python编程从入门到实践, author: Eric Matthes}, {title: 流畅的Python, author: Luciano Ramalho}, {title: Django 3.0 实战, author: William S. Vincent}, ] context { book_list: books, } return render(request, books/index.html, context)这里用render替代了之前的HttpResponse。render做了三件事加载指定模板、把 context 中的数据渲染进去、返回一个完整的 HttpResponse。context就是传给模板的字典模板里通过键名访问。在templates/books/index.html里写!DOCTYPE html html head title我的读书清单/title /head body h1待读与已读/h1 ul {% for book in book_list %} li{{ book.title }} —— {{ book.author }}/li {% empty %} li还没有记录任何书籍/li {% endfor %} /ul /body /html刷新首页你就能看到自己的第一个动态页面了。虽然它的数据还是硬编码在 Python 列表里但已经完整跑通了“视图 → 模板 → 浏览器”这一层。4.4 模板继承一个减少重复的利器实际页面通常有统一的头部、导航、底部信息如果每个页面都复制粘贴一份改一行导航信息就能让你改到崩溃。模板继承就是来解决这个问题的。先建一个templates/base.html!DOCTYPE html html head title{% block title %}我的站点{% endblock %}/title /head body header nav网站导航首页 | 书籍列表 | 关于我们/nav /header main {% block content %} {% endblock %} /main footer p版权信息/p /footer /body /html然后让页面模板继承它{% extends base.html %} {% block title %}书籍列表{% endblock %} {% block content %} h1我的读书清单/h1 ul {% for book in book_list %} li{{ book.title }} —— {{ book.author }}/li {% endfor %} /ul {% endblock %}{% extends base.html %}必须写在模板的第一行表示“我是基于这个基础模板的”。{% block %}就是可以被子模板覆盖的“坑位”。这种模式一旦用顺了你会发现在 Django 里搭页面变得特别快核心页面只需要关注内容区即可。4.5 静态文件CSS、JS 放哪里模板里要加载 CSS 和 JS需要把静态文件放在正确的位置并用特定语法引入。我一般在应用目录下创建books/static/books/css/style.css然后在模板顶部加载静态文件标签{% load static %} ... link relstylesheet typetext/css href{% static books/css/style.css %}{% static %}标签会根据STATIC_URL生成正确的静态文件地址。这里最容易犯的错有两类一是文件放错了目录Django 默认只在每个应用的static目录下找二是忘了写{% load static %}模板直接报Invalid block tag。这两点我在自己带的人身上见过太多次了。5. 模型与数据库让页面真正有数据可读5.1 ORM 到底帮我们做了什么很多初学者一听到“数据库”就有点紧张觉得自己 SQL 没学好是不是就做不了 Web 开发。Django 的 ORM 可以让你暂时放下这种焦虑。ORM 全称是对象关系映射通俗讲它相当于一个翻译官你把 Python 类写出来它负责翻译成对应的 SQL 建表语句你对类属性的操作它翻译成对应的 SQL 增删改查语句。这意味着在开发阶段你可以完全用 Python 的思维方式操作数据库。等以后遇到复杂查询、性能瓶颈时再回头学 SQL 也会容易很多因为那时候你已经理解了数据模型本身。5.2 定义第一个模型在books/models.py中定义一个书籍模型from django.db import models class Book(models.Model): title models.CharField(书名, max_length200) author models.CharField(作者, max_length100) pages models.IntegerField(页数, default0) is_read models.BooleanField(是否已读, defaultFalse) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return f《{self.title}》- {self.author}每个模型类继承models.Model类属性对应数据库字段。各字段类型的含义很直白CharField变长字符串必须给max_lengthIntegerField整数BooleanField布尔值DateTimeField日期时间auto_now_addTrue表示记录创建时自动写入当前时间__str__方法决定这个对象在后台列表、控制台打印时的显示文本。提示CharField里的第一个字符串参数是“字段的人类可读名称”里直接写中文没问题Django 对 Unicode 支持很好。5.3 迁移为什么不是直接建表写好了模型很多人下意识觉得“数据库表是不是已经建好了”答案是没有。Django 采用“迁移”机制两步走python manage.py makemigrations根据模型生成一个迁移文件可以理解成“建表计划”这个文件会被记录在应用的migrations/目录下python manage.py migrate真正把这些计划应用到数据库里。为什么要分两步因为迁移文件是文本文件可以放进版本控制里。协作开发时别人拉取代码后执行一次migrate就能把你新增的表建好全程不用手工去生产数据库敲 SQL。这也是 Django 从设计理念上就很强调的一点数据库结构应该跟随代码版本一起演进。实际操作python manage.py makemigrations python manage.py migrate第一次执行完migrate之后项目中会多出一个db.sqlite3文件那就是整个开发数据库。Django 默认的表用户、权限、会话等也会一并建好。5.4 用 Django Shell 操作数据数据库表建好了怎么往里面加数据最简单的方式就是用 Django 自带的交互式 Shell它是带项目环境的 Python 终端python manage.py shell然后输入from books.models import Book book Book(titleDjango基础入门, author某开发者, pages220, is_readFalse) book.save()save()是创建单条记录的标准方法Python 里创建对象没有被自动提交到数据库必须调用save()。你还可以一次性处理多条数据Book.objects.create( titlePython网络爬虫实战, authorA同学, pages310, is_readTrue ) # 查询所有 all_books Book.objects.all() for b in all_books: print(b.title, b.author) # 过滤查询 unread_books Book.objects.filter(is_readFalse) # 获取单条 first_book Book.objects.get(id1)Book.objects是模型的“管理器”它提供了all()、filter()、get()、create()这些方法分别对应查询全部、条件查询、查单条、创建记录。等以后学到复杂查询你会发现这个管理器几乎覆盖了日常 90% 的数据需求。5.5 把数据库数据渲染到页面现在把视图里的硬编码列表换成数据库真实数据from django.shortcuts import render from .models import Book def index(request): book_list Book.objects.all().order_by(-created_at) return render(request, books/index.html, {book_list: book_list})刷新页面你之前通过 Shell 写入的几条数据就会出现在列表里。再加一条数据试试页面会跟着更新。到这个阶段第一篇的核心链路已经全部打通请求进来、路由分发、视图处理、模型查库、模板渲染、返回响应。6. 新手最常见的坑与排查经验6.1 端口被占用runserver 起不来你运行python manage.py runserver时报错 “Error: That port is already in use”这是最常见的开局坑。多数情况是上一个开发服务器没有关干净或者 8000 端口被其他程序占了。处理方式很简单换一个端口启动python manage.py runserver 8001如果你经常遇到这个问题还可以在 Windows 上执行netstat -ano | findstr :8000Linux/macOS 上执行lsof -i:8000找到占用进程的 PID 后结束它。不过我更推荐直接换端口毕竟本地开发阶段端口不重要跑起来才重要。6.2 TemplateDoesNotExist模板明明在却找不到Django 报这个错大部分情况下不是模板真的不存在而是搜索路径不对。排查顺序是确认模板文件的后缀和路径是否正确比如你视图里写的是books/index.html那文件必须在某个templates目录下的books/index.html如果放在了项目根目录的templates文件夹确认settings.py的DIRS是否配置了BASE_DIR / templates确认应用是否出现在INSTALLED_APPS里APP_DIRS模式下应用没有注册Django 是不会去它的templates目录找文件的改完settings.py后是否重启了开发服务器。这里有一个我踩过坑的细节Django 的模板搜索按顺序进行如果多个应用有同名模板文件它只会加载第一个找到的。所以给应用内模板加一层以应用名命名的子目录比如books/index.html而不是直接index.html是个很好习惯。6.3 时区警告和中文乱码启动项目时如果冒出 “WE ARE IN THE YEAR ... You may need to set TIME_ZONE” 这类提示意思是当前用的是默认 UTC 时间本地时间会不准。按之前说的在settings.py里设置LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai然后重启服务器警告就消失了后台界面也会变成中文。如果你以后把数据库切到 MySQL中文乱码大概率是字符集问题。Django 连接 MySQL 时默认使用的编码取决于配置建议在建库时指定utf8mb4。不过在 SQLite 阶段基本不用关心这个SQLite 的 Unicode 支持很省心。6.4 表单提交 403新手第一次写表单时POST 提交几乎必遇 403 Forbidden。这个不是服务器拒绝你访问而是 Django 的 CSRF 防护机制在起作用。Django 默认要求所有 POST 请求必须携带一个 CSRF 令牌为了确认请求是来自本站页面。模板里的表单只要加一行{% csrf_token %}就能解决。如果你正在用 Django 写 API 接口而不是传统表单页面可以在视图上加csrf_exempt装饰器但初学阶段不建议这么做——CSRF 防护是你适应“安全和开发效率如何平衡”的第一个真实案例。6.5 改了模型但数据库没变化这里说的是你改了models.py里的某个字段然后直接刷新页面或重启服务器发现表结构还是旧的样子。Django 不会像某些脚本框架那样自动同步模型和数据库。你改了模型类必须重新执行python manage.py makemigrations python manage.py migrate如果运行makemigrations时提示 “No changes detected”先确认两件事一类是否在INSTALLED_APPS中注册二、类的代码是否真的保存了。很多时候改完没有保存或者应用没注册Django 压根看不到你的修改。6.6 runserver 自动刷新别迷信用热加载开发服务器默认会在代码变化时自动重载你改完views.py保存后服务器会自动重启页面刷新就能看到效果。但有些情况它不会自动生效比如新增了模板目录下的文件、改了settings.py中的某些配置。我习惯是搞不清楚为什么没生效时直接 CtrlC 停掉然后重新runserver。重启大法在 Django 开发阶段几乎是万能解药。这篇 Django 入门教程我写了很多容易踩的细节因为我自己就是从这些问题里爬出来的。当年我第一个 Django 项目跑通时其实只做了一件简单的事把一个表格从数据库里取出来渲染到网页上。但就是这么个小功能把 MTV 模式、ORM、模板系统、URL 路由全都串起来了。我个人的建议是学 Django 不要贪多先把今天这条链路反复跑几遍直到你能不看教程写出一个“列表页”为止。下一篇我会接着讲 Admin 后台、表单、用户登录和分页这些进阶内容。练熟这篇再看后面就会轻松很多。