级别管理
完成级别管理的增删改查
# 级别列表
# -- 动态菜单
NB_MENU = {
"ADMIN": [
{
"text": "用户信息1",
"icon": "fa-calendar-plus-o",
"children": [
{"text": "级别管理1", "url": "/level/list/", "name": "level_list"},
]
},
{...},
],
"CUSTOMER": [...]
}
# -- 权限校验
NB_PERMISSION_PUBLIC = {
"home": {"text": "主页", 'parent': None},
"logout": {"text": "注销", 'parent': None},
}
NB_PERMISSION = {
"ADMIN": {
"level_list": {"text": "级别列表", "parent": None},
"level_add": {"text": "新建级别", "parent": "level_list"},
},
"CUSTOMER": {...}
}
urlpatterns = [
path('login/', account.login, name='login'),
path('sms/login/', account.sms_login, name='sms_login'),
path('sms/send/', account.sms_send, name='sms_send'),
path('logout/', account.logout, name='logout'),
path('home/', account.home, name='home'),
path('level/list/', level.level_list, name='level_list'),
path('level/add/', level.level_add, name='level_add'),
]
[进行级别列表的展示]
在level_list视图函数中查询Level表中的数据,传递给level_list.html模版.
(level_list.html继承了layout.html母版) 在level_list.html中加入表格,使用模版语法-循环加载出每一条数据.
>注意:
记得在settings配置文件中加上对应的权限!!配置后,路径导航也会自动生成!
★ 不仅是级别列表,编辑级别、删除级别等都要记得在配置文件中加权限.
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
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
# 新建级别
1> 我们通常会使用 ModelForm做表结构的CURD;
2>若只是单独的对用户请求进行校验, 或者校验后有做复杂的sql操作, 再或者Form中的字段跟数据库中的不一致,还需手动写,
通常使用Form!
# 使用Form
简单回顾下,Form的使用!
关键代码如下:
from django import forms
from django.shortcuts import render, redirect
from django.urls import reverse
from web import models
class LevelForm(forms.Form):
title = forms.CharField(
label="标题",
required=True,
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "请输入标题!",
"style": "width:10%;"
},
)
)
percent = forms.CharField(
label="折扣",
required=True,
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "请输入折扣!",
"style": "width:25%;"
},
),
help_text="填入0-100整数表示百分比,例如:90表示90%"
)
def level_list(request):
queryset = models.Level.objects.filter(active=1)
return render(request, "level_list.html", {"queryset": queryset})
def level_add(request):
if request.method == "GET":
form = LevelForm()
return render(request, "form.html", {"form": form})
form = LevelForm(data=request.POST)
if not form.is_valid():
return render(request, "form.html", {"form": form})
models.Level.objects.create(**form.cleaned_data)
return redirect(reverse("level_list"))
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
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
代码分析如下:
在LevelForm(forms.Form)里,定义字段.
在视图函数level_add里,对LevelForm类进行实例化,传递给模版form.html 那么在模版里可以对该实例化对象进行循环展示定义的字段.
form.html中注意几点:
1> 使用子绝父相,可以避免有错误信息,显示时影响布局.
2> 注意label标签跟input标签的关系!!要想实现点击label标签,对应的input标签获取光标.
需要在label标签里写上for属性,其值为input标签中id属性的值.
<label for="{{ field.id_for_label }}"> ★★★★★★★★★★★★★★★★★★★★★★
3> 可以加入提示信息 但我们一般会加个判断.
4> 点击`保存`按钮,以post请求提交.若不写action,会提交到当前显示的页面所在的url!
5> 不希望浏览器帮我们进行一些表单校验,加上novalidate.
6> 以表单的形式提交,解决csrf的错误,加上 {% csrf_token %}
{% extends 'layout.html' %}
{% block content %}
<form method="post" novalidate>
{% for field in form %}
{% csrf_token %}
<div class="form-group" style="position: relative;margin-bottom: 25px">
<label for="{{ field.id_for_label }}">
{{ field.label }}
{% if field.help_text %}
<span style="font-weight: 300;color: lightgray;">
({{ field.help_text }})
</span>
{% endif %}
</label>
{{ field }}
<span style="color: lightcoral;position: absolute;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">保 存</button>
</form>
{% endblock %}
提交表单数据后,在视图函数level_add中注意:
表单校验通过后,存入数据库, 若是这样写: models.Level.objects.create(**form.cleaned_data)
得保证,LevelForm类里定义的字段名要与Level模型类里的字段名一样!且字段的个数也要相同.否则,添加失败!
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
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
# 使用ModelForm
相较于Form. 使用ModelForm可以不用写字段,直接使用model里的!! 保存操作也很方便.
注意:
基于models.Level表在LevelModelForm中自动生成字段
1> models.Level字段的类型在forms.ModelForm中是有一一对应的!
2> forms.ModelForm字段的属性 <--> models.Level字段的属性
- label 对应 verbose_name ★★★★★★★★★★★★★★★★★★★★★★★
- required默认为True/不为空,若需要其为False/想为空,设置 null=True,blank=True ★★★★★★★★★★★★★★★★
- help_text 对应 help_text
3> 当往后做添加更新等操作时,无需自己拿到用户输入的值,然后手动操作对应的数据库.
直接执行语句, form.save()即可.
required 是否必填
label 注释信息
error_messages 报错信息
initial 默认值
widget 控制标签属性和样
class LevelModelForm(forms.ModelForm):
class Meta:
model = models.Level
fields = ['title', 'percent'] # fields = "__all__"
# -- 使用widgets插件
widgets = {
"title": forms.TextInput(attrs={
"class": "form-control",
"placeholder": "请输入标题!",
"style": "width:10%;"
}),
"percent": forms.TextInput(attrs={
"class": "form-control",
"placeholder": "请输入折扣!",
"style": "width:25%;"
}),
}
def level_add(request):
if request.method == "GET":
# form = LevelForm()
form = LevelModelForm()
return render(request, "form.html", {"form": form})
# form = LevelForm(data=request.POST)
form = LevelModelForm(data=request.POST)
if not form.is_valid():
return render(request, "form.html", {"form": form})
# models.Level.objects.create(**form.cleaned_data) # 使用ModelForm,这样操作也是成功的!
form.save()
return redirect(reverse("level_list"))
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
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
# 自定义widgets插件
思考: 无论在使用Form和ModelForm时, 想要让页面好看, 就需要将每个字段的widgets插件中给它设置class样式等.
字段很多的话? 也一个一个写吗?不至于不至于.
解决方案: 无论是Form还是ModelForm, 在里面写入__init__
方法. (此处以ModelForm为例)
class LevelModelForm(forms.ModelForm):
class Meta:
model = models.Level
fields = ['title', 'percent'] # fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.fields是一个字典,类似于 {'title':forms.CharField()对象,"percent":forms.CharField()对象}
for name, field in self.fields.items():
field.widget.attrs['class'] = "form-control"
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
if name == "title":
field.widget.attrs['style'] = "width:10%;"
else:
field.widget.attrs['style'] = "width:25%;"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
进一步优化!! 注意: super关键字根据类的MRO"类的继承关系"顺序, 从下到上去找.
class BootStrapForm: # -- 通常会将其扔到utils目录中,用到它时导入即可.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = "form-control"
field.widget.attrs['placeholder'] = "请输入{}".format(field.label)
if name == "title":
field.widget.attrs['style'] = "width:10%;"
else:
field.widget.attrs['style'] = "width:25%;"
class LevelModelForm(BootStrapForm, forms.ModelForm):
class Meta:
model = models.Level
fields = ['title', 'percent']
"""
实例化LevelModelForm类时,会执行__init__方法,LevelModelForm类中没有,就先去BootStrapForm中找
BootStrapForm类的__init__方法中 super().__init__(*args, **kwargs) 根据LevelModelForm类mro的继承链
会去forms.ModelForm中找__init__
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 编辑级别
编辑级别的页面,用的是跟“新建级别” 页面 一样的 "form.html", 但不同的一点在于, 编辑时, 要先展示出默认值!!
展示默认值
Form或modelForm显示默认值,都可以通过 initial 属性实现.
比如:
form = LevelForm(initial={'title': "xxx"})
form = LevelModelForm(initial={'title': "xxx"})
若使用LevelModelForm,还有种更简便的方式.
举个例子:
level_object = models.Level.objects.filter(id=pk).first()
form = LevelModelForm(instance=level_object)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
关键代码如下:
def level_edit(request, pk):
level_object = models.Level.objects.filter(id=pk, active=1).first()
# 获取页面,显示默认值
if request.method == "GET":
form = LevelModelForm(instance=level_object)
return render(request, 'form.html', {"form": form})
# 获取数据 + 表单校验
# !! 此处,写了instance=level_object,后续的save操作是对其更新;不写的话,就是新增.
form = LevelModelForm(data=request.POST, instance=level_object)
if not form.is_valid():
# 校验失败,让用户看错误页面
return render(request, 'form.html', {"form": form})
# form.instance.percent = 10
# 校验成功,根据pk将指定的级别数据进行更新
form.save()
return redirect(reverse('level_list'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 删除级别
此处就简单的点击删除按钮就删除啦.. 后续再做处理.
注意: 删除级别是逻辑删除, 不是物理删除!!!
def level_delete(request, pk):
# models.Level.objects.filter(active=1, id=pk).delete() # 物理删除
models.Level.objects.filter(id=pk).update(active=0) # 逻辑删除
return redirect(reverse('level_list'))
1
2
3
4
2
3
4
# 小节
Q: Form和ModelForm提供的功能有什么不一样?!
A: ModelForm 比 Form 更加适合增删改查!!
[Form]
编写字段,生成HTML标签.
在字段里可以做widget插件的设置,为对应生成的HTML标签添加class属性、style属性等.
显示出页面,用户填写表单后提交,可以做表单的验证.通过继续往下走,不通过做相应的错误提示.
[ModelForm]
★ Form有的ModelForm都有/都可以!!
比较优秀的两点!(只有ModelForm才有的,Form没有)
- 可以不编写字段,直接引用Model表中的字段,自动生成!
- 保存 (新增、更新)
<添加>
form = LevelModelForm(data=request.POST)
form.save()
<更新>
form = LevelModelForm(data=request.POST,instance=对象/表中的某条记录)
form.save()
<补充>
在页面上,只显示了数据表中的一部分字段.用户填写传递到后端后,验证通过,直接save()进行新增操作数据库肯定会报错.
因为字段不够,数据库的某些字段不能为null.
举个例子,Level表有title、percent两个字段!新增页面展示在前端的只有title字段!
form = LevelModelForm(data=request.POST)
form.instance.percent = 10
form.save()
举个例子,Level表有title、percent两个字段!更新页面展示在前端的只有title字段!
这样做,是ok的!!因为更新操作,不需要所有字段! -- 只会更新title字段.
form = LevelModelForm(data=request.POST,instance=对象/表中的某条记录)
form.save()
但我们也可以在更新之前,手动操作下. -- 悄无声息的在后台暗箱操作,只要更新就将percent字段值改为10.
form = LevelModelForm(data=request.POST,instance=对象/表中的某条记录)
form.instance.percent = 10
form.save()
<注意> 多扩展的字段对save操作不会有任何影响,save操作只会拿自己需要的字段!!
为啥需要扩写字段:可用于密码修改时,必须 密码与确认密码 一致,才能进行save操作.
[ModelForm]
1> 注意(假设表中有3个字段active、title、percent):下面三者的适用场景
- fields = ['title', 'percent'] # 指定,可以控制页面中字段展示的先后顺序!!
- fields = "__all__" # 所有
- exclude = ["active"] # 除了
2> 可以扩写字段
举个例子,用户表只有password,往后我们创建用户时,还需要确定密码的字段.就需要我们扩写
class LevelModelForm(BootStrapForm, forms.ModelForm):
xxx = forms.CharField(label='xxx')
class Meta:
model = models.Level
fields = ['title', 'xxx', 'percent', ] # 若指定字段的方式的话,记得将扩展的字段一并写进去
3> 可以重写字段.
先要知道,无论是使用Form组件,自己编写的字段,还是使用ModelForm自动生成的字段
其字段的widget插件默认是输入框TextInput
举个例子,title我想要其在页面展示多选框,那么就得重写.
Ps:重写的语法跟Form组件里编写字段的一样!所以,想要定义某个字段的widget插件的内容也可以通过重写来实现!
class LevelModelForm(BootStrapForm, forms.ModelForm):
title = forms.ChoiceField(label='xxx', choices=((1, "xxx"), (2, "xxxxxx")))
class Meta:
model = models.Level
fields = ['title', 'percent']
-- 常见的插件
★ 有两种方式定义插件!!一种是在自定义的ModelForm的自个儿编写的字段中,一种是在Meta中.
widget=forms.TextInput # 输入框
widget=forms.PasswordInput # 密码框
widget=forms.Select # 下拉框
widget=forms.RadioSelect # 单选框
widget=forms.CheckboxInput # 多选框
widget=forms.Textarea # 多行文本框
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
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