开发环境:

  • 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)


自己实现表单添加组织构架

在项目 templates/department/ 文件夹添加用于以下几个新的HTML文件待用: edit.html, detail.html, delete.html,

打开 department/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
from django.shortcuts import render, redirect, get_object_or_404
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from .models import Department # 添加模型类的导入


def index(request):
departments = Department.objects.all()
# 调用并传递数据到指定的模板文件
return render(request, 'department/index.html', {'departments': departments})


def create(request):
return render(request, 'department/edit.html')


def update(request, dep_id):
return render(request, 'department/edit.html')


def delete(request, dep_id):
return render(request, 'department/delete.html')


def detail(request, dep_id):
return render(request, 'department/detail.html')

添加路由时, 找不到目标函数会报错, 所以这里先添加几个没有具体功能的函数

添加新功能的路由

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

1
2
3
4
5
6
7
8
9
10
11
from django.urls import path
from . import views

urlpatterns = [
path('', views.index, name='index'),
path('create/', views.create, name='create'),
path('<int:dep_id>/', views.detail, name='detail'),
path('update/<int:dep_id>/', views.update, name='update'),
path('delete/<int:dep_id>/', views.delete, name='delete')
]

<int:dep_id>: 表示该路由接收一个名为 dep_idint 类型的参数

跳转到新页面

打开 templates/department/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
{% extends "layout.html" %}

{% block title %}部门管理{% endblock %}

{% block content %}
{% include "nav.html" %}
<div class="container flex-grow-1">
<div class="my-2 d-flex">
<h3 class="flex-grow-1">组织构架</h3>
<a class="btn btn-outline-success"
href="{% url 'create' %}">新增</a>
</div>

<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>类别</th>
<th>所属</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in departments %}
<tr>
<td>{{ item.id }}</td>
<td>
<a class="text-decoration-none"
href="{% url 'detail' item.id %}">
{{ item.title }}</a>
</td>
<td>
{% if item.type == 'department' %}
部门
{% else %}
公司
{% endif %}
</td>
<td>{{ item.parent.title }}</td>
<td width="120px">
<a class="btn btn-sm btn-outline-primary"
href="{% url 'update' item.id %}">编辑</a>
<a class="btn btn-sm btn-outline-danger"
href="{% url 'delete' item.id %}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

{% include "footer.html" %}
{% endblock %}

修改说明: 在表格右上解添加了一个按钮外观的 新增 超链接; 表格行右边的两个按钮也修改为 超链接

{% url 'create' %}: 此处的 create 表示在 路由表中 urlpatternsname 的值

{% url 'update' item.id %}: item.id 为传递到路由的参数

实现新增函数

打开 department/views.py 文件, 编辑 create 函数的代码如下所示:

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
def create(request):
if request.method == 'POST':
_title = request.POST['title'] # 接收表单数据
_type = request.POST['type']
_description = request.POST['description']
_parentId = request.POST['parent']
# 查找是否已经存在同名部门
dep = Department.objects.filter(title=_title)
if dep:
pass
else:
# '------' 与表单中对应下拉框的第一个值要相同
if _parentId == '------':
_parent = None
else:
# 根据id 找出对应的部门
_parent = Department.objects.filter(pk=_parentId).first()
# 添加 新 记录
Department.objects.create(
title=_title,
type=_type,
description=_description,
parent=_parent)
return redirect(index) # 重定向
else:
departments = Department.objects.all() # 查询已有的全部部门
# 显示新增页面(新增和修改使用同一个页面)
return render(request, 'department/edit.html', {'departments': departments})

实现新增界面

打开 templates/department/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
{% extends "layout.html" %}
{% block title %}组织构架{% endblock %}
{% block content %}
{% include "nav.html" %}
<div class="container flex-grow-1">
<h3>新增组织构架</h3>
<form class="row justify-content-md-center" method="POST">
{% csrf_token %}
<div class="mb-3 col-md-8">
<label for="parent" class="form-label">上级部门</label>
<select class="form-select" name="parent">
<option>------</option>
{% for item in departments %}
<option
value="{{ item.id }}"
{% if item.title == department.parent.title %}
selected
{% endif %}
>{{ item.title }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3 col-md-8">
<label for="title" class="form-label">名称</label>
<input type="text" class="form-control" name="title"
placeholder="输入名称" value="{{ department.title }}">
</div>
<div class="mb-3 col-md-8">
<label for="type" class="form-label">类型</label>
<select class="form-select" name="type">
<option value="department"
{% if department.type == 'deprtment' %}selected{% endif %}
>部门
</option>
<option value="firm"
{% if department.type == 'firm' %}selected{% endif %}
>公司
</option>
</select>
</div>
<div class="mb-3 col-md-8">
<label for="description" class="form-label">描述</label>
<textarea rows="3" class="form-control" name="description"
placeholder="说明">{{ department.description }}</textarea>
</div>
<div class="mb-3 col-md-8 d-flex flex-row-reverse">
<button type="submit" class="btn btn-sm btn-outline-success">保存</button>
</div>
</form>
</div>

{% include "footer.html" %}
{% endblock %}

表单中 name 属性的值要与接收数据的函数 request.POST['title'] 中的名字(例如这里的 title)相同

运行、预览

启动项目, 点击跳转到 新增 页面, 输入新部门数据

保存 按钮后, 添加数据成功, 重定向到部门列表

使用表单类

自定义的表单没有数据验证功能, 在实现开发时可以使用 Django Forms API 来增强表单的功能

Django 使用两种类型的 formforms.Formforms.ModelFormForm 类是通用的表单实现。我们可以使用它来处理与应用程序 model 没有直接关联的数据。ModelFormForm 的子类,它与 model 类相关联。

添加表单类

users 文件夹新建名为 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
25
26
27
28
29
30
31
32
33
from django import forms
from .models import Department


class DepartmentUpdateForm(forms.ModelForm):
parent = forms.ModelChoiceField(
label='上级部门',
required=False,
queryset=Department.objects.all(),
widget=forms.Select(attrs={'class': 'form-select'})
)
title = forms.CharField(
label='名称',
required=True,
max_length=60,
widget=forms.TextInput(attrs={'class': 'form-control'})
)
type = forms.ChoiceField(
label='类型',
choices=(('department', '部门'), ('firm', '公司')),
widget=forms.Select(attrs={'class': 'form-select'}),
)
description = forms.CharField(
label='描述',
required=False,
max_length=200,
widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
)

class Meta:
model = Department
fields = ['parent', 'title', 'type', 'description']

forms.ModelChoiceField: 用于对 modelsForekey(外键字段) 进行渲染的下拉框

forms.CharField: 渲染为输入框, 属性 widget=forms.TextInput表示单行文本框; widget=forms.Textarea() 表示多行文本框

forms.ChoiceField: 可以渲染为 单选框(widget=forms.RadioSelect()), 或 下拉框(widget=forms.Select())

required=False: 是否为必填项

attrs={'class': 'form-control', 'rows': 3}: 用于给渲染出来的表单控件添加属性, 'class': 'form-control' 为添加CSS, 'rows': 3为指定文本框的行数

修改新增部门的视图函数

打开 department/views.py文件, 修改 create 函数如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def create(request):
if request.method == 'POST':
form = DepartmentUpdateForm(request.POST)
if form.is_valid():
# form.cleaned_data 为表单接收到的数据
cd = form.cleaned_data
Department.objects.create(
title=cd['title'],
type=cd['type'],
description=cd['description'],
parent=cd['parent'])
return redirect(index) # 重定向
else:
error_msg = '新增部门出现异常'
return render(request, 'department/edit.html', locals())
else:
form = DepartmentUpdateForm()
# departments = Department.objects.all() # 查询已有的全部部门
return render(request, 'department/edit.html', locals())

修改新增界面

打开 templates/department/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
{% extends "layout.html" %}
{% block title %}组织构架{% endblock %}
{% block content %}
{% include "nav.html" %}
<div class="container flex-grow-1">
<h3>
{% if form.title.value %}
修改组织构架
{% else %}
新增组织构架
{% endif %}
</h3>
<form class="row justify-content-md-center" method="POST">
{% csrf_token %}
<div class="text-danger text-center"> {{ error_msg }}</div>
<div class="mb-3 col-md-8">
<label for="parent" class="form-label">上级部门</label>
{{ form.parent }}
</div>
<div class="mb-3 col-md-8">
<label for="title" class="form-label">名称</label>
{{ form.title }}
</div>
<div class="mb-3 col-md-8">
<label for="type" class="form-label">类型</label>
{{ form.type }}
</div>
<div class="mb-3 col-md-8">
<label for="description" class="form-label">描述</label>
{{ form.description }}
</div>
<div class="mb-3 col-md-8 d-flex flex-row-reverse">
<button type="submit" class="btn btn-sm btn-outline-success">保存</button>
</div>
</form>
</div>

{% include "footer.html" %}
{% endblock %}

运行、预览

再次点击跳转到 新增 页面, 输入新部门数据

保存 按钮后, 添加数据成功, 重定向到部门列表

实现修改功能

打开 department/views.py文件, 修改 update 函数如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def update(request, dep_id):
if request.method == 'POST':
# 查找对应id的数据
department = get_object_or_404(Department, pk=dep_id)
form = DepartmentUpdateForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
department.title = cd['title']
department.type = cd['type']
department.description = cd['description']
department.parent = cd['parent']
department.save()
return redirect(index) # 重定向
else:
data = get_object_or_404(Department, pk=dep_id)
form = DepartmentUpdateForm(initial={
'title': data.title,
'type': data.type,
'description': data.description,
'parent': data.parent
})
return render(request, 'department/edit.html', locals())

新增修改 使用同一个模板文件, 模板中的 <form></form> 标签不设置 action 属性, 当点击提交按钮时, 数据会回传到 启动该模板 的函数.

删除组织构架

打开 department/views.py文件, 修改 delete 函数如以下代码:

1
2
3
4
5
6
7
8
def delete(request, dep_id):
if request.method == 'GET':
data = get_object_or_404(Department, pk=dep_id)
return render(request, 'department/delete.html', {'department': data})
else:
data = get_object_or_404(Department, pk=dep_id)
data.delete()
return redirect(index)

打开 templates/department/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
{% extends "layout.html" %}
{% block title %}组织构架{% endblock %}
{% block content %}
{% include "nav.html" %}
<div class="container flex-grow-1">
<h3>删除记录</h3>
<form class="row justify-content-md-center" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{ department.id }}">
<div class="mb-3 col-md-8">
<label for="parent" class="form-label">上级部门:</label>
{{ department.parent }}
</div>
<div class="mb-3 col-md-8">
<label for="title" class="form-label">名称:</label>
{{ department.title }}
</div>
<div class="mb-3 col-md-8">
<label for="type" class="form-label">类型:</label>
{% if item.type == 'department' %}
部门
{% else %}
公司
{% endif %}
</div>
<div class="mb-3 col-md-8">
<label for="description" class="form-label">描述:</label>
{{ department.description }}
</div>
<div class="mb-3 col-md-8 d-flex flex-row-reverse">
<button type="submit" class="btn btn-sm btn-outline-danger">删除</button>
<a class="btn btn-sm btn-outline-success mx-2" href="{% url 'index' %}">放弃</a>
</div>
</form>
</div>

{% include "footer.html" %}
{% endblock %}

进入删除页面

查看详细内容

打开 department/views.py文件, 修改 detail 函数如以下代码:

1
2
3
def detail(request, dep_id):
data = get_object_or_404(Department, pk=dep_id)
return render(request, 'department/detail.html', {'department': data})

打开 templates/department/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
{% extends "layout.html" %}
{% block title %}组织构架{% endblock %}
{% block content %}
{% include "nav.html" %}
<div class="container flex-grow-1">
<h3>详细信息</h3>
<div class="row justify-content-md-center">
<div class="mb-3 col-md-8">
<label for="parent" class="form-label">上级部门:</label>
{{ department.parent }}
</div>
<div class="mb-3 col-md-8">
<label for="title" class="form-label">名称:</label>
{{ department.title }}
</div>
<div class="mb-3 col-md-8">
<label for="type" class="form-label">类型:</label>
{% if item.type == 'department' %}
部门
{% else %}
公司
{% endif %}
</div>
<div class="mb-3 col-md-8">
<label for="description" class="form-label">描述:</label>
{{ department.description }}
</div>
<div class="mb-3 col-md-8 d-flex flex-row-reverse">
<a class="btn btn-sm btn-outline-primary mx-2" href="{% url 'update' department.id %}">修改</a>
<a class="btn btn-sm btn-outline-success mx-2" href="{% url 'index' %}">返回</a>
</div>
</div>
</div>
{% include "footer.html" %}
{% endblock %}

在组织架构列表点击 名称 进入详细内容页

分页功能

打开 department/views.py文件, 编辑 index 函数的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def index(request):
"""
分页查询时URL格式:/department/?page=1&limit=10
"""
page = request.GET.get('page') # 当前页码, 缺少时为第1页
limit = request.GET.get('limit') # 每页大小
if not limit:
limit = 2 # 设置没有 limit 参数时每页的大小, 2 是为了方便测试
department_list = Department.objects.all()
paginator = Paginator(department_list, limit)

try:
current_page = paginator.page(page)
departments = current_page.object_list
except PageNotAnInteger:
current_page = paginator.page(1)
departments = current_page.object_list
except EmptyPage:
current_page = paginator.page(paginator.num_pages)
departments = current_page.object_list
# 调用并传递数据到指定的模板文件
# return render(request, 'department/index.html', {'departments': departments})
return render(request, 'department/index.html', locals())

locals(): 返回一个包含 当前作用域 里面的所有变量和它们的值的字典

打开 templates/department/index.html 文件, 在 </table> 标签后加入 Bootstrap5 分页组件, 代码如下:

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
<ul class="pagination">
{# current_page 为视图中使用的变量名 #}
{% if current_page.has_previous %}
<li class="page-item">
<a class="page-link"
href="/department/?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="/department/?page={{ item }}">{{ item }}</a></li>
{% endfor %}
{% if current_page.has_next %}
<li class="page-item">
<a class="page-link"
href="/department/?page={{ current_page.next_page_number }}">下一页</a></li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">下一页</a>
</li>
{% endif %}
</ul>

分页效果预览

完整代码参考

视图文件代码

department/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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from django.shortcuts import render, redirect, get_object_or_404
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.http import HttpResponse, HttpResponseRedirect
from .models import Department # 添加模型类的导入
from .forms import DepartmentUpdateForm
from django.contrib.auth.decorators import login_required


def home(request):
return HttpResponseRedirect('/department/')


def index(request):
"""
分页查询时URL格式:/department/?page=1&limit=10
"""
page = request.GET.get('page') # 当前页码, 缺少时为第1页
limit = request.GET.get('limit') # 每页大小
if not limit:
limit = 10 # 设置没有 limit 参数时每页的大小, 2 是为了方便测试
department_list = Department.objects.all()
paginator = Paginator(department_list, limit)

try:
current_page = paginator.page(page)
departments = current_page.object_list
except PageNotAnInteger:
current_page = paginator.page(1)
departments = current_page.object_list
except EmptyPage:
current_page = paginator.page(paginator.num_pages)
departments = current_page.object_list
# 调用并传递数据到指定的模板文件
# return render(request, 'department/index.html', {'departments': departments})
return render(request, 'department/index.html', locals())


# def create(request):
# if request.method == 'POST':
# _title = request.POST['title'] # 接收表单数据
# _type = request.POST['type']
# _description = request.POST['description']
# _parentId = request.POST['parent']
# # 查找是否已经存在同名部门
# dep = Department.objects.filter(title=_title)
# if dep:
# pass
# else:
# # '------' 与表单中对应下拉框的第一个值要相同
# if _parentId == '------':
# _parent = None
# else:
# # 根据id 找出对应的部门
# _parent = Department.objects.filter(pk=_parentId).first()
# # 添加 新 记录
# Department.objects.create(
# title=_title,
# type=_type,
# description=_description,
# parent=_parent)
# return redirect(index) # 重定向
# else:
# departments = Department.objects.all() # 查询已有的全部部门
# # 显示新增页面(新增和修改使用同一个页面)
# return render(request, 'department/edit.html', {'departments': departments})

@login_required(login_url='/login/')
def create(request):
if request.method == 'POST':
form = DepartmentUpdateForm(request.POST)
if form.is_valid():
# form.cleaned_data 为表单接收到的数据
cd = form.cleaned_data
Department.objects.create(
title=cd['title'],
type=cd['type'],
description=cd['description'],
parent=cd['parent'])
return redirect(index) # 重定向
else:
error_msg = '新增部门出现异常'
return render(request, 'department/edit.html', locals())
else:
form = DepartmentUpdateForm()
# departments = Department.objects.all() # 查询已有的全部部门
return render(request, 'department/edit.html', locals())


@login_required(login_url='/login/')
def update(request, dep_id):
if request.method == 'POST':
# 查找对应id的数据
department = get_object_or_404(Department, pk=dep_id)
form = DepartmentUpdateForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
department.title = cd['title']
department.type = cd['type']
department.description = cd['description']
department.parent = cd['parent']
department.save()
return redirect(index) # 重定向
else:
data = get_object_or_404(Department, pk=dep_id)
form = DepartmentUpdateForm(initial={
'title': data.title,
'type': data.type,
'description': data.description,
'parent': data.parent
})
return render(request, 'department/edit.html', locals())


@login_required(login_url='/login/')
def delete(request, dep_id):
if request.method == 'GET':
data = get_object_or_404(Department, pk=dep_id)
return render(request, 'department/delete.html', {'department': data})
else:
data = get_object_or_404(Department, pk=dep_id)
data.delete()
return redirect(index)


def detail(request, dep_id):
data = get_object_or_404(Department, pk=dep_id)
return render(request, 'department/detail.html', {'department': data})


模板文件代码

templates/department/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
81
82
83
84
85
{% extends "layout.html" %}

{% block title %}组织构架{% endblock %}

{% block content %}
{% include "nav.html" %}
<div class="container flex-grow-1">
<div class="my-2 d-flex">
<h3 class="flex-grow-1">组织构架</h3>
<a class="btn btn-outline-success"
href="{% url 'create' %}">新增</a>
</div>

<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>类别</th>
<th>所属</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in departments %}
<tr>
<td class="align-middle">{{ item.id }}</td>
<td class="align-middle">
<a class="text-decoration-none"
href="{% url 'detail' item.id %}">
{{ item.title }}</a>
</td>
<td class="align-middle">
{# get_type_display: 显示 type 字段的 choices 的名字 #}
{{ item.get_type_display }}
{# {% if item.type == 'department' %}#}
{# 部门#}
{# {% else %}#}
{# 公司#}
{# {% endif %}#}
</td>
<td class="align-middle">{{ item.parent.title }}</td>
<td class="align-middle" width="120px">
<a class="btn btn-sm btn-outline-primary"
href="{% url 'update' item.id %}">编辑</a>
<a class="btn btn-sm btn-outline-danger"
href="{% url 'delete' item.id %}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>

<ul class="pagination">
{# current_page 为视图中使用的变量名 #}
{% if current_page.has_previous %}
<li class="page-item">
<a class="page-link"
href="/department/?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="/department/?page={{ item }}">{{ item }}</a></li>
{% endfor %}
{% if current_page.has_next %}
<li class="page-item">
<a class="page-link"
href="/department/?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 %}


参考资料:

  1. Django v4.0 中文文档

  2. Django入门与实践教程

  3. Bootstrap5 中文手册

===END===