昨日内容回顾
1. 权限系统的流程?2. 权限的表有几个?3. 技术点中间件sessionorm- 去重- 去空inclusion_tagfilter有序字典settings配置引入静态文件url别名namespace路由分发构造数据结构ModelForm组件应用adminicon爬虫mark_safe下载文件
一、客户管理之 编辑权限(二)
下载代码:
链接:https://pan.baidu.com/s/1xYkyWFwmOZIFK4cqWWUizg 密码:zzs9
效果如下:

上面的权限管理,需要构造一个字典
构造字典
权限管理数据结构如下:
"1": {"title": "客户列表","url": "/customer/list/","name": "customer_list","children": [{"title": "添加客户","url": "/customer/add/","name": "customer_add"}{"title": "编辑客户","url": "/customer/edit/","name": "customer_edit"}],},
这里的1指的是权限id。也就Permission表的主键
修改 rbac—>urls.py,增加路径test,用来做测试。
from django.conf.urls import urlfrom rbac.views import role,menu,permissionurlpatterns = [url(r'^role/list/$', role.role_list,name='role_list'),url(r'^role/add/$', role.role_add,name='role_add'),url(r'^role/edit/(?P<rid>\d+)/$', role.role_edit,name='role_edit'),url(r'^menu/list/$', menu.menu_list,name='menu_list'),url(r'^menu/add/$', menu.menu_add,name='menu_add'),url(r'^menu/edit/(?P<pk>\d+)/$', menu.menu_edit,name='menu_edit'),url(r'^menu/del/(?P<pk>\d+)/$', menu.menu_del,name='menu_del'),url(r'^permission/edit/(?P<pk>\d+)/$', menu.permission_edit, name='permission_edit'),url(r'^permission/del/(?P<pk>\d+)/$', menu.permission_del, name='permission_del'),url(r'^permission/test/$', permission.test,name='test'),]
进入目录 rbac—>views,创建文件permission.py
from django.shortcuts import render, redirect,HttpResponsefrom rbac import modelsdef test(request):permission_queryset = models.Permission.objects.all().values("id","title","url","name","parent_id")root_permission_dict = {}for item in permission_queryset:print(item['parent_id'])# 判断parent_id不为空时,也就是能做菜单的urlif not item['parent_id']:# 以id为keyroot_permission_dict[item['id']] = {'title':item['title'],'url': item['url'],'name': item['name'],'children':[] # 子列表默认为空}return HttpResponse('ok')
访问测试页面: http://127.0.0.1:8000/rbac/permission/test/,效果如下:

查看Pycharm控制台输出:
None1111None777None
再筛选不能做菜单的url,也就是parent_id不能为空的
from django.shortcuts import render, redirect,HttpResponsefrom rbac import modelsdef test(request):permission_queryset = models.Permission.objects.all().values("id","title","url","name","parent_id")root_permission_dict = {}# 先添加二级菜单for item in permission_queryset:# print(item['parent_id'])# 判断parent_id不为空时,也就是能做菜单的urlif not item['parent_id']:# 以id为keyroot_permission_dict[item['id']] = {'title':item['title'],'url': item['url'],'name': item['name'],'children':[] # 子列表默认为空}# 再添加非菜单的urlfor item in permission_queryset:# 获取pidparent_id = item['parent_id']if parent_id: # 判断pid是否存在# 追加到parent_id对应的children中root_permission_dict[parent_id]['children'].append({'title': item['title'],'url': item['url'],'name': item['name'],})# 查看最终数据for root in root_permission_dict.values():print(root['title'],root['name'],root['url'])for node in root['children']:print('--->',node['title'],node['name'],node['url'])return HttpResponse('ok')
刷新页面,查看Pycharm控制台输出:
客户列表 customer_list /customer/list/---> 添加客户1 customer_add /customer/add/---> 编辑客户 customer_edit /customer/edit/(?P<cid>\d+)/---> 删除客户 customer_del /customer/del/(?P<cid>\d+)/---> 批量导入客户 customer_import /customer/import/角色列表 role /role/账单列表 payment_list /payment/list/---> 添加账单 payment_add /payment/add/---> 编辑账单 payment_edit /payment/edit/(?P<pid>\d+)/---> 删除账单 payment_del /payment/del/(?P<pid>\d+)/
点击菜单里面的超链接,url会有mid

这个时候,需要修改ORM查询语句
修改 rbac—>views—>menu.py
from django.shortcuts import render, redirect,HttpResponsefrom django.urls import reversefrom rbac import modelsfrom django import formsfrom django.utils.safestring import mark_safeICON_LIST = [['fa-address-book', '<i aria-hidden="true" class="fa fa-address-book"></i>'],['fa-address-book-o', '<i aria-hidden="true" class="fa fa-address-book-o"></i>'],['fa-address-card', '<i aria-hidden="true" class="fa fa-address-card"></i>'],['fa-address-card-o', '<i aria-hidden="true" class="fa fa-address-card-o"></i>'],['fa-adjust', '<i aria-hidden="true" class="fa fa-adjust"></i>'],]for item in ICON_LIST:item[1] = mark_safe(item[1])def menu_list(request):"""权限管理和分配:param request::return:"""menus = models.Menu.objects.all()mid = request.GET.get('mid')root_permission_list = []if mid:# 找到可以成为菜单的权限 + 某个菜单下的permissions = models.Permission.objects.filter(menu_id=mid).order_by('-id')else:# 找到可以成为菜单的权限permissions = models.Permission.objects.filter(menu__isnull=False).order_by('-id')root_permission_queryset = permissions.values('id', 'title', 'url', 'name', 'menu__title')root_permission_dict = {}for item in root_permission_queryset:item['children'] = []root_permission_list.append(item)root_permission_dict[item['id']] = item# 找到可以成为菜单的权限的所有子权限node_permission_list = models.Permission.objects.filter(parent_id__in=permissions).order_by('-id').values('id','title','url','name','parent_id')for node in node_permission_list:pid = node['parent_id']root_permission_dict[pid]['children'].append(node)return render(request,'rbac/menu_list.html',{'menu_list': menus,'root_permission_list': root_permission_list,'mid': mid})class MenuModelForm(forms.ModelForm):class Meta:model = models.Menufields = ['title','icon']widgets = {# title字段添加class'title': forms.TextInput(attrs={'class': 'form-control'}),'icon': forms.RadioSelect(choices=ICON_LIST)}error_messages = {'title': {'required': '菜单名称不能为空'},'icon': {'required': '请选择图标'}}def menu_add(request):if request.method == "GET":form = MenuModelForm()else:form = MenuModelForm(request.POST)if form.is_valid():form.save()return redirect(reverse('rbac:menu_list'))return render(request,"rbac/menu_add.html",{'form':form})def menu_edit(request,pk):obj = models.Menu.objects.filter(id=pk).first()if request.method =='GET':# instance参数,如果存在那么save()方法会更新这个实例,否则会创建一个新的实例form = MenuModelForm(instance=obj)return render(request,'rbac/menu_edit.html',{'form':form})else:form = MenuModelForm(data=request.POST,instance=obj)if form.is_valid():form.save()return redirect(reverse('rbac:menu_list'))else:return render(request, 'rbac/menu_edit.html', {'form': form})def menu_del(request, pk):"""删除菜单:param request::param pk::return:"""obj = models.Menu.objects.filter(id=pk).delete()# print(obj)return redirect(reverse('rbac:menu_list'))class PermissionModelForm(forms.ModelForm):class Meta:model = models.Permissionfields = '__all__'help_texts = {'pid': '父级权限,无法作为菜单的权限才需要选择。','menu': "选中,表示该权限可以作为菜单;否则,不可做菜单。"}widgets = {'title': forms.TextInput(attrs={'class': "form-control", 'placeholder': '请输入权限名称'}),'url': forms.TextInput(attrs={'class': "form-control", 'placeholder': '请输入URL'}),'name': forms.TextInput(attrs={'class': "form-control", 'placeholder': '请输入URL别名'}),'pid': forms.Select(attrs={'class': "form-control", 'placeholder': '请选择父级权限'}),'menu': forms.Select(attrs={'class': "form-control", 'placeholder': '请选择菜单'}),}def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)def clean(self):menu = self.cleaned_data.get('menu')pid = self.cleaned_data.get('pid')if menu and pid:self.add_error('menu', '菜单和根权限同时只能选择一个')def permission_edit(request, pk):"""编辑权限:param request::return:"""obj = models.Permission.objects.filter(id=pk).first()if not obj:return HttpResponse('权限不存在')if request.method == 'GET':form = PermissionModelForm(instance=obj)else:form = PermissionModelForm(request.POST, instance=obj)if form.is_valid():form.save()return redirect(reverse('rbac:menu_list'))return render(request, 'rbac/permission_edit.html', {'form': form})def permission_del(request, pk):"""删除权限:param request::return:"""models.Permission.objects.filter(id=pk).delete()return redirect(request.META['HTTP_REFERER'])
修改 rbac—>templates—>rbac—>menu_list.html
{% extends 'layout.html' %}{% block css %}<style>tr.root {background-color: #f1f7fd;}.menu-area tr.active {background-color: #f1f7fd;border-left: 3px solid #fdc00f;}#menuBody td[mid], #permissionBody > .root .title {cursor: pointer;}#permissionBody tr.root {background-color: #f1f7fd;}td a {margin: 0 2px;cursor: pointer;}table {font-size: 12px;}.panel-body {font-size: 12px;}.panel-body .form-control {font-size: 12px;}</style>{% endblock %}{% block content %}<div style="padding: 10px"><h1>菜单管理</h1><div class="col-sm-3 menu-area"><div class="panel panel-default"><!-- Default panel contents --><div class="panel-heading"><i class="fa fa-book" aria-hidden="true"></i>菜单管理<a href="{% url 'rbac:menu_add' %}" class="btn btn-xs btn-success right"style="padding: 2px 8px;margin: -3px;"><i class="fa fa-plus-circle" aria-hidden="true"></i>新建</a></div><!-- Table --><table class="table"><th>名称</th><th>图标</th><th>选项</th><tbody id="menuBody">{% for row in menu_list %}{% if row.id|safe == mid %}<tr class="active">{% else %}<tr>{% endif %}<td><a href="?mid={{ row.id }}">{{ row.title }}</a></td><td><!-- 展示图标 --><i class="fa {{ row.icon }}" aria-hidden="true"></i></td><td><!-- 编辑和删除 --><a href="{% url 'rbac:menu_edit' pk=row.id %}"><i class="fa fa-edit"aria-hidden="true"></i></a><span style="padding: 0 5px;display: inline-block">|</span><a href="{% url 'rbac:menu_del' pk=row.id %}"><i class="fa fa-trash-o"aria-hidden="true"></i></a></td></tr>{% endfor %}</tbody></table></div></div><div class="col-sm-9"><div class="panel panel-default"><!-- Default panel contents --><div class="panel-heading"><i class="fa fa-cubes" aria-hidden="true"></i>权限管理<a href="{% url 'rbac:menu_add' %}" class="btn btn-xs btn-success right"style="padding: 2px 8px;margin: -3px;"><i class="fa fa-plus-circle" aria-hidden="true"></i>新建</a></div><!-- Table --><table class="table"><th>名称</th><th>URL</th><th>CODE</th><th>菜单</th><th>所属菜单</th><th>选项</th><tbody>{% for row in root_permission_list %}{# 判断菜单id是否为空 #}{% if row.menu__title %}<tr class="root">{% else %}<tr style="display: none">{% endif %}<td><i class="fa fa-caret-down" aria-hidden="true"></i>{{ row.title }}</td><td>{{ row.url }}</td><td>{{ row.name }}</td><td>{# 判断菜单id是否为空 #}{% if row.menu__title %}是{% endif %}</td><td>{# 判断菜单id是否为空 #}{% if row.menu__title %}{{ row.menu__title }}{% endif %}</td><td><!-- 编辑和删除 --><a href="{% url 'rbac:permission_edit' pk=row.id %}"><i class="fa fa-edit"aria-hidden="true"></i></a><span style="padding: 0 5px;display: inline-block">|</span><a href="{% url 'rbac:permission_del' pk=row.id %}"><i class="fa fa-trash-o"aria-hidden="true"></i></a></td></tr><tbody class="two">{% for child in row.children %}<tr pid="{{ row.id }}"><td class="title">{{ child.title }}</td><td>{{ child.url }}</td><td>{{ child.name }}</td><td></td><td>{% if child.menu__title %}{{ row.menu__title }}{% endif %}</td><td><a href="{% url 'rbac:permission_edit' pk=row.id %}" style="color:#333333;"><i class="fa fa-edit" aria-hidden="true"></i></a><a href="{% url 'rbac:permission_del' pk=row.id %}" style="color:#d9534f;"><i class="fa fa-trash-o" aria-hidden="true"></i></a></td></tr>{% endfor %}</tbody>{% endfor %}</tbody></table></div></div></div>{% endblock %}{% block js %}<script>//默认隐藏{#$('.aa').prev().next().hide();#}$('.two').prev().click(function () {console.log('点击了');//卷帘门切换$(this).next().slideToggle(300);})</script>{% endblock %}
访问页面:http://127.0.0.1:8000/rbac/menu/list,效果如下:

点击左边的信息管理,效果如下:

二、Django表单集合Formset
什么是Formset
Formset(表单集)是多个表单的集合。Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息
为什么要使用Django Formset
我们先来下看下Django中不使用Formset情况下是如何在同一页面上一键提交2张或多张表单的。我们在模板中给每个表单取不同的名字,如form1和form2(如下面代码所示)。注: form1和form2分别对应forms.py里的Form1()和Form2()。
<form >{{ form1.as_p }}{{ form2.as_p }}</form>
用户点击提交后,我们就可以在视图里了对用户提交的数据分别处理。
if request.method == 'POST':form1 = Form1( request.POST,prefix="form1")form2 = Form2( request.POST,prefix="form2")if form1.is_valid() or form2.is_valid():passelse:form1 = Form1(prefix="form1")form2 = Form2(prefix="form2")
这段代码看似并不复杂,然而当表单数量很多或不确定时,这个代码会非常冗长。我们希望能控制表单的数量,这是我们就可以用Formset了。
Formset的分类
Django针对不同的formset提供了3种方法: formset_factory, modelformset_factory和inlineformset_factory。我们接下来分别看下如何使用它们。
如何使用formset_factory
对于继承forms.Form的自定义表单,我们可以使用formset_factory。我们可以通过设置extra和max_num属性来确定我们想要展示的表单数量。注意: max_num优先级高于extra。比如下例中,我们想要显示3个空表单(extra=3),但最后只会显示2个空表单,因为max_num=2。
from django import formsclass BookForm(forms.Form):name = forms.CharField(max_length=100)title = forms.CharField()pub_date = forms.DateField(required=False)# forms.py - build a formset of booksfrom django.forms import formset_factoryfrom .forms import BookForm# extra: 想要显示空表单的数量# max_num: 表单显示最大数量,可选,默认1000BookFormSet = formset_factory(BookForm, extra=3, max_num=2)
在视图文件views.py里,我们可以像使用form一样使用formset。
# views.py - formsets example.
from .forms import BookFormSet
from django.shortcuts import render
def manage_books(request):
if request.method == 'POST':
formset = BookFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
pass
else:
formset = BookFormSet()
return render(request, 'manage_books.html', {'formset': formset})
模板里可以这样使用formset
<form action=”.” method=”POST”>
{{ formset }}
</form>
也可以这样使用
<form method="post">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
关于其他更多信息,请参考链接:
https://blog.csdn.net/weixin_42134789/article/details/81505983
举例
新建一个django项目,注意:django版本为1.11

修改urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.index),
url(r'^index/', views.index),
]
修改views.py
from django.shortcuts import render
# Create your views here.
def index(request):
return render(request,"index.html")
在templates目录下创建文件index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide{
display: none;
}
</style>
</head>
<body>
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table border="1">
<tr>
<th>用户名</th>
<th>密码</th>
<th>邮箱</th>
</tr>
<tr>
<td><input type="text" name="user" placeholder="请输入用户名"></td>
<td><input type="text" name="pwd" placeholder="请输入密码"></td>
<td><input type="text" name="email" placeholder="请输入邮箱"></td>
</tr>
<tr>
<td><input type="text" name="user" placeholder="请输入用户名"></td>
<td><input type="text" name="pwd" placeholder="请输入密码"></td>
<td><input type="text" name="email" placeholder="请输入邮箱"></td>
</tr>
<tr>
<td><input type="text" name="user" placeholder="请输入用户名"></td>
<td><input type="text" name="pwd" placeholder="请输入密码"></td>
<td><input type="text" name="email" placeholder="请输入邮箱"></td>
</tr>
</table>
<input type="submit" value="提交">
</form>
</body>
</html>
启动项目,访问页面: http://127.0.0.1:8000/

点击提交按钮,需要一次性写入3条信息到数据库中
如果使用post提交,那么后台需要做form表单验证。之前学习的form组件,一次验证一行数据。
使用form
修改views.py
from django.shortcuts import render
from django import forms
# Create your views here.
class UserForm(forms.Form):
user = forms.CharField()
pwd = forms.CharField()
email = forms.CharField()
def index(request):
if request.method == 'GET':
form = UserForm()
return render(request,"index.html",{'form':form})
修改index.html
由于一个form对象,只能生成一行。那么3行,得需要复制3次
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide{
display: none;
}
</style>
</head>
<body>
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table border="1">
<tr>
<th>用户名</th>
<th>密码</th>
<th>邮箱</th>
</tr>
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
</table>
<input type="submit" value="提交">
</form>
</body>
</html>
刷新页面,效果如下:

假设不知道有多少条数据呢?得使用formset
使用formset
修改views.py
extra设置展示的表单数量,如果是0,则不会生成!
min_num=1,第一个form表单,必须是完整的。否则提示This field is required
from django.shortcuts import render
from django import forms
# Create your views here.
class UserForm(forms.Form):
user = forms.CharField()
pwd = forms.CharField()
email = forms.CharField()
def index(request):
# 生成一个类,它是form集合。extra设置展示的表单数量
# min_num至少提交一个完整的form表单
UserFormSet = forms.formset_factory(UserForm,extra=2,min_num=1)
if request.method == 'GET':
formset = UserFormSet()
return render(request,"index.html",{'formset':formset})
formset = UserFormSet(request.POST)
if formset.is_valid():
print(formset.cleaned_data) # 验证通过的数据
print('验证成功')
return render(request, "index.html", {'formset': formset})
修改index.html
它必须使用2层for循环。第一次是fromset,它是一个集合,集合每一个元素都是form对象。
第二次是form对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide{
display: none;
}
</style>
</head>
<body>
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table border="1">
<tr>
<th>用户名</th>
<th>密码</th>
<th>邮箱</th>
</tr>
{% for form in formset %}
<tr>
{% for field in form %}
<td>{{ field }}{{ field.errors.0 }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="提交">
</form>
</body>
</html>
关闭网页,重新访问,效果如下:

它有3个form对象,分别是extra和min_num设置的总和。min_num就是第一个!
直接提交一个空的表单,它会提示This field is required

提交数据

查看Pycharm控制台输出:
[{'pwd': '11', 'email': '11', 'user': '11'}, {'pwd': '22', 'email': '22', 'user': '22'}, {}]
验证成功
可以看出formset.cleaned_data是一个列表,列表的每一个元素,都是字典。
由于第3行没有填写,它是一个空字典
批量保存数据
修改models.py
from django.db import models
# Create your models here.
class User(models.Model):
"""
用户表
"""
username = models.CharField(verbose_name='用户名', max_length=32)
password = models.CharField(verbose_name='密码', max_length=32)
email = models.EmailField(verbose_name='邮箱', max_length=32)
def __str__(self):
return self.username
使用2个命令生成表
python manage.py makemigrations
python manage.py migrate
修改views.py,使用**ModelForm做添加,并且对数据做校验**
from django.shortcuts import render,HttpResponse
from django import forms
from app01 import models
# Create your views here.
class UserForm(forms.ModelForm):
class Meta:
model = models.User # user表
fields = '__all__' # 所有字段
def index(request):
# 生成一个类,它是form集合。extra设置展示的表单数量
# min_num至少提交一个完整的form表单
UserFormSet = forms.formset_factory(UserForm,extra=2,min_num=1)
if request.method == 'GET':
formset = UserFormSet()
return render(request,"index.html",{'formset':formset})
formset = UserFormSet(request.POST)
if formset.is_valid():
# print(formset.cleaned_data) # 验证通过的数据
flag = False # 标志位
for row in formset.cleaned_data:
if row: # 判断数据不为空
# print(row) # 它是一个字典
# **表示将字典扩展为关键字参数
res = models.User.objects.create(**row)
if res: # 判断返回信息
flag = True
if flag:
return HttpResponse('添加成功')
else:
return HttpResponse('添加失败')
return render(request, "index.html", {'formset': formset})
注意:表示将字典扩展为关键字参数**
问题来了,为什么要用**呢?
先来看一下,使用create的添加示例
models.User.objects.create(username='xiao',password='123',email='123@qq.com')
由于row是一个字典,而create需要关键字参数。那么就可以完美解决这个问题!论Python基础的重要性!**
重启django,刷新页面。
如果添加不合法的数据,会有提示!

输入2条正确的值

提示添加成功

查看用户表,发现有2条数据了!

批量修改数据
修改urls.py,增加路径
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.index),
url(r'^index/', views.index),
url(r'^batch_update/', views.batch_update),
]
修改views.py,增加视图函数
from django.shortcuts import render,HttpResponse
from django import forms
from app01 import models
# Create your views here.
class UserForm(forms.ModelForm):
id = forms.IntegerField()
class Meta:
model = models.User # user表
fields = '__all__' # 所有字段
def index(request):
# 生成一个类,它是form集合。extra设置展示的表单数量
# min_num至少提交一个完整的form表单
UserFormSet = forms.formset_factory(UserForm,extra=2,min_num=1)
if request.method == 'GET':
formset = UserFormSet()
return render(request,"index.html",{'formset':formset})
formset = UserFormSet(request.POST)
if formset.is_valid():
# print(formset.cleaned_data) # 验证通过的数据
flag = False # 标志位
for row in formset.cleaned_data:
if row: # 判断数据不为空
# print(row) # 它是一个字典
# **表示将字典扩展为关键字参数
res = models.User.objects.create(**row)
if res: # 判断返回信息
flag = True
if flag:
return HttpResponse('添加成功')
else:
return HttpResponse('添加失败')
return render(request, "index.html", {'formset': formset})
def batch_update(request):
"""
批量更新
:param request:
:return:
"""
queryset = models.User.objects.all().values() # 查询表的所有记录
# extra=0表示不渲染form表单。如果指定为0,页面会多一个空的form表单。强迫症者表示不爽!
UserFormSet = forms.formset_factory(UserForm, extra=0)
if request.method =='GET':
# initial 参数用来给 ModelForm 定义初始值。注意:同名 Field 会覆盖 instance 的值
formset = UserFormSet(initial=queryset)
# print(queryset)
return render(request,'batch_update.html',{'formset':formset})
else:
formset = UserFormSet(request.POST)
# print(request.POST)
if formset.is_valid(): # 判断数据
flag = False # 标志位
for row in formset.cleaned_data:
# pop() 方法删除字典给定键 key 及对应的值,返回值为被删除的值
id = row.pop('id') # 获取id
if id: # 判断数据不为空
# print(row) # 它是一个字典
# **表示将字典扩展为关键字参数
res = models.User.objects.filter(id=id).update(**row)
if res: # 判断返回信息
flag = True
if flag:
return HttpResponse('修改成功')
else:
return HttpResponse('修改失败')
else:
return render(request, "batch_update.html", {'formset': formset})
在templates目录新建文件batch_update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.hide{
display: none;
}
</style>
</head>
<body>
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table border="1">
<tr>
<th>用户名</th>
<th>密码</th>
<th>邮箱</th>
</tr>
{% for form in formset %}
<tr>
{% for field in form %}
{#判断最后一个字段,也就是id#}
{% if forloop.last %}
{#隐藏id#}
<td class="hide">{{ field }}</td>
{% else %}
{#显示其他字段#}
<td>{{ field }}{{ field.errors.0 }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="提交">
</form>
</body>
</html>
访问页面:http://127.0.0.1:8000/batch_update/

修改密码

点击提交,效果如下:

查看表记录

注释事项
在更新的时候,需要有一个id。由于id字段是AutoField,该 Field 不会出现在 ModelForm 表单中。
因此,在views.py里面的UserForm中,定义了一个id字段。
那么在batch_update.html渲染时,就会显示这个id字段。但是更新时,这个id是主键,是不允许更改的。
所以用了一个很巧妙的办法,使用css样式,来隐藏它。
使用ModelForm渲染表单时,自定义的字段它是排在最后面的。所以使用forloop.last就可以定位到ID!
最后点击提交时,request.POST就带有id数据。那么后端,就可以通过id进行更新了!
关于其他更多的ModelForm信息,请参考链接:
https://blog.csdn.net/Leo062701/article/details/80963625
三、ORM之limit_choices_to
limit_choices_to介绍
它是在Admin或ModelForm中显示关联数据时,提供的条件
比如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
举例
打开权限管理项目,访问url: http://127.0.0.1:8000/rbac/menu/list/

点击删除账单后面的编辑按钮,进入之后,点击父权限

注意:这里的父权限,展示不对。为什么?因为它必须是能作为菜单权限的!
而这里却展示了所有url。
怎么解决呢?有2个办法
第一:页面渲染时,对数据做判断。
第二:使用limit_choices_to(推荐)
修改 rbac—>models.py,找到字段
parent = models.ForeignKey(verbose_name='父权限',to='Permission',null=True,blank=True)
更改为
parent = models.ForeignKey(verbose_name='父权限',to='Permission',null=True,blank=True,limit_choices_to={'menu__isnull': False})
这个时候,不需要执行那2个命令
直接刷新页面,效果如下:

补充另外一个选项help_text,它是用来提示帮助信息
比如:
help_text="对于无法作为菜单的URL,可以为其选择一个可以作为菜单的权限,那么访问时,则默认选中此权限",
parent 字段完整信息如下:
parent = models.ForeignKey(verbose_name='父权限', to='Permission', null=True, blank=True,
limit_choices_to={'menu__isnull': False},
help_text="对于无法作为菜单的URL,可以为其选择一个可以作为菜单的权限,那么访问时,则默认选中此权限")
在输入框的右侧,就可以显示信息
在模板中,使用{{ field.help_text }}渲染,效果如下:

由于父权限和菜单是二选一的,前端无法做验证,需要在后端做验证。
使用全局钩子,来做判断!
def clean(self):
menu = self.cleaned_data.get('menu_id')
pid = self.cleaned_data.get('pid_id')
if menu and pid:
self.add_error('menu','菜单和根权限同时只能选择一个')
四、构造家族结构
先来看一下Laravel官网的评论区,这是一个评论树形结构

其中可以针对任何一个人进行回复.说白一点就是多叉树,类似的结构如下:

文章的id就是根节点,每一个子节点都保存着上一级节点的id,同级节点之间使用根据创建时间进行先后排序。
评论数型结构
根评论
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None}
]
子评论
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1}
]
三级评论
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1},
{'id':3, 'title':'什么玩意', 'pid':2}
]
后面的层级,依次类推。
最终数据如下:
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1},
{'id':3, 'title':'什么玩意', 'pid':2},
{'id':4, 'title':'q1', 'pid':2},
{'id':5, 'title':'666', 'pid':1},
{'id':6, 'title':'去你的吧', 'pid':3},
]
层级关系应该是这个样子的
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1},
{'id':3, 'title':'什么玩意', 'pid':2}
{'id':6, 'title':'去你的吧', 'pid':3},
{'id':4, 'title':'q1', 'pid':2}
{'id':5, 'title':'666', 'pid':1},
如何构造
转换字典
先将数据转换成字典
comment_dict = {}
for item in comment_list:
comment_dict[item['id']] = item
执行之后,结果是一个大字典
comment_dict = {
1: {'title': '写的不错', 'id': 1, 'pid': None},
2: {'title': '还不错', 'id': 2, 'pid': 1},
3: {'title': '什么玩意', 'id': 3, 'pid': 2},
4: {'title': 'q1', 'id': 4, 'pid': 2},
5: {'title': '666', 'id': 5, 'pid': 1},
6: {'title': '去你的吧', 'id': 6, 'pid': 3}
}
注意:这里面的每一个小字典,和comment_list里面的字典,用的是同一个内存地址。
证明一下,改个数据
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1},
{'id':3, 'title':'什么玩意', 'pid':2},
{'id':4, 'title':'q1', 'pid':2},
{'id':5, 'title':'666', 'pid':1},
{'id':6, 'title':'去你的吧', 'pid':3},
]
comment_dict = {}
for item in comment_list:
comment_dict[item['id']] = item
print('字典更改前',comment_dict[1])
comment_list[0]['title'] = '写的不错~~~'
print('字典更改后',comment_dict[1])
执行输出:
字典更改前 {'title': '写的不错', 'pid': None, 'id': 1}
字典更改后 {'title': '写的不错~~~', 'pid': None, 'id': 1}
发现了吧!明明更改的是大列表,但是大字典里面的数据,也随之变动!
加children
加children的目的,是为了存放除了根评论之外的,比如:二级和三级评论!
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1},
{'id':3, 'title':'什么玩意', 'pid':2},
{'id':4, 'title':'q1', 'pid':2},
{'id':5, 'title':'666', 'pid':1},
{'id':6, 'title':'去你的吧', 'pid':3},
]
comment_dict = {}
for item in comment_list:
# 增加新的key为children,值为空列表
item['children'] = []
comment_dict[item['id']] = item
for row in comment_list:
if not row['pid']:
continue
执行之后,comment_dict结构如下:
comment_dict = {
1: {'title': '写的不错', 'id': 1, 'pid': None,'children':[]},
2: {'title': '还不错', 'id': 2, 'pid': 1,'children':[]},
3: {'title': '什么玩意', 'id': 3, 'pid': 2,'children':[]},
4: {'title': 'q1', 'id': 4, 'pid': 2,'children':[]},
5: {'title': '666', 'id': 5, 'pid': 1,'children':[]},
6: {'title': '去你的吧', 'id': 6, 'pid': 3,'children':[]}
}
最加到children
判断是根评论,跳过循环。否则最加到children
comment_list = [
{'id':1, 'title':'写的不错', 'pid':None},
{'id':2, 'title':'还不错', 'pid':1},
{'id':3, 'title':'什么玩意', 'pid':2},
{'id':4, 'title':'q1', 'pid':2},
{'id':5, 'title':'666', 'pid':1},
{'id':6, 'title':'去你的吧', 'pid':3},
]
comment_dict = {}
for item in comment_list:
# 增加新的key为children,值为空列表
item['children'] = []
comment_dict[item['id']] = item
for row in comment_list:
if not row['pid']: # 判断根评论
continue # 跳过此次循环
pid = row['pid'] # 获取pid
# 最加到children中
comment_dict[pid]['children'].append(row)
执行之后,comment_dict结构如下:
{
"1": {
"pid": null,
"id": 1,
"title": "写的不错",
"children": [{
"pid": 1,
"id": 2,
"title": "还不错",
"children": [{
"pid": 2,
"id": 3,
"title": "什么玩意",
"children": [{
"pid": 3,
"id": 6,
"title": "去你的吧",
"children": []
}]
}, {
"pid": 2,
"id": 4,
"title": "q1",
"children": []
}]
}, {
"pid": 1,
"id": 5,
"title": "666",
"children": []
}]
},
"2": {
"pid": 1,
"id": 2,
"title": "还不错",
"children": [{
"pid": 2,
"id": 3,
"title": "什么玩意",
"children": [{
"pid": 3,
"id": 6,
"title": "去你的吧",
"children": []
}]
}, {
"pid": 2,
"id": 4,
"title": "q1",
"children": []
}]
},
"3": {
"pid": 2,
"id": 3,
"title": "什么玩意",
"children": [{
"pid": 3,
"id": 6,
"title": "去你的吧",
"children": []
}]
},
"4": {
"pid": 2,
"id": 4,
"title": "q1",
"children": []
},
"5": {
"pid": 1,
"id": 5,
"title": "666",
"children": []
},
"6": {
"pid": 3,
"id": 6,
"title": "去你的吧",
"children": []
}
}
上面的结构,是用json格式化工具之后的!
这里面的字典,虽然有重复的,但是它们是同一个内存地址!
可以看到children的数据,都加上了
终极版
import json
comment_list = [
{'id': 1, 'title': '写的不错', 'pid': None},
{'id': 2, 'title': '还不错', 'pid': 1},
{'id': 3, 'title': '什么玩意', 'pid': 2},
{'id': 4, 'title': 'q1', 'pid': 2},
{'id': 5, 'title': '666', 'pid': 1},
{'id': 6, 'title': '去你的吧', 'pid': 3},
]
comment_dict = {}
for item in comment_list:
# 增加新的key为children,值为空列表
item['children'] = []
comment_dict[item['id']] = item
result = [] # 空列表
for row in comment_list:
if not row['pid']: # 判断根评论
result.append(row) # 添加到列表
else:
pid = row['pid'] # 获取pid
# 最加到children中
comment_dict[pid]['children'].append(row)
print(json.dumps(result))
执行输出:
注意:这个是使用网页版json工具,进行排版的!!!
[{
"id": 1,
"children": [{
"id": 2,
"children": [{
"id": 3,
"children": [{
"id": 6,
"children": [],
"pid": 3,
"title": "去你的吧"
}],
"pid": 2,
"title": "什么玩意"
}, {
"id": 4,
"children": [],
"pid": 2,
"title": "q1"
}],
"pid": 1,
"title": "还不错"
}, {
"id": 5,
"children": [],
"pid": 1,
"title": "666"
}],
"pid": null,
"title": "写的不错"
}]
关于客户权限管理系统,详细步骤没有时间写了,附上终极版本
未完待续…