外观
Django框架-完整的增删改查应用模块
约 5785 字大约 19 分钟
2026-03-24
本节将介绍如何通过手工编写应用中学生信息的增删改查操作,来了解项目中URL路由、视图和模板之间的调度关系。
本节将涵盖以下主题:
创建项目、应用、admin后台管理
应用中的路由配置,以及模板中的调用
手工编写应用中的视图函数(浏览、详情、删除和添加)
手工编写模板文件(数据浏览、表单的提交)
体验完整的应用模块开发过程
功能概述
本章将构建的视图、模板和功能示意图:
整体项目目录结构:
stu_obj/ # 项目根目录
├── manage.py # Django 命令行管理工具
├── stu_obj # 项目配置包(与项目同名的 Python 包)
│ ├── __init__.py # 空文件,标识这是一个Python包
│ ├── settings.py # 项目配置文件
│ ├── urls.py # 项目的URL路由配置
│ ├── asgi.py # ASGI 服务器入口点
│ └── wsgi.py # WSGI 服务器入口点
└── stu_app # stu_app/ 学生管理应用目录(Python包)
├── __init__.py # 空文件,标识这是一个Python包
├── admin.py # 在Django后台管理中注册模型
├── apps.py # stu_app 应用的主要配置
├── migrations # 数据库迁移文件目录(Python包)
├── models.py # 应用的数据模型定义文件
├── tests.py # 应用的测试文件
├── views.py # 应用的视图逻辑,处理HTTP请求并返回响应
└─── templates/ # 模版目录
└── stu_app/ # 当前应用模板目录
├── base.html # 当前应用前端页面父模板
├── stu_list.html # 学生信息列表
├── stu_detail.html # 学生信息详情
├── stu_form.html # 学生表单
└── stu_confirm_delete.html # 删除确认模板- 关于上面完整的静态资源与模板文件的下载:
一、 构建项目前的准备
注:准备Python3.12版本的虚拟环境,并安装Django5.2版本框架。若已完成请跳过。
1. 使用Python3.12的版本:
Django 5.2 支持 Python 3.10、3.11、3.12 和 3.13。接下来我们将使用 Python 3.12。
如果您的 Python 版本低于 3.12,或者您的计算机上未安装 Python,请从https://www.python.org/downloads/下载 Python 3.12并按照说明进行安装。
python3 --version
python -V
# Python 3.12.92. 创建Python虚拟环境:
- 自 Python 3.3 版本起,Python 自带了一个
venv库,用于创建轻量级的虚拟环境。
# 1. 创建虚拟环境
python -m venv my_env
# 2. 激活虚拟环境
# Linux下的激活虚拟环境
#source my_env/bin/activate
# Windows下的激活虚拟环境
.\my_env\Scripts\activate
#3. Linux下的验证激活
# (my_env) $ which python
# Windows下的验证激活
(my_env) PS> Get-Command python
# 4. 停用虚拟环境
(my_env) $ deactivate3. 安装Django
- 作为Python Web框架,Django需要Python,在安装Python同时需要安装pip。
# 在线安装Django,指定版本安装,目前5.2的最新版为5.2.9
python -m pip install django==5.2
# 默认会安装:Django==5.2.9、sqlparse==0.5.4、asgiref==3.11.0 和 tzdata==2025.3
# 可以使用pip list查看
# 检测当前是否安装Django及版本
python -m django --version
5.2.9
# 我们也可以先下载安装包:pip download django=4.2.18 -d ./二、创建项目与构建应用
1. 创建项目 stu_obj
在当前Python虚拟环境中运行以下命令创建 stu_obj 项目:
$ django-admin startproject stu_obj查看startproject自动创建项目结构如下:
stu_obj/ # 外部 stu_obj/ 根目录只是一个项目的容器
├── manage.py # Django 命令行管理工具
├── stu_obj # 项目配置包
│ ├── __init__.py # 空文件,标识 Python 包
│ ├── settings.py # 项目配置文件
│ ├── urls.py # 项目URL路由配置
│ ├── asgi.py # ASGI 服务器入口点
│ └── wsgi.py # WSGI 服务器入口点2. 创建应用程序 stu_app
Django 自带脚手架工具,可以自动生成应用的基本目录结构,这样你就可以专注于编写代码,而不用手动创建目录。
创建应用前,确保你在 manage.py 所在的目录下,然后运行以下命令:
$ cd stu_obj
$ python manage.py startapp stu_app- 这将创建一个目录stu_app,其目录如下。
stu_obj/
├── manage.py
├─── stu_obj
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
└── stu_app # stu_app/ 学生管理应用目录(Python包)
├── __init__.py # 空文件,标识 Python 包
├── admin.py # 在Django后台管理中注册模型
├── apps.py # stu_app 应用的主要配置
├── migrations # 数据库迁移文件目录(Python包)
│ └── __init__.py
├── models.py # 应用的数据模型定义文件(可留空)
├── tests.py # 应用的测试文件
└── views.py # 应用的视图逻辑,处理HTTP请求并返回响应3. 启用网站Admin管理
(1). 项目配置文件的设置
进入 stu_obj/settings.py 文件,进行如下配置:
默认情况下,继续保持使用SQLite数据库,无需额外设置 DATABASES :
# SQLite数据库的连接配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}- 设置时区和语言,让admin后台显示中文版界面:
...
LANGUAGE_CODE = 'zh-hans' # 语言设置
TIME_ZONE = 'Asia/Shanghai' # 时区设置
...(2). 数据迁移
- 运行以下命令实现网站Admin管理的数据结构迁移:
$ python manage.py migrate- 也可执行下面命令来查看数据结构迁移后的效果。
$ python manage.py showmigrations- 具体执行效果如下:
# 执行上面命令后的输出结果
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
# 执行下面命令查看迁移状态,具体如下:
$ python manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
[X] 0012_alter_user_first_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
sessions
[X] 0001_initial(3). 创建管理员用户
首先,我们需要创建一个可以登录后台管理的超级用户。运行以下命令:
$ python manage.py createsuperuser
# 输入您所需的用户名,然后按Enter键。
Username: admin
# 然后将提示您输入所需的电子邮件地址:
Email address: admin@example.com
# 最后一步是输入你的密码(>=8位)。您将被要求输入密码两次,第二次作为第一次的确认
Password: **********
Password (again): *********
Superuser created successfully.(4). 启动开发服务器
默认情况下,Django 后台管理已经启用。启动开发服务器来试试看。
启动开发服务器命令如下:
$ python manage.py runserver
或
$ python manage.py runserver 0.0.0.0:8000现在,打开一个Web浏览器,访问地址: http://127.0.0.1:8000/admin/
使用刚刚创建的账号密码进行登录测试:

三、添加Stu学生信息模型
1. 添加Stu学生信息模型
在我们的stu_app应用程序中,去创建一个stu表信息操作的Model类。
编辑 stu_app/models.py文件,完整学生stu的model类信息如下:
"""
学生数据模型定义
定义了Stu表的字段结构和元数据
"""
from django.db import models
class Stu(models.Model):
"""学生信息模型类"""
id = models.AutoField("学号", primary_key=True) # 学号字段,主键自增
name = models.CharField("姓名", max_length=16) # 姓名字段,最长16字符
age = models.SmallIntegerField("年龄") # 年龄字段,小整数类型
# 性别字段,单字符,M表示男,F表示女
sex = models.CharField(
"性别",
max_length=1,
choices=[('M', '男'), ('F', '女')])
classid = models.CharField("班级", max_length=8) # # 班级,最长8字符
class Meta:
"""模型元数据"""
verbose_name = "学生" # 单数名称
verbose_name_plural = "学生列表" # 复数名称
ordering = ['id'] # 默认按学号升序排序
def __str__(self):
"""对象字符串表示"""
return f"{self.id} - {self.name}"2. 添加学生表单到应用中
- 在 stu_app 应用目录中创建学生表单 forms.py 文件
stu_app/forms.py并添加如下代码。
# stu_app/forms.py
"""
学生表单定义
用于创建和编辑学生信息的前端表单
"""
from django import forms
from .models import Stu
class StuForm(forms.ModelForm):
"""学生信息表单"""
class Meta:
"""表单元数据"""
model = Stu # 关联的模型
fields = ['id', 'name', 'age', 'sex', 'classid'] #表单包含的字段
# 字段标签
labels = {
'id': '学号',
'name': '姓名',
'age': '年龄',
'sex': '性别',
'classid': '班级',
}
# 字段小部件(控件)
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'age': forms.NumberInput(attrs={'class': 'form-control'}),
'sex': forms.Select(attrs={'class': 'form-control'},
choices=[('M', '男'), ('F', '女')]),
'classid': forms.TextInput(attrs={'class': 'form-control'}),
'id': forms.NumberInput(attrs={'class': 'form-control'}),
}表单控件装甲升级
Widget Armor Upgrade (ModelForm `widgets` attrs)
ModelForm Widget Forge
Default: forms.CharField()
|
<input type="text" name="name" />
Django ModelForm 默认生成的前端 <input> 标签是灰头土脸“裸奔”的。然而通过配置内嵌的 widgets 属性并注入 attrs={'class': 'form-control'} 就像在控制台远程为它披上了一层 Bootstrap 的发光装甲,瞬间拥有了现代化的精美 UI 外观。
By default, Django ModelForms generate drab, "naked" `` tags. But by configuring the inner `widgets` dictionary with `attrs={'class': 'form-control'}`, it's like remotely forging a glowing coat of Bootstrap armor onto the plain inputs, instantly granting them a sleek, modern UI appearance.
3. 编辑项目配置文件
- 激活应用,在 stu_obj/settings.py 配置文件的
INSTALLED_APPS列表中添加 stu_app 应用:
# 应用列表
INSTALLED_APPS = [
'django.contrib.admin', # 管理后台
'django.contrib.auth', # 认证系统
'django.contrib.contenttypes', # 内容类型框架
'django.contrib.sessions', # 会话框架
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件管理
'stu_app', # 添加自定义stu_app应用
]4. 应用数据库迁移
# 创建迁移文件,在stu_app/migrations/目录下:
$ python manage.py makemigrations stu_app
Migrations for 'blog':
stu_app/migrations/0001_initial.py
+ Create model stu
# 执行应用迁移
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, stu_app, contenttypes, sessions
Running migrations:
Applying stu_app.0001_initial... OK
# 查看迁移状态
python manage.py showmigrations5. 导入学生信息管理的测试数据
- 在项目的根目录下创建 create_test_data.py 文件,并添加如下代码:
# create_test_data.py
"""
创建测试数据脚本
用于向数据库添加示例学生数据
"""
import os
import django
# 设置Django环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'stu_obj.settings')
django.setup()
from stu_app.models import Stu
def create_test_data():
"""创建测试学生数据"""
# 清空现有数据
Stu.objects.all().delete()
print("已清空现有数据")
# 测试数据列表
test_stus = [
{'id': 1001, 'name': '张三', 'age': 20, 'sex': 'M', 'classid': 'py101'},
{'id': 1002, 'name': '李四', 'age': 21, 'sex': 'M', 'classid': 'py101'},
{'id': 1003, 'name': '王五', 'age': 19, 'sex': 'F', 'classid': 'py102'},
{'id': 1004, 'name': '赵六', 'age': 22, 'sex': 'M', 'classid': 'py102'},
{'id': 1005, 'name': '钱七', 'age': 20, 'sex': 'F', 'classid': 'py103'},
{'id': 1006, 'name': '孙八', 'age': 21, 'sex': 'M', 'classid': 'py103'},
{'id': 1007, 'name': '周九', 'age': 23, 'sex': 'F', 'classid': 'py104'},
{'id': 1008, 'name': '吴十', 'age': 19, 'sex': 'M', 'classid': 'py104'},
{'id': 1009, 'name': '小王', 'age': 20, 'sex': 'M', 'classid': 'py101'},
{'id': 1010, 'name': '小李', 'age': 22, 'sex': 'F', 'classid': 'py102'},
{'id': 1011, 'name': '小刘', 'age': 21, 'sex': 'M', 'classid': 'py104'},
]
# 批量创建学生
created_count = 0
for data in test_stus:
try:
Stu.objects.create(**data)
created_count += 1
except Exception as e:
print(f"创建学生 {data['name']} 失败: {e}")
print(f"成功创建 {created_count} 条学生记录")
if __name__ == "__main__":
create_test_data()- 在终端使用 python 执行此文件,进行学生测试数据的导入。
$ python create_test_data.py
已清空现有数据
成功创建 11 条学生记录批量克隆生产舱
Batch Cloning Vat (Executing `create_test_data.py`)
$ python create_test_data.py
Stu.objects.create(**data)
DATABASE (table: stu_app_stu)
每次手工在后台录入测试数据令人抓狂。通过独立运行一段 Python 脚本,利用极其强大的 create() 方法瞬间遍历循环,我们就如同开启了批量克隆工厂一样,在几秒钟内将大批记录空投进数据库深池中。
Manually entering test data is frustrating. By running an independent Python script that loops through data calling `Stu.objects.create()`, we activate a batch cloning vat that instantly drops massive numbers of records into the database pool in seconds.
6. 将模型添加到管理站点
但是我们自定义的应用还没有出现在后台管理页面上。
接下来,打开 stu_app/admin.py 文件,编辑代码如下:
# 导入Django的admin模块
from django.contrib import admin
# 导入应用中的stu模型
from .models import Stu
# 注册stu模型到Django管理后台
@admin.register(Stu)
# 定义stu模型的管理类
class StuAdmin(admin.ModelAdmin):
# 列表页显示的字段
list_display = ('id', 'name', 'age', 'sex_display', 'classid')
list_filter = ('sex', 'classid') # 列表页右侧过滤条件
search_fields = ('name', 'id', 'classid') # 搜索字段
ordering = ('id',) # 默认排序
def sex_display(self, obj):
"""自定义性别显示"""
return '男' if obj.sex == 'M' else '女'
sex_display.short_description = '性别' # 设置列标题
# 可选:自定义 Admin 站点标题
admin.site.site_header = '学生管理系统' # 登录页面和后台顶部标题
admin.site.site_title = '学生管理后台' # 浏览器标签页标题
admin.site.index_title = '欢迎使用学生管理系统' # 首页标题- 编辑 stu_app/apps.py 应用的主配置文件,添加最后一行代码。
from django.apps import AppConfig
# 定义stu_app应用的配置类
class stu_appConfig(AppConfig):
# 默认名称(不要改这个)
default_auto_field = 'django.db.models.BigAutoField'
name = 'stu_app' # 应用名称(保持与创建时一致)
verbose_name = '学生管理' # 在Django Admin中显示的应用名称启动服务:
$ python manage.py runserver
或
$ python manage.py runserver 0.0.0.0:8000现在,打开一个Web浏览器,访问地址: http://127.0.0.1:8000/admin/
- 后台首页:

- 学生信息列表展示测试数据

- 添加学生信息表单:

- 学生信息后台管理的项目代码:stu_obj.zip
四. 在应用中开发学生信息管理
1. 学生信息浏览
- 编辑 stu_app/views.py 视图文件:
from django.shortcuts import render
from .models import Stu
# Create your views here.
# 查看学生信息路由函数
def stu_list(request):
# 获取所有学生信息
stus = Stu.objects.all()
# 渲染模板
return render(request, 'stu_app/stu_list.html', {'stus': stus})- 创建模板文件: stu_app/templates/stu_app/stu_list.html 代码如下
<!DOCTYPE html>
<html>
<head>
<title>学生信息列表</title>
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: center;
}
</style>
</head>
<body>
<h1>学生信息列表</h1>
<table border="1" width="700">
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>班级</th>
<th>操作</th>
</tr>
{% for stu in stus %}
<tr>
<td>{{ stu.id }}</td>
<td>{{ stu.name }}</td>
<td>{{ stu.age }}</td>
<td>{% if stu.sex == 'M' %}男{% else %}女{% endif %}</td>
<td>{{ stu.classid }}</td>
<td>
<a href="#">详情</a> |
<a href="#">编辑</a> |
<a href="#">删除</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>- 创建 stu_app/urls.py 应用下的路由文件:
# 定义当前应用下的路由
from django.urls import path
from . import views
urlpatterns = [
path('', views.stu_list, name='stu_list'), # 浏览学生信息路由
]- 编辑 stu_obj/urls.py 项目路由文件:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('stu_app.urls')),
]- 浏览效果:
2. 学生信息详情:
- 编辑 stu_app/views.py 视图文件,并追加代码如下:
... ...
# 学生信息详情的路由函数
def stu_detail(request, pk):
# 获取指定学号的学生信息
stu = Stu.objects.get(pk=pk)
# 渲染模板
return render(request, 'stu_app/stu_detail.html', {'stu': stu})- 创建模板文件: stu_app/templates/stu_app/stu_detail.html 代码如下
<!DOCTYPE html>
<html>
<head>
<title>学生详情</title>
<style>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 8px;
text-align: center;
}
</style>
</head>
<body>
<h1>学生详情</h1>
<table border="1" width="400">
<tr>
<th>学号</th>
<td>{{ stu.id }}</td>
</tr>
<tr>
<th>姓名</th>
<td>{{ stu.name }}</td>
</tr>
<tr>
<th>年龄</th>
<td>{{ stu.age }}</td>
</tr>
<tr>
<th>性别</th>
<td>{% if stu.sex == 'M' %}男{% else %}女{% endif %}</td>
</tr>
<tr>
<th>班级</th>
<td>{{ stu.classid }}</td>
</tr>
</table>
<h3>
<a href="{% url 'stu_list' %}">返回学生列表</a>
</h3>
</body>
</html>- 编辑 stu_app/urls.py 应用下的路由文件:
# 定义当前应用下的路由
from django.urls import path
from . import views
urlpatterns = [
path('', views.stu_list, name='stu_list'), # 浏览学生信息路由
path('<int:pk>/', views.stu_detail, name='stu_detail'), # 详情路由
]- 编辑模板文件: stu_app/templates/stu_app/stu_list.html 的【详情】链接代码如下
... ...
<a href="{% url 'stu_detail' stu.id %}">详情</a> |
... ...- 浏览效果:

- 删除学生信息
- 编辑 stu_app/views.py 视图文件,并追加代码如下:
... ...
# 执行学生信息删除的路由函数
def stu_delete(request, pk):
# 获取指定学号的学生信息
stu = Stu.objects.get(pk=pk)
# 删除该学生信息
stu.delete()
# 重定向到学生列表页面
stus = Stu.objects.all()
return render(request, 'stu_app/stu_list.html', {'stus': stus})- 编辑 stu_app/urls.py 应用下的路由文件:
# 定义当前应用下的路由
from django.urls import path
from . import views
urlpatterns = [
path('', views.stu_list, name='stu_list'), # 浏览学生信息路由
path('<int:pk>/', views.stu_detail, name='stu_detail'), # 详情路由
# 删除路由
path('<int:pk>/delete/', views.stu_delete, name='stu_delete'),
]- 编辑模板文件: stu_app/templates/stu_app/stu_list.html 的【详情】链接代码如下
... ...
<a href="{% url 'stu_delete' stu.id %}">删除</a>
... ...- 添加学生信息:
.......
参考代码:stu_obj.zip
五、学生应用中完整代码
- 上面第四节中的模板代码和视图代码比较简单,接下来对其进行优化升级。
1. 导入模板文件:
- 从上面【功能概述】中下载模板文件,在stu_app应用目录下,完成下面模板文件的部署:
└── stu_app # stu_app/ 学生管理应用目录(Python包)
├── __init__.py # 空文件,标识 Python 包
├── admin.py # 在Django后台管理中注册模型
├── apps.py # stu_app 应用的主要配置
├── migrations # 数据库迁移文件目录(Python包)
├── models.py # 应用的数据模型定义文件
├── tests.py # 应用的测试文件
├── views.py # 应用的视图逻辑,处理HTTP请求并返回响应
└─── templates/ # 模版目录
└── stu_app/ # 当前应用模板目录
├── base.html # 当前应用前端页面父模板
├── stu_list.html # 学生信息列表
├── stu_detail.html # 学生信息详情
├── stu_form.html # 学生表单
└── stu_confirm_delete.html # 删除确认模板2. 完整的视图文件
# stu_app/views.py
"""
视图函数定义
处理HTTP请求,返回HTTP响应
"""
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from .models import Stu
from .forms import StuForm
def stu_list(request):
"""
显示学生列表
支持按姓名搜索和按班级过滤
"""
# 获取查询参数
query = request.GET.get('q', '') # 搜索关键词
class_filter = request.GET.get('class', '') # 班级过滤条件
# 获取所有学生
stus = Stu.objects.all()
# 应用搜索过滤:按姓名模糊搜索
if query:
stus = stus.filter(name__icontains=query)
# 应用班级过滤:精确匹配班级
if class_filter:
stus = stus.filter(classid=class_filter)
# 获取所有班级用于过滤下拉框
classes = Stu.objects.values_list('classid', flat=True).distinct()
# 准备上下文数据
context = {
'stus': stus,
'query': query,
'class_filter': class_filter,
'classes': classes,
}
# 渲染模板并返回响应
return render(request, 'stu_app/stu_list.html', context)
def stu_detail(request, pk):
"""
显示学生详细信息
pk: 学生主键(学号)
"""
# 获取学生对象,如果不存在返回404
stu = get_object_or_404(Stu, pk=pk)
# 渲染详情页面
return render(request, 'stu_app/stu_detail.html', {'stu': stu})
def stu_create(request):
"""创建新学生"""
if request.method == 'POST':
# POST请求:处理表单提交
form = StuForm(request.POST)
if form.is_valid():
# 表单验证通过,保存数据
form.save()
messages.success(request, '学生信息添加成功!')
return redirect('stu_list') # 重定向到列表页
else:
# GET请求:显示空表单
form = StuForm()
# 渲染表单页面
return render(request, 'stu_app/stu_form.html', {
'form': form,
'title': '添加学生'
})
def stu_update(request, pk):
"""更新学生信息"""
# 获取要更新的学生对象
stu = get_object_or_404(Stu, pk=pk)
if request.method == 'POST':
# POST请求:处理表单提交
form = StuForm(request.POST, instance=stu)
if form.is_valid():
# 表单验证通过,更新数据
form.save()
messages.success(request, '学生信息更新成功!')
return redirect('stu_detail', pk=stu.id) # 重定向到详情页
else:
# GET请求:显示预填充表单
form = StuForm(instance=stu)
# 渲染表单页面
return render(request, 'stu_app/stu_form.html', {
'form': form,
'title': '编辑学生信息'
})
def stu_delete(request, pk):
"""删除学生"""
# 获取要删除的学生对象
stu = get_object_or_404(Stu, pk=pk)
if request.method == 'POST':
# POST请求:确认删除
stu.delete()
messages.success(request, '学生信息删除成功!')
return redirect('stu_list') # 重定向到列表页
# GET请求:显示确认删除页面
return render(request, 'stu_app/stu_confirm_delete.html', {'stu': stu})雷达锁敌扫描
QuerySet Radar Sweep (`.filter(name__icontains=query)`)
张三 (py101)
李四 (py101)
张小五 (py102)
赵六 (py102)
阿猫 (py103)
张小狗 (py103)
钱七 (py101)
SELECT * FROM stu
Filter Panel
objects.all() 会把所有人捞出来,仿佛雷达上密密麻麻的红点。当你在代码中接入 request.GET 参数并使用 filter() 时,就如同一道探照波扫过:不符合条件的目标迅速被隐去排查掉,符合 icontains 或绝对相等匹配的目标则会被高亮锁定。
`objects.all()` fetches everyone, like a crowded radar screen. Passing `request.GET` parameters into `filter()` acts like a sonar sweep: it instantly dims out the irrelevant targets and brightly illuminates only the records that match your exact or `icontains` queries.
翻新车间与造新厂
Instance Paint Shop (ModelForm `instance=stu` vs No Instance)
GET (Form Init)
NEW DATA
POST (.save())
同一个 StuForm,如何既能“新增条目”又能“修改条目”?秘密就在于 instance 参数!不带实例时,表单就像一张图纸,save() 会凭空造出一辆全新的车(Create);而传入 instance=stu 时,表单就把这辆旧车直接拉进了翻新车间,save() 只会给它重新喷漆,修改原有数据而绝不产生第二辆车(Update)。
How can one `StuForm` handle both insertion and modification? The secret lies in the `instance` argument! Without it, the form acts as a blueprint where `save()` manufactures a brand new vehicle (Create). Passing `instance=stu` drives the existing older vehicle into the paint shop, so `save()` directly modifies and repaints it without accidentally spawning a duplicate record (Update).
防复交空间跃迁门
PRG Pattern (`post -> save -> redirect -> get`)
/stu_list/ (GET Room)
/create/ (POST Room)
User
DATABASE Records: 0
这展示了著名的 PRG 重定向防重复提交模式。如果 POST 数据保存后直接原地停留(渲染),用户一旦按下 F5 刷新,浏览器就会把上一次的 POST “数据炸弹”重新丢一次导致产生数据库沉余垃圾。而执行 redirect() 就像在脚下开了一个虫洞传送门,瞬间把用户吸走扔到安全的 GET 列表房,此时再怎么刷新也是毫无威胁的无状态展示了。
This demonstrates the famous PRG (Post/Redirect/Get) pattern. If you stay on the POST page (render directly) after saving, pressing F5 to refresh will trick the browser into re-submitting the same "data bomb", creating database duplicates. But executing `redirect()` opens a wormhole beneath the user, instantly teleporting them to a safe GET room where refreshing is harmless.
3. 完整的路由文件
- 在应用程序
stu_app目录中的urls.py路由文件:
# stu_app/urls.py
"""
学生管理应用URL配置
定义学生相关的所有URL路径
"""
from django.urls import path
from . import views
urlpatterns = [
# 首页:学生列表
path('', views.stu_list, name='stu_list'),
# 创建学生
path('create/', views.stu_create, name='stu_create'),
# 学生详情
path('<int:pk>/', views.stu_detail, name='stu_detail'),
# 更新学生信息
path('<int:pk>/update/', views.stu_update, name='stu_update'),
# 删除学生
path('<int:pk>/delete/', views.stu_delete, name='stu_delete'),
]- 主URL路由配置: stu_obj/urls.py
"""
项目主URL配置
将不同URL路径映射到对应的视图函数
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# 管理后台URL
path('admin/', admin.site.urls),
# 学生管理应用URL,包含所有学生相关页面
path('', include('stu_app.urls')),
]4. 应用中的模板文件
- 公共父模板文件:stu_app/templates/stu_app/base.html
<!-- stu_app/templates/stu_app/base.html -->
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}学生管理系统{% endblock %}</title>
<!-- 使用CDN引入Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
/* 基础样式 */
body {
padding: 20px 0;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* 导航样式 */
.navbar {
margin-bottom: 20px;
border-radius: 5px;
}
/* 卡片样式 */
.card {
margin-bottom: 15px;
border: 1px solid #dee2e6;
}
.card-header {
background-color: #f8f9fa;
font-weight: bold;
}
/* 按钮样式 */
.btn {
margin-right: 5px;
margin-bottom: 5px;
}
/* 表格样式 */
table {
margin-top: 15px;
}
/* 页脚样式 */
footer {
margin-top: 30px;
padding-top: 15px;
border-top: 1px solid #eee;
text-align: center;
color: #666;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- 主容器 -->
<div class="container">
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'stu_list' %}">
学生管理系统
</a>
<!-- 导航链接 -->
<div class="navbar-nav">
<a class="nav-link" href="{% url 'stu_list' %}">学生列表</a>
<a class="nav-link" href="{% url 'stu_create' %}">添加学生</a>
</div>
</div>
</nav>
<!-- 消息提示 -->
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- 页面标题 -->
<h2 class="my-4">{% block page_title %}{% endblock %}</h2>
<!-- 主要内容区域 -->
<div class="content">
{% block content %}{% endblock %}
</div>
<!-- 页脚 -->
<footer>
<p>© 2024 学生管理系统 | 基于Django 5.2开发</p>
</footer>
</div>
<!-- 引入Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>- 浏览学生信息模板文件:stu_app/templates/stu_app/stu_list.html
<!-- stu_app/templates/stu_app/stu_list.html -->
{% extends 'stu_app/base.html' %}
{% block title %}学生列表{% endblock %}
{% block page_title %}学生信息列表{% endblock %}
{% block content %}
<!-- 搜索和过滤区域 -->
<div class="card">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-6">
<input type="text" class="form-control" name="q"
placeholder="输入姓名搜索..." value="{{ query }}">
</div>
<div class="col-md-4">
<select class="form-control" name="class">
<option value="">所有班级</option>
{% for class in classes %}
<option value="{{ class }}"
{% if class_filter == class %}selected{% endif %}>
{{ class }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">搜索</button>
</div>
</form>
</div>
</div>
<!-- 操作按钮 -->
<div class="my-3">
<a href="{% url 'stu_create' %}" class="btn btn-success">
+ 添加新学生
</a>
</div>
<!-- 学生列表 -->
{% if stus %}
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>班级</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for stu in stus %}
<tr>
<td>{{ stu.id }}</td>
<td>{{ stu.name }}</td>
<td>{{ stu.age }}</td>
<td>{% if stu.sex == 'M' %}男{% else %}女{% endif %}</td>
<td>{{ stu.classid }}</td>
<td>
<a href="{% url 'stu_detail' stu.id %}" class="btn btn-sm btn-info">查看</a>
<a href="{% url 'stu_update' stu.id %}" class="btn btn-sm btn-warning">编辑</a>
<a href="{% url 'stu_delete' stu.id %}" class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- 统计信息 -->
<div class="alert alert-secondary">
共找到 {{ stus|length }} 名学生
{% if class_filter %}(班级: {{ class_filter }}){% endif %}
</div>
{% else %}
<!-- 无数据提示 -->
<div class="alert alert-warning">
{% if query or class_filter %}
没有找到符合条件的学生。
{% else %}
还没有学生信息,请点击"添加新学生"按钮添加。
{% endif %}
</div>
{% endif %}
{% endblock %}- 编辑学生信息模板文件:stu_app/templates/stu_app/stu_detail.html
<!-- stu_app/templates/stu_app/stu_detail.html -->
{% extends 'stu_app/base.html' %}
{% block title %}学生详情 - {{ stu.name }}{% endblock %}
{% block page_title %}学生详细信息{% endblock %}
{% block content %}
<!-- 基本信息卡片 -->
<div class="card">
<div class="card-header">
学生基本信息
</div>
<div class="card-body">
<!-- 两列布局显示信息 -->
<div class="row">
<div class="col-md-6">
<p><strong>学号:</strong> {{ stu.id }}</p>
<p><strong>姓名:</strong> {{ stu.name }}</p>
<p><strong>年龄:</strong> {{ stu.age }}</p>
</div>
<div class="col-md-6">
<p><strong>性别:</strong>
{% if stu.sex == 'M' %}男{% else %}女{% endif %}
</p>
<p><strong>班级:</strong> {{ stu.classid }}</p>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="mt-4">
<a href="{% url 'stu_update' stu.id %}" class="btn btn-warning">编辑信息</a>
<a href="{% url 'stu_delete' stu.id %}" class="btn btn-danger">删除学生</a>
<a href="{% url 'stu_list' %}" class="btn btn-secondary">返回列表</a>
</div>
{% endblock %}- 学生信息表单模板文件:stu_app/templates/stu_app/stu_form.html
<!-- stu_app/templates/stu_app/stu_form.html -->
{% extends 'stu_app/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block page_title %}{{ title }}{% endblock %}
{% block content %}
<!-- 表单卡片 -->
<div class="card">
<div class="card-body">
<form method="post">
{% csrf_token %}
<!-- 显示表单错误 -->
{% if form.errors %}
<div class="alert alert-danger">
请修正以下错误:
<ul>
{% for field, errors in form.errors.items %}
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% endif %}
<!-- 表单字段 -->
<div class="mb-3">
<label for="{{ form.id.id_for_label }}" class="form-label">
{{ form.id.label }}
</label>
{{ form.id }}
</div>
<div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label">
{{ form.name.label }}
</label>
{{ form.name }}
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="{{ form.age.id_for_label }}" class="form-label">
{{ form.age.label }}
</label>
{{ form.age }}
</div>
<div class="col-md-4 mb-3">
<label for="{{ form.sex.id_for_label }}" class="form-label">
{{ form.sex.label }}
</label>
{{ form.sex }}
</div>
<div class="col-md-4 mb-3">
<label for="{{ form.classid.id_for_label }}" class="form-label">
{{ form.classid.label }}
</label>
{{ form.classid }}
</div>
</div>
<!-- 表单按钮 -->
<div class="mt-4">
<button type="submit" class="btn btn-primary">保存</button>
<a href="{% url 'stu_list' %}" class="btn btn-secondary">取消</a>
</div>
</form>
</div>
</div>
{% endblock %}- 删除学生信息确认模板文件:stu_app/templates/stu_app/stu_confirm_delete.html
<!-- stu_app/templates/stu_app/stu_confirm_delete.html -->
{% extends 'stu_app/base.html' %}
{% block title %}删除确认{% endblock %}
{% block page_title %}确认删除学生信息{% endblock %}
{% block content %}
<!-- 确认删除卡片 -->
<div class="card">
<div class="card-body">
<h5 class="card-title">您确定要删除以下学生吗?</h5>
<!-- 学生信息 -->
<div class="alert alert-danger">
<p><strong>学号:</strong> {{ stu.id }}</p>
<p><strong>姓名:</strong> {{ stu.name }}</p>
<p><strong>班级:</strong> {{ stu.classid }}</p>
</div>
<p class="text-danger">注意:此操作不可恢复!</p>
<!-- 删除表单 -->
<form method="post">
{% csrf_token %}
<div class="mt-3">
<button type="submit" class="btn btn-danger">确认删除</button>
<a href="{% url 'stu_detail' stu.id %}" class="btn btn-secondary">取消</a>
</div>
</form>
</div>
</div>
{% endblock %}防误删安全盖
Delete Safety (GET confirm / POST destroy)
ID:1001 (张三)
千万不要让一条带有删除指令的普通 HTML href 链接(GET请求)直接操纵数据库干掉数据,否则爬虫抓取你网页链接时会无意清空你的库表。最佳实践是构建两步验证的安全盖:点击链接只是一次 GET 请求进入渲染警告页面(掀开防误触盖子),唯有真人在随后页面中主动且确切地按下面包屑表单中的提交按钮(POST指令),才会真正销毁数据。
Never allow a plain HTML `href` link (GET request) to directly delete data from the database; otherwise, web crawlers visiting your links could unintentionally wipe out your tables. The best practice is establishing a two-step safety switch: the initial GET request merely displays a confirmation warning page (flipping open the safety cover), while actual deletion strictly requires a human to explicitly submit a POST request on that subsequent form.
模板文件代码:templates.zip
展示效果:

- 添加表单

- 学生详情页面:

- 删除确认页:

- 完整项目代码:stu_obj.zip

