简介:Django是一款基于Python的高效开源Web框架,广泛用于快速构建安全且可维护的网站。本《Django开发指南中文版》是一份面向初学者与进阶开发者的全面参考资料,系统讲解了从环境搭建、项目结构、模型设计、视图与URL配置、模板系统、表单处理到用户认证、中间件、性能优化及生产部署等核心内容。通过清晰的实践指导和深入的技术解析,帮助开发者掌握Django全栈开发流程,提升项目构建能力,适用于各类Web应用开发场景。
Django开发环境搭建与核心概念入门
哎呀,是不是刚打开编辑器就一脸懵?别慌!咱们今天不整那些“首先…其次…”的八股文,直接上干货。想象一下:你正坐在咖啡馆里,手边是杯快凉的美式,脑子里盘算着怎么把那个拖了三个月的个人博客项目启动起来——而Django,就是你的救星 🚀
环境隔离的艺术:从“我机器上能跑”到团队协作无压力
先问一句:有没有经历过“在我电脑上明明好好的,怎么部署上去就炸了”这种灵魂拷问?😅 想必不少人都被这个坑绊过。其实问题的根源很简单—— 依赖冲突 。
Python项目就像一锅炖菜,不同项目需要不同的“香料组合”。有的要Django 3.x配旧版数据库驱动,有的又要用Django 5的新特性。如果所有包都装在系统全局环境里,那简直就是一场灾难。
所以啊,第一课不是写代码,而是学会“划地盘”——虚拟环境搞起来!
用 venv 打造专属沙箱
好消息是:从Python 3.3开始,你根本不需要额外安装什么 virtualenv ,因为官方自带了一个更轻量、更稳定的工具—— venv 。它就像是给每个项目发了个独立的小房间,谁也别想打扰谁。
来,动手试试:
python -m venv myblog_env
就这么一行命令,你就拥有了一个干净整洁的开发沙箱。目录结构长这样:
myblog_env/
├── bin/ # Linux/macOS下的可执行文件(pip、python等)
├── Scripts/ # Windows专用脚本区
├── lib/ # 所有第三方库都会安家在这里
└── pyvenv.cfg # 配置文件,记录基础Python路径
💡 小贴士:建议把虚拟环境名起得有意义一点,比如
myblog_env、shop_api_venv,避免日后面对一堆叫env1、env2的神秘文件夹抓耳挠腮。
如果你想要更精细的控制,还可以加上这些参数:
python -m venv --clear --copies myblog_env
-
--clear:如果同名目录存在,先清空再重建(适合重置环境) -
--copies:强制复制Python解释器而不是软链接(某些Windows或Docker环境下更稳定)
流程图走一波,帮你理清整个创建逻辑:
graph TD
A[开始] --> B{检查Python版本}
B -->|Python >= 3.3| C[执行: python -m venv env_name]
B -->|Python < 3.3| D[需先安装virtualenv]
C --> E[生成独立目录结构]
E --> F[包含bin/, lib/, pyvenv.cfg]
F --> G[环境创建完成]
看到没?整个过程简单得不像话。但记住一件事:一定要把你这个 myblog_env/ 加进 .gitignore !
# .gitignore
__pycache__/
*.pyc
.env
myblog_env/
.DS_Store
不然哪天手滑提交了,同事拉下代码一看——好家伙,几百兆的虚拟环境全塞进来了 😵💫
跨平台激活指南:别让操作系统卡住你的节奏
接下来就是“开门入住”的环节了——激活虚拟环境。这里有个小痛点:不同系统的激活命令不一样,新人最容易在这一步出错。
| 平台 | 激活命令 | 退出命令 |
|---|---|---|
| Linux/macOS | source myblog_env/bin/activate | deactivate |
| Windows CMD | myblog_env\Scripts\activate.bat | deactivate |
| PowerShell | myblog_env\Scripts\Activate.ps1 | deactivate |
注意看PowerShell那一行, .ps1 结尾的脚本默认是被禁用的!第一次运行会提示权限错误。解决办法也很简单:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
这句的意思是:“允许当前用户运行本地编写的脚本”,既安全又实用。
激活成功后,你会看到终端前面多了一串括号:
(myblog_env) $ python --version
Python 3.11.4
(myblog_env) $ which pip
/Users/you/project/myblog_env/bin/pip
(myblog_env) 这个小标记太重要了,它时刻提醒你:“你现在在一个封闭空间里工作,请放心大胆地装包。”
为了防止队友误操作,我还喜欢加个自动化检测脚本:
import sys
def is_venv():
return hasattr(sys, 'real_prefix') or \
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
if __name__ == "__main__":
if is_venv():
print("✅ 当前运行在虚拟环境中")
else:
print("❌ 未检测到虚拟环境,请先激活")
这段代码可以嵌入CI/CD流程中作为前置检查,避免“忘记激活环境导致打包失败”的低级错误。
依赖管理终极方案: requirements.txt 分层设计
现在环境准备好了,该装Django了:
pip install "Django>=4.2,<5.0"
为啥不用固定版本?因为 >=4.2,<5.0 既能锁定大版本,又能自动获取小版本的安全更新,平衡了稳定性与安全性。
装完之后,立刻导出依赖清单:
pip freeze > requirements.txt
生成的内容大概是这样的:
Django==4.2.7
sqlparse==0.4.4
asgiref==3.7.2
每一行都是精确版本号,确保任何人还原环境时都能得到完全一致的结果。
但这只是初级玩法。真正专业的做法是 分层管理依赖 :
requirements/
├── base.txt # 基础依赖(Django、常用工具)
├── dev.txt # 开发专用(debug-toolbar、flake8)
└── prod.txt # 生产环境(gunicorn、whitenoise)
然后在 dev.txt 里这样写:
-r base.txt
django-debug-toolbar==4.2.0
flake8==6.1.0
pytest-django==4.5.2
而在 prod.txt 里:
-r base.txt
gunicorn==21.2.0
whitenoise==6.6.0
psycopg2-binary==2.9.7
这样做有几个巨大好处:
- 团队开发只装必要的调试工具,不影响生产;
- 部署时可以精准控制运行时依赖;
- CI测试可以选择性加载,节省时间;
恢复环境也变得超级清晰:
# 新成员入职第一步
git clone https://github.com/team/myblog.git
cd myblog
# 第二步:建环境
python -m venv venv
source venv/bin/activate # 或对应平台命令
# 第三步:装依赖
pip install -r requirements/dev.txt
整个流程丝滑如德芙,再也不用担心“为什么我的页面样式不对?”“你是不是忘了装xxx?”这类扯皮问题。
顺便提一嘴,国内网络环境下推荐加镜像源提速:
pip install -r requirements/dev.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
清华源yyds!👏
下面是完整的依赖流转流程图:
graph LR
A[开发者本地开发] --> B[pip freeze > requirements.txt]
C[新成员加入项目] --> D[git clone 项目代码]
D --> E[创建并激活虚拟环境]
E --> F[pip install -r requirements.txt]
F --> G[环境一致性达成]
这套机制看似简单,实则解决了团队协作中最头疼的问题之一。信我,坚持这么做,你会成为办公室最受欢迎的人 😎
Django项目的初始化流程:别急着写业务逻辑!
OK,环境搭好了,接下来是不是就想冲进去写视图、建模型?停!先深呼吸,听我说一句掏心窝子的话: 一个项目的命运,往往在初始化那一刻就决定了 。
很多人图省事,随手敲一行 django-admin startproject mysite ,结果后面越写越乱,文件到处飞,最后不得不重构……何必呢?
我们一步一步来,稳扎稳打。
初始化命令背后的秘密
进入虚拟环境后,先确认Django装好了:
django-admin --version
# 输出:4.2.7
没问题就开始创建项目骨架:
django-admin startproject mysite .
注意末尾那个 . !它的作用是把项目文件直接放在当前目录,而不是再嵌套一层 mysite/mysite/ 。虽然看起来只是少一层目录,但在后续导入路径处理上能省去无数麻烦。
生成的结构如下:
mysite/
├── manage.py
├── mysite/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
└── requirements/ # 我们手动创建的
这里面最关键的几个角色是谁?
-
manage.py:万能指挥官,所有命令都要靠它发起; -
settings.py:项目的“大脑”,一切配置从这儿出; -
urls.py:路由中枢,决定请求去哪儿; -
wsgi.py:生产部署的入口点; -
asgi.py:支持WebSocket和异步任务的通道;
你可以把 manage.py 理解成遥控器,按一下 runserver 就启动服务器,按一下 migrate 就同步数据库,非常方便。
它的内部实现其实也不复杂:
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError("无法导入Django,请检查是否已安装") from exc
execute_from_command_line(sys.argv)
重点看第6行: os.environ.setdefault(...) 设置了默认配置模块路径。这意味着你在任何地方调用 python manage.py xxx ,Django都知道该去哪里找 settings.py 。
这也是为什么你不应该随便移动这些核心文件的原因——一旦路径断了,整个项目就瘫痪了。
settings.py :掌控全局的关键配置
现在打开 mysite/settings.py ,你会发现一大堆变量。别怕,我们挑重点说几个,这几个直接关系到你的项目能不能活下去。
DEBUG模式:开发与生产的分水岭
DEBUG = True # 开发时设为True,方便看错误详情
但!只要上线,必须改成 False 。否则一旦出错,用户就能看到完整的堆栈信息,包括你的数据库密码、密钥路径……想想都吓人。
所以最佳实践是:
import os
DEBUG = os.environ.get('DJANGO_DEBUG', '') == 'True'
然后通过环境变量控制:
export DJANGO_DEBUG=True && python manage.py runserver
这样就不需要改代码也能切换模式,适合自动化部署。
SECRET_KEY:不能泄露的生命线
SECRET_KEY = 'your-secret-key-here'
这个值用于加密session、生成token等关键操作。如果被人拿到,相当于把家门钥匙交给了黑客。
所以千万不要硬编码!正确的做法是从环境变量读取:
from decouple import config # 需要先pip install python-decouple
SECRET_KEY = config('SECRET_KEY')
配合一个 .env 文件:
SECRET_KEY=abcdef1234567890yourverylongsecretkeyhere
DEBUG=True
DB_NAME=myblog
然后把这个 .env 加进 .gitignore ,永远不提交到仓库。
🔐 提示:推荐使用 dj-config-builder 或
django-environ这类库,它们专为配置管理而生,支持多种格式和层级合并。
数据库配置:SQLite够用吗?
默认配置是SQLite:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
对于小型项目或者初期原型来说完全OK,但它有个致命弱点: 不支持并发写入 。一旦流量上来,多个用户同时提交表单就会卡死。
所以正式项目强烈建议用PostgreSQL:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASS'),
'HOST': 'localhost',
'PORT': '5432',
}
}
PostgreSQL不仅性能更强,还支持JSON字段、全文搜索、地理数据等高级功能,长远来看值得投入。
ALLOWED_HOSTS:防止HTTP Host头攻击
当 DEBUG=False 时,必须设置:
ALLOWED_HOSTS = [
'example.com',
'www.example.com',
'localhost',
'127.0.0.1',
]
这是为了防御一种叫做“Host头伪造”的攻击。比如黑客发送一个 Host: evil.com 的请求,试图让你的应用返回敏感内容。有了这个白名单,Django就会直接拒绝非法域名的访问。
总结一下生产环境的关键配置模板:
import os
from decouple import config
DEBUG = False
SECRET_KEY = config('SECRET_KEY')
ALLOWED_HOSTS = ['example.com', 'api.example.com']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASS'),
'HOST': 'localhost',
'PORT': '5432',
}
}
STATIC_ROOT = '/var/www/myblog/static/'
MEDIA_ROOT = '/var/www/myblog/media/'
把这些配置外置、加密、动态加载,才是现代Web应用的标准姿势 ✅
数据模型构建:如何设计一张经得起时间考验的表?
终于到了最激动人心的部分——建模!很多人以为ORM就是“把类变成表”,其实远远不止。一个好的模型设计,不仅要反映业务逻辑,还要考虑未来扩展性、查询效率、数据一致性等问题。
让我们以一个博客系统为例,看看怎么一步步打造出健壮的数据结构。
模型类设计三要素:字段、约束、关系
先定义作者和文章两个基本模型:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='articles')
class Meta:
db_table = 'blog_articles'
ordering = ['-pub_date']
来,逐个拆解亮点:
-
CharField(max_length=100):字符串字段,必须指定最大长度,对应数据库的VARCHAR; -
EmailField(unique=True):自带邮箱格式验证 + 唯一索引,防重复注册神器; -
TextField():不限长度的大文本,适合文章正文; -
DateTimeField(auto_now_add=True):创建时自动记录时间,再也不用手动赋值; -
BooleanField(default=False):状态开关,默认未发布; -
ForeignKey(..., on_delete=models.CASCADE):外键关联,删除作者时连带删掉所有文章; -
related_name='articles':反向查询快捷方式,author.articles.all()就能拿到作者所有文章;
⚠️ 注意:不要滥用
CASCADE!如果是评论系统,删用户不该连评论一起删,否则历史数据全没了。这时候可以用SET_NULL或PROTECT。
再来看个复杂点的例子——订单系统:
class Order(models.Model):
STATUS_CHOICES = [
('pending', '待处理'),
('shipped', '已发货'),
('delivered', '已送达'),
('cancelled', '已取消'),
]
order_number = models.CharField(max_length=50, unique=True, db_index=True)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
customer_note = models.TextField(blank=True, null=True)
这里有几点值得细品:
-
choices参数:不仅限制取值范围,还能在Admin后台自动生成下拉菜单,用户体验满分; -
DecimalField:金融计算必须用它!浮点数会有精度丢失问题,0.1 + 0.2 != 0.3懂的都懂; -
db_index=True:为订单号加索引,让Order.objects.get(order_number='...')飞起来; -
blank=True, null=True:说明这个字段在表单里可以为空,数据库也允许NULL;
特别是 auto_now 和 auto_now_add 的区别:
-
auto_now_add:只在新建时设置一次; -
auto_now:每次保存都会更新,适合“最后修改时间”;
千万别搞混了,不然你可能会遇到“每次访问详情页订单时间都被刷新”的诡异bug 😂
模型继承:别复制粘贴了,用抽象基类!
有没有发现很多模型都有 created_at 、 updated_at 这两个字段?每次都要写一遍?太累了!
Django提供了三种继承方式,其中最实用的是 抽象基类 :
class TimestampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True # 关键!不会生成实际数据表
class Product(TimestampedModel):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
这样一来, Product 表就会自动包含三个字段: id , created_at , updated_at , name , price 。而且以后新增的模型都可以轻松复用。
另外两种继承模式也值得了解:
| 类型 | 是否生成新表 | 典型用途 |
|---|---|---|
| 抽象基类 | 否 | 字段复用(时间戳、软删除标志) |
| 多表继承 | 是 | 扩展已有模型(如User→Admin) |
| 代理模型 | 否 | 修改行为而不改结构(排序、方法重写) |
举个例子,你想让用户分为普通用户和VIP用户,但共用同一张表:
class User(models.Model):
name = models.CharField(max_length=50)
points = models.IntegerField(default=0)
class VIPUser(User):
class Meta:
proxy = True
def is_vip(self):
return self.points > 1000
VIPUser 和 User 共享数据表,但你可以定义专属方法,非常适合做权限判断或定制逻辑。
下面是三种继承模式的类图对比:
classDiagram
class AbstractBase {
+created_at: DateTimeField
+updated_at: DateTimeField
<<abstract>>
}
class Product {
+name: CharField
+price: DecimalField
}
class Place {
+name: CharField
+address: CharField
}
class Restaurant {
+serves_pizza: BooleanField
+rating: FloatField
}
AbstractBase <|-- Product : Inherits
Place <|-- Restaurant : Multi-table Inheritance
记住一句话: 能用组合不用继承,能用抽象不用多表 。过度设计只会增加维护成本。
Meta元类:不只是名字,更是行为定义
Meta 类藏了很多宝藏配置,很多人只知道改表名,其实它还能干大事!
比如联合唯一约束:
class Enrollment(models.Model):
student = models.ForeignKey('Student', on_delete=models.CASCADE)
course = models.ForeignKey('Course', on_delete=models.CASCADE)
enrollment_date = models.DateField(auto_now_add=True)
class Meta:
unique_together = ('student', 'course') # 同一个学生不能重复报名
不过官方现在更推荐用 constraints :
from django.db.models import UniqueConstraint
class Meta:
constraints = [
UniqueConstraint(
fields=['student', 'course'],
name='unique_student_course_enrollment'
),
models.CheckConstraint(
check=models.Q(enrollment_date__lte=models.Now()),
name='enrollment_date_cannot_be_future'
)
]
看到了吗?不仅能定义唯一性,还能加 检查约束 ,比如报名日期不能是未来的!
还有权限管理:
class Report(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
class Meta:
verbose_name = "报告"
verbose_name_plural = "报告"
permissions = [
("can_view_sensitive", "可以查看敏感报告"),
("can_export_report", "可以导出报告数据"),
]
这样一来,在视图中就可以这样控制访问:
from django.contrib.auth.decorators import permission_required
@permission_required('myapp.can_view_sensitive')
def view_sensitive_report(request):
...
甚至可以在模板里判断:
{% if user.has_perm('myapp.can_export_report') %}
<button>导出数据</button>
{% endif %}
这才是真正的“权限下沉”到模型层,而不是散落在各个视图里。
URL路由与视图:打造灵活高效的请求处理器
Django的URL系统可以说是全框架最优雅的设计之一。它既支持简洁的路径匹配,又能处理复杂的正则规则,还能实现反向解析,简直是路由界的六边形战士 🛡️
模块化路由设计:告别臃肿的urls.py
很多人一开始就把所有URL塞进主 urls.py ,结果几千行代码挤在一起,根本没法维护。
正确姿势是用 include() 拆分:
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('shop/', include('shop.urls')),
]
然后每个应用自己管自己的路由:
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog' # 命名空间,防止冲突
urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
]
这样做的好处太多了:
- 各团队可以独立开发,互不影响;
- 路由结构清晰,一眼看出模块划分;
- 支持命名空间,模板里用 {% url 'blog:post_list' %} 就不会撞名;
下面是请求分发的全过程:
graph TD
A[用户请求 /blog/post/123/] --> B{根路由匹配}
B --> C[/blog/ 匹配成功]
C --> D[调用 include('blog.urls')]
D --> E{子路由匹配}
E --> F[path('post/<int:post_id>/', ...)]
F --> G[执行 views.post_detail 视图]
G --> H[返回HTTP响应]
整个过程像快递分拣一样高效,层层递进,职责分明。
动态参数捕获:让URL更有意义
静态路由只能做展示页,真正强大的是动态路由。
Django支持两种方式:
1. 简单转换器(推荐)
path('article/<int:id>/', views.article_detail),
path('user/<str:username>/', views.profile_view),
内置转换器包括:
| 转换器 | 匹配规则 | 示例 |
|---|---|---|
str | 非空字符串(不含 / ) | john |
int | 正整数 | 123 |
slug | 字母数字连字符 | hello-world |
uuid | UUID字符串 | a1b2c3d4-... |
path | 可含斜杠的任意字符串 | docs/tutorial/install/ |
2. 正则表达式(复杂场景)
re_path(r'^product/(?P<category>[\w-]+)/(?P<id>[0-9]+)/$', views.product_detail)
命名捕获组 (?P<name>...) 会被自动传给视图函数:
def product_detail(request, category, id):
return HttpResponse(f"Category: {category}, ID: {id}")
SEO友好又语义清晰,完美!
反向解析:彻底告别硬编码URL
还在写 <a href="/blog/post/123/"> ?太危险了!万一哪天改了URL结构,全站链接全废。
Django提供反向解析机制:
from django.urls import reverse
url = reverse('blog:post_detail', kwargs={'post_id': 123})
# 结果:/blog/post/123/
在模板里更方便:
<a href="{% url 'blog:post_list' %}">所有文章</a>
<a href="{% url 'blog:post_detail' post_id=post.id %}">{{ post.title }}</a>
甚至可以在模型里定义绝对路径:
class Post(models.Model):
title = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('blog:post_detail', args=[self.pk])
然后模板里直接调:
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
无论URL怎么变,链接永远正确。这才是专业开发的态度 👍
安全认证体系:保护用户数据的最后一道防线
最后压轴登场的是Django内置的 auth 系统。说实话,这是我见过最完整、最易用的身份认证模块,没有之一。
登录登出:只需几行代码
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.shortcuts import render, redirect
def user_login(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
user = form.get_user()
login(request, user)
return redirect('home')
else:
form = AuthenticationForm()
return render(request, 'login.html', {'form': form})
def user_logout(request):
logout(request)
return redirect('login')
就这么简单!Django自动处理session写入、CSRF防护、密码哈希等所有细节。
密码安全机制揭秘
Django默认使用PBKDF2算法+盐值哈希,即使两个用户密码相同,存储的哈希值也完全不同。
手动操作也很方便:
from django.contrib.auth.hashers import make_password, check_password
hashed = make_password('mypassword')
is_valid = check_password('mypassword', hashed) # True
而且支持多种哈希算法栈,可平滑升级:
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher', # 最安全
'django.contrib.auth.hashers.PBKDF2PasswordHasher', # 默认
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
密码重置全流程
Django连“忘记密码”都想好了:
# urls.py
path('password-reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('reset/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
流程全自动:
1. 用户输入邮箱 → 发送带token的链接
2. 点击链接 → 验证token有效性(有效期3天)
3. 设置新密码 → 更新并清除token
graph TD
A[用户点击“忘记密码”] --> B{输入注册邮箱}
B --> C[系统查找用户]
C -- 找到 --> D[生成唯一Token和URL]
D --> E[通过Email发送重置链接]
E --> F[用户点击链接]
F --> G[验证Token有效性]
G -- 有效 --> H[允许设置新密码]
G -- 过期或无效 --> I[提示错误]
H --> J[更新密码并清除Token]
J --> K[跳转至登录页]
配合邮件后端配置(Gmail/SMTP),几分钟就能上线。
写在最后:Django的魅力在于“约定优于配置”
你看,我们从环境搭建一路讲到认证系统,几乎没有哪里需要“发明轮子”。Django的强大之处就在于: 它已经替你想好了90%的问题,并给出了优雅的解决方案 。
与其花时间造重复的轮子,不如深入理解这套成熟架构的设计哲学。当你真正掌握MTV模式、ORM机制、URL分发、中间件链等工作原理后,你会发现——
开发,原来可以这么轻松 🎉
简介:Django是一款基于Python的高效开源Web框架,广泛用于快速构建安全且可维护的网站。本《Django开发指南中文版》是一份面向初学者与进阶开发者的全面参考资料,系统讲解了从环境搭建、项目结构、模型设计、视图与URL配置、模板系统、表单处理到用户认证、中间件、性能优化及生产部署等核心内容。通过清晰的实践指导和深入的技术解析,帮助开发者掌握Django全栈开发流程,提升项目构建能力,适用于各类Web应用开发场景。
176

被折叠的 条评论
为什么被折叠?



