开发环境:

  • Microsoft Windows 10 Enterprise LTSC [Version 10.0.19044.1586], locale zh-CN

  • Python 3.8.10

  • PyCharm 2021.2 (Professional Edition)

  • Visual Studio Code, 64-bit edition (version 1.67.2)


添加新应用

  1. 激活虚拟环境

在项目文件夹中 manage.py 文件所在的位置打开 PowerShell窗口 或其他命令行终端, 使用命令激活虚拟环境

1
.\venv\Scripts\activate
  1. 输入命令, 创建名为 equipment 的应用:
1
python .\manage.py startapp equipment

修改项目配置文件

打开项目的 settings.py 文件, 添加当前应用到 INSTALLED_APPS 列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

'department', # 安装自己添加的新应用
'users', # 安装新添加的应用
'equipment', # 安装新添加的应用
]

定义模型类

打开 equipment/models.py文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.db import models


class Equipment(models.Model):
serial_number = models.CharField(max_length=30, default="", verbose_name="设备编号")
name = models.CharField(max_length=30, verbose_name="设备名称", help_text="设备名称")
equipment_model = models.CharField(max_length=50, default="", verbose_name="设备型号")
desc = models.TextField(blank=True, null=True, verbose_name="备注")
purchase_date = models.DateField(blank=True, null=True, verbose_name="购买日期")
owner = models.CharField(max_length=20, blank=True, null=True, verbose_name="使用人")

class Meta:
verbose_name = "设备管理"
verbose_name_plural = verbose_name

def __str__(self):
return self.serial_number


迁移模型

1
2
3
4
5
6
7
8
9
10
(venv) PS D:\PycharmProjects\djangoProject> python manage.py makemigrations
Migrations for 'equipment':
equipment\migrations\0001_initial.py
- Create model EquipmentType
- Create model Equipment
(venv) PS D:\PycharmProjects\djangoProject> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, department, equipment, sessions, users
Running migrations:
Applying equipment.0001_initial... OK

创建用于添加修改数据的表单类

  1. equipment 文件夹添加名为 forms.py 的文件

  2. 打开 equipment/forms.py 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django import forms
from .models import Equipment


class EquipmentCreateForm(forms.ModelForm):
class Meta:
model = Equipment # 指定表单来自的模型
fields = '__all__' # 包含模型类的中所有字段
# 定义错误信息
error_messages = {
"serial_number": {"required": "设备编号不能为空"},
"equipment_model": {"required": "请输入设备型号"},
}

# 设置各个表单控件显示的外观 和 样式
widgets = {
'serial_number': forms.TextInput(attrs={'class': 'form-control'}),
'name': forms.TextInput(attrs={'class': 'form-control'}),
'equipment_model': forms.TextInput(attrs={'class': 'form-control'}),
'desc': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'owner': forms.TextInput(attrs={'class': 'form-control'}),
'purchase_date': forms.TextInput(attrs={'class': 'form-control'}),
}

添加模板文件

  1. 在项目的 templates 文件夹添加名为 equipment 的新文件夹

  2. templates/equipment 文件夹添加以下几个HTML文件待用:index.html, edit.html, delete.html, detail.html

添加视图类

打开 equipment/views.py 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.views.generic.base import View
from django.contrib.auth.mixins import LoginRequiredMixin
from .forms import EquipmentCreateForm
from .models import Equipment


class EquipmentListView(View):
def get(self, request):
equipments = Equipment.objects.all()
return render(request, 'equipment/index.html', locals())


class EquipmentCreateView(LoginRequiredMixin, View):
login_url = '/login/' # 未登录用户自动跳转的目标url

def get(self, request):
form = EquipmentCreateForm()
return render(request, 'equipment/edit.html', locals())

def post(self, request):
form = EquipmentCreateForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
Equipment.objects.create(
serial_number=cd['serial_number'],
name=cd['name'],
equipment_model=cd['equipment_model'],
desc=cd['desc'],
owner=cd['owner'],
purchase_date=cd['purchase_date'],
)
return HttpResponseRedirect('/equipment/')
else:
error_msg = '新增设备出现异常'
return HttpResponseRedirect('/equipment/')


class EquipmentUpdateView(LoginRequiredMixin, View):
login_url = '/login/' # 未登录用户自动跳转的目标url

def get(self, request, *args, **kwargs):
id = self.kwargs['id'] # 接收名为 id 的参数(与urls.py一致)
data = get_object_or_404(Equipment, pk=id)
form = EquipmentCreateForm(initial={
'serial_number': data.serial_number,
'name': data.name,
'equipment_model': data.equipment_model,
'desc': data.desc,
'owner': data.owner,
'purchase_date': data.purchase_date,
})
return render(request, 'equipment/edit.html', locals())

def post(self, request, *args, **kwargs):
id = self.kwargs['id'] # 接收名为 id 的参数(与urls.py一致)
data = get_object_or_404(Equipment, pk=id)
form = EquipmentCreateForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
data.serial_number = cd['serial_number']
data.name = cd['name']
data.equipment_model = cd['equipment_model']
data.desc = cd['desc']
data.owner = cd['owner']
data.purchase_date = cd['purchase_date']
data.save()
return HttpResponseRedirect('/equipment/')
else:
form = EquipmentCreateForm(initial={
'serial_number': data.serial_number,
'name': data.name,
'equipment_model': data.equipment_model,
'desc': data.desc,
'owner': data.owner,
'purchase_date': data.purchase_date,
})

return render(request, 'equipment/edit.html', locals())


class EquipmentDeleteView(LoginRequiredMixin, View):
login_url = '/login/' # 未登录用户自动跳转的目标url

def get(self, request, *args, **kwargs):
id = self.kwargs['id'] # 接收名为 id 的参数(与urls.py一致)
equipment = get_object_or_404(Equipment, pk=id)
return render(request, 'equipment/delete.html', locals())

def post(self, request, *args, **kwargs):
id = self.kwargs['id'] # 接收名为 id 的参数(与urls.py一致)
equipment = get_object_or_404(Equipment, pk=id)
equipment.delete()
return HttpResponseRedirect('/equipment/')


class EquipmentDetailView(View):
def get(self, request, *args, **kwargs):
id = self.kwargs['id'] # 接收名为 id 的参数(与urls.py一致)
equipment = get_object_or_404(Equipment, pk=id)
return render(request, 'equipment/detail.html', locals())


包含分页功能的设备列表视图类

如果需要使用分页功能,可以 EquipmentListView 替换为以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class EquipmentListView(View):
def get(self, request, *args, **kwargs):
params = self.request.GET.dict()
try:
page = params['page']
except:
page = 1
try:
limit = params['limit']
except:
limit = 10
equipment_list = Equipment.objects.all()
paginator = Paginator(equipment_list, limit) # 对数据进行分页
try:
current_page = paginator.page(page) # 获取 指定页的数据
equipments = current_page.object_list # 将当前页数据转为列表
except PageNotAnInteger:
current_page = paginator.page(1) # 页码不是数字时,返回第一页
equipments = current_page.object_list
except EmptyPage:
current_page = paginator.page(paginator.num_pages)
equipments = current_page.object_list

return render(request, 'equipment/index.html', locals())

路由配置

添加应用内的子路由

  1. equipment 文件夹添加名为 urls.py 的文件

  2. 打开 equipment/urls.py 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.urls import path
from . import views

urlpatterns = [
# 第一个参数是路径, 第二个参数是匹配的视图函数,第三个参数(可选) 命名路由
# 实际路径等于当前 path 和项目urls 中对应 path的拼接
# 完整路径:department/
# 项目中所有 路由项的 path 中的name 值 不能相同
path('', views.EquipmentListView.as_view(), name='equip_index'),
# 完整路径为:department/create/
path('create/', views.EquipmentCreateView.as_view(), name='equip_create'),
path('<int:id>/', views.EquipmentDetailView.as_view(), name='equip_detail'),
path('update/<int:id>/', views.EquipmentUpdateView.as_view(), name='equip_update'),
path('delete/<int:id>/', views.EquipmentDeleteView.as_view(), name='equip_delete'),

]

添加应用子路由到项目路由中

打开项目的路由文件 djangoProject/urls.py, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.contrib import admin
from django.urls import path, include
import department.views
from users.views import LoginView, LogoutView, SignupView # 导入视图

urlpatterns = [
path('admin/', admin.site.urls),
path('department/', include('department.urls')),
path('equipment/', include('equipment.urls')), # 添加设备管理的子路由
path('', department.views.home),

# 用户注册、登录相关的路由
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('signup/', SignupView.as_view(), name='signup'),
]

修改网站的导航栏

打开 templates/nav.html 文件,添加到 equipment 应用的导航菜单, 编辑文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!-- 导航菜单栏 -->
<nav class="navbar navbar-expand-sm bg-light mb-4">
<div class="container d-flex w-100">
<h2>我的网站</h2>
<div class="flex-grow-1"></div>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/department/">组织构架管理</a>
</li> <li class="nav-item">
<a class="nav-link" href="/equipment/">设备管理</a>
</li>
{% if request.user.is_authenticated %}

<li class="nav-item">
<div class="nav-link" href="#">
当前用户: {{ request.user.username }}
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout/">注销 </a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/login/">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup/">注册</a>
</li>
{% endif %}
</ul>
</div>
</nav>

修改模板文件

编辑设备列表显示页

打开 templates/equipment/index.html 文件,编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
{#  继承指定的模板文件 layout.html  #}
{% extends "layout.html" %}

{% block title %} 设备管理 {% endblock %}

{# 内容为替换到 母版页 中对应的位置 #}
{% block content %}
{% include "nav.html" %}
{# container:让内部元素左右留出一定的空间,居中, #}
{# flex-grow-1:让元素占用弹性布局内所有的剩余空间 #}
<div class="container flex-grow-1">
{# my-3:m 表示 margin 外边距, y 表示 y轴、垂直方向的两个边; 数据3表示间距的大小 #}
<div class="d-flex my-3">
<h3 class="flex-grow-1">设备信息</h3>
<a class="btn btn-outline-success" href="{% url 'equip_create' %}">新增</a>
</div>

<table class="table table-striped table-hover">
<tr>
<th>设备编号</th>
<th>设备名称</th>
<th>设备型号</th>
<th>购买日期</th>
<th>使用人</th>
<th>操作</th>
</tr>
{% for item in equipments %}
<tr class="align-middle">
<td>{{ item.serial_number }}</td>
<td>
<a class="text-decoration-none" href="{% url 'equip_detail' item.id %}">
{{ item.name }}
</a>
</td>
<td>
{{ item.equipment_model }}
</td>
<td>{{ item.purchase_date }}</td>
<td>{{ item.owner }}</td>
<td style="width: 140px;">
<a class="btn btn-outline-primary" href="{% url 'equip_update' item.id %}">修改</a>
<a class="btn btn-outline-danger" href="{% url 'equip_delete' item.id %}">删除</a>
</td>
</tr>
{% endfor %}
</table>


<ul class="pagination">
{# current_page 为视图中使用的变量名 #}
{% if current_page.has_previous %}
<li class="page-item">
<a class="page-link"
href="/equipment/?page={{ current_page.previous_page_number }}">前一页</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">前一页</a>
</li>
{% endif %}
{% for item in paginator.page_range %}
<li class="page-item">
<a class="page-link"
href="/equipment/?page={{ item }}">{{ item }}</a></li>
{% endfor %}
{% if current_page.has_next %}
<li class="page-item">
<a class="page-link"
href="/equipment/?page={{ current_page.next_page_number }}">下一页</a></li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">下一页</a>
</li>
{% endif %}
</ul>


</div>
{% include "footer.html" %}
{% endblock %}

编辑新建和修改设备页

打开 templates/equipment/edit.html 文件,编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
{#  继承指定的模板文件 layout.html  #}
{% extends "layout.html" %}

{% block title %} 设备管理 {% endblock %}

{# 内容为替换到 母版页 中对应的位置 #}
{% block content %}
{% include "nav.html" %}
{# container:让内部元素左右留出一定的空间,居中, #}
{# flex-grow-1:让元素占用弹性布局内所有的剩余空间 #}
<div class="container flex-grow-1 d-flex flex-column">
{# my-3:m 表示 margin 外边距, y 表示 y轴、垂直方向的两个边; 数据3表示间距的大小 #}
<div class="d-flex my-3">
<h3 class="flex-grow-1">
{% if form.name.value %}
修改设备信息
{% else %}
添加新设备
{% endif %}
</h3>
</div>

<form method="post">
{% csrf_token %}
<div class="row mb-3">
<div class="col-lg-6">
<label class="form-label">设备编号</label>
{{ form.serial_number }}
</div>
<div class="col-lg-6">
<label class="form-label">设备名称</label>
{{ form.name }}
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">设备型号</label>
{{ form.equipment_model }}
</div>
</div>
<div class="row mb-3">
<div class="col-lg-6">
<label class="form-label">购买日期</label>
{{ form.purchase_date }}
</div>
<div class="col-lg-6">
<label class="form-label">使用人</label>
{{ form.owner }}
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">备注</label>
{{ form.desc }}
</div>
</div>
<div class="mb-3">
<a class="btn btn-outline-primary" href="/equipment/">返回</a>
<button type="submit" class="btn btn-success">提交</button>
</div>
</form>

</div>

{% include "footer.html" %}

{% endblock %}

编辑确认设备删除页

打开 templates/equipment/delete.html 文件,编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
{#  继承指定的模板文件 layout.html  #}
{% extends "layout.html" %}

{% block title %} 设备管理 {% endblock %}

{# 内容为替换到 母版页 中对应的位置 #}
{% block content %}
{% include "nav.html" %}
{# container:让内部元素左右留出一定的空间,居中, #}
{# flex-grow-1:让元素占用弹性布局内所有的剩余空间 #}
<div class="container flex-grow-1 d-flex flex-column">
{# my-3:m 表示 margin 外边距, y 表示 y轴、垂直方向的两个边; 数据3表示间距的大小 #}
<div class="d-flex my-3">
<h3 class="flex-grow-1">
删除当前设备
</h3>
</div>

<form method="post">
{% csrf_token %}
<div class="row mb-3">
<div class="col-lg-6">
<label class="form-label">设备编号:</label>
{{ equipment.serial_number }}
</div>
<div class="col-lg-6">
<label class="form-label">设备名称:</label>
{{ equipment.name }}
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">设备型号:</label>
{{ equipment.equipment_model }}
</div>
</div>
<div class="row mb-3">
<div class="col-lg-6">
<label class="form-label">购买日期:</label>
{{ equipment.purchase_date }}
</div>
<div class="col-lg-6">
<label class="form-label">使用人:</label>
{{ equipment.owner }}
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">备注:</label>
{{ equipment.desc }}
</div>
</div>
<div class="mb-3">
<a class="btn btn-outline-primary" href="/equipment/">返回</a>
<button type="submit" class="btn btn-danger">确认删除设备</button>
</div>
</form>

</div>

{% include "footer.html" %}

{% endblock %}

编辑设备详细页

打开 templates/equipment/detail.html 文件,编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{#  继承指定的模板文件 layout.html  #}
{% extends "layout.html" %}

{% block title %} 设备管理 {% endblock %}

{# 内容为替换到 母版页 中对应的位置 #}
{% block content %}
{% include "nav.html" %}
{# container:让内部元素左右留出一定的空间,居中, #}
{# flex-grow-1:让元素占用弹性布局内所有的剩余空间 #}
<div class="container flex-grow-1 d-flex flex-column">
{# my-3:m 表示 margin 外边距, y 表示 y轴、垂直方向的两个边; 数据3表示间距的大小 #}
<div class="d-flex my-3">
<h3 class="flex-grow-1">
设备详细信息
</h3>
</div>

<div>
<div class="row mb-3">
<div class="col-lg-6">
<label class="form-label">设备编号</label>
{{ equipment.serial_number }}
</div>
<div class="col-lg-6">
<label class="form-label">设备名称</label>
{{ equipment.name }}
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">设备型号</label>
{{ equipment.equipment_model }}
</div>
</div>
<div class="row mb-3">
<div class="col-lg-6">
<label class="form-label">购买日期</label>
{{ equipment.purchase_date }}
</div>
<div class="col-lg-6">
<label class="form-label">使用人</label>
{{ equipment.owner }}
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label">备注</label>
{{ equipment.desc }}
</div>
</div>
<div class="mb-3">
<a class="btn btn-outline-primary" href="/equipment/">返回</a>
</div>
</div>

</div>

{% include "footer.html" %}

{% endblock %}

参考资料:

  1. Django v4.0 中文文档

  2. Django入门与实践教程

  3. Bootstrap5 中文手册

===END===