组件实现效果图
组件项目源代码
Django组建策划之可插拔权限组件RBAC(上)
本期涉及知识点
知识点一:自定义Tag(templatetags)inclusion_tag
知识点二:有序字典(OrderedDict())
知识点三:利用URL,get请求动态加载页面
知识点四:生成带有原搜索条件的URL(替代了模板中的url)QueryDict
知识点五,根据model字符类型CharField/TextField生成Radio单选框
知识点六:form表单之自动填充字段初始值
知识点七:form表单save前,保存前端表单未提交的数据
知识点八:批量生成formset
知识点九:批量修改字段的修改信息
知识点十:获取整个项目所有的URL
知识点十一:form渲染数据的常用格式
知识点十二:保存数据遇到的坑
知识点十三:保存多对多数据ORM操作
知识点十四:批量保存数据
知识点十五:获取前端多个表单form的数据,如何在后端区分是哪个表单进行提交
知识点一:自定义Tag(templatetags)inclusion_tag
在app下创建templatetags/app名.py
使用register.inclusion_tag()装饰器,使该Tag返回一个自定义html页面
In templatetags/appname.py
from django.tempalte import Library
register = Library()
@register.inclusion_tag('html页面路径')
def func_name(request):
return data
In html页面
编写自定义的html页面,可根据函数return的数据,使用模板语法进行html页面的渲染
在layout_plus.html页面中{% load app名称 %}
,在需要自定义位置上{% 函数名 request %}
,记得要传入参数request,方便在tag函数中获取请求相关的数据
知识点二:有序字典(OrderedDict())
我们知道字典都是无序的,所以通过模板语法循环字典的话会导致数据顺序每次刷新页面都会不同,导致用户体验不佳。那么我们怎么让字典变成和列表元组等有序的呢?
from collections import OrderedDict # 导入有序字典
menu_dict = request.session[settings.MENU_SESSION_KEY] # 拿到初始化的菜单字典,如果直接用该字典生成左侧的菜单列表,则会出现排序混乱
# 因为menu_dict字典key为数字1/2/3,所以字典的key进行排序
key_list = sorted(menu_dict) # sorted()默认为正序(升序)排序
# 空的有序字典
ordered_dict = OrderedDict()
for key in key_list:
val = menu_dict[key]
val['class'] = '' # 默认每一个1级菜单不展开,隐藏二级菜单
for per in val['children']: # 循环二级菜单
if per['id'] == request.current_selected_permission: # 二级菜单ID = 当前选中菜单对应的ID/PID,用于判断展开哪个一级菜单
per['class'] = 'active'
val['class'] = 'nav-active' # 让1级菜单的class由hide--->空
ordered_dict[key] = val # 给有序字典赋值
return {'menu_dict': ordered_dict}
OrderDict格式
ordered OrderedDict([
('1',
{
'title': '权限管理',
'icon': 'fa-gears',
'children': [
{
'id': 13,
'title': '角色列表',
'url': '/rbac/role/list/'
},
{
'id': 17,
'title': '菜单列表',
'url': '/rbac/menu/list/',
'class': 'active'
},
{
'id': 29,
'title': '权限分配',
'url': '/rbac/distribute/permissions/'
}],
'class': 'nav-active'
}),
('2',
{
'title': '用户管理',
'icon': 'fa-users',
'children': [
{
'id': 4,
'title': '用户列表',
'url': '/user/list/'
}],
'class': ''
}),
.........
])
知识点三:利用URL,get请求动态加载页面
html的a标签href属性,默认跳转当前页面,在href后添加?参数=xxx可实现GET访问带参数的URL页面
{{ row.title }}
用户点击后重新走一遍访问流程:WSGI--->中间键--->URL--->对应的视图函数
其中在视图函数中通过request.GET.get("?后的参数")
获取参数对应的value,再根据value处理将在前端展示的数据--->template
知识点四:生成带有原搜索条件的URL(替代了模板中的url)QueryDict
如果我们要点击链接跳转到另外一个页面上,但是我们想保留当前URL上?后面的参数,让我们原本的搜索条件得到保存,则我们需要引入QueryDict
修改需要跳转的a标签上为SimpleTag
使用方法和inclusion_tag类似,只是simpletag生成的是字符串
根据a标签href跳转的模板语法,参数用空格隔开,传给simpletag,使用QueryDict对url进行拼接
from django.urls import reverse
from django.http import QueryDict
def memory_url(request, name, *args, **kwargs):
"""
生成带有原搜索条件的URL(替代了模板中的url)
:param name: 反向生成URL的name
"""
basic_url = reverse(name, args=args, kwargs=kwargs)
# 当前URL中无参数
if not request.GET:
return basic_url
query_dict = QueryDict(mutable=True) # 生成QueryDict对象,mutable=True使对象可修改
query_dict['_filter'] = request.GET.urlencode() # mid=2&age=99
return "%s?%s" % (basic_url, query_dict.urlencode()) # 返回拼接好的URL字符串
- 通过POST请求在视图函数中反向生成原有搜索条件的URL
In view.py
def menu_edit(request, pk):
"""
编辑一级菜单
:param request:
:param pk:
:return:
"""
obj = models.Menu.objects.filter(id=pk).first()
if not obj:
return HttpResponse('菜单不存在')
if request.method == 'GET':
form = MenuModelForm(instance=obj)
return render(request, 'rbac/menu_change.html', {'form': form})
form = MenuModelForm(instance=obj, data=request.POST)
if form.is_valid():
form.save()
return redirect(memory_reverse(request, 'rbac:menu_list')) # 调用memory_reverse反向生成带有当前搜索条件的url
return render(request, 'rbac/menu_change.html', {'form': form})
def memory_reverse(request, name, *args, **kwargs):
"""
反向生成URL
http://127.0.0.1:8001/rbac/menu/add/?_filter=mid%3D2
1. 在url中讲原来搜索条件,如filter后的值
2. reverse生成原来的URL,如:/menu/list/
3. /menu/list/?mid%3D2
"""
url = reverse(name, args=args, kwargs=kwargs)
origin_params = request.GET.get('_filter') # 获取当前页面url搜索条件_filter
if origin_params:
url = "%s?%s" % (url, origin_params,) # 拼接url
return url
知识点五,根据model字符类型CharField/TextField生成Radio单选框
models.py
class Menu(models.Model):
"""
菜单表
"""
title = models.CharField(verbose_name='菜单名称', max_length=32)
icon = models.CharField(verbose_name='图标', max_length=32)
form.py
ICON_LIST_PLUS = [['fa-bluetooth', 'fa fa-bluetooth'],
['fa-bluetooth-b', 'fa fa-bluetooth-b'],
......]]
class MenuModelForm(forms.ModelForm):
class Meta:
model = models.Menu
fields = ['title', 'icon']
widgets = {
'title': forms.TextInput(attrs={'placeholder': '请输入菜单名称', 'class': 'form-control'}),
'icon': forms.RadioSelect(
choices=ICON_LIST_PLUS,
)
}
template
{% for foo in form.icon %}
{{ foo }}
{% endfor %}
知识点六:form表单之自动填充字段初始值
通过GET请求访问表单URL,如果我们需要给表单设置初始值,则普遍有两种方法:
- 给对象form表单全部字段填充数据初始值
通常url会获取该对象的id,视图函数则可以通过id来过滤出该对象,例如:
permission_object = models.Permission.objects.filter(id=pk).first()
if request.method == 'GET':
form = SecondMenuModelForm(instance=permission_object) # Form(instance = obj)
return render(request, 'rbac/change.html', {'form': form})
通过给Form添加instance=obj
给Form表单全部字段填充初始值
- 给对象form表单指定字段填充数据初始值
在视图函数中通过在前端传来的参数,获取到经过过滤条件的对象
def second_menu_add(request, menu_id):
"""
添加二级菜单
:param request:
:param menu_id: 已选择的一级菜单ID(用于设置初始值)
:return:
"""
menu_object = models.Menu.objects.filter(id=menu_id).first()
if request.method == 'GET':
form = SecondMenuModelForm(initial={'menu': menu_object}) # initial={'字段':字段属性} 用于给该字段自动填充默认值
return render(request, 'rbac/change.html', {'form': form})
通过给Form添加initial={form字段:form字段对象}
给Form表单填充该字段的初始值,如果有多个字段需要初始值initial=[{字段:},{},{},.....
知识点七:form表单save前,保存前端表单未提交的数据
在特殊场景下,有些form字段不会让前端表单进行填写,此时在保存form表单数据时,会用到
if form.is_valid():
# second_menu_id是通过url传递的参数
second_menu_object = models.Permission.objects.filter(id=second_menu_id).first()
# form.instance包含用户提交的所有值
# PermissionModelForm添加了title,name,url,还需要添加pid则可以使用form.instance.pid = obj为pid添加对应值
form.instance.字段名 = 字段对象
form.save()
知识点八:批量生成Formset(表单集)是多个表单的集合
Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息
导入from django.forms import formset_factory
语法:formset_factory(form表单, extra/max_num)
generate_formset_class = formset_factory(MultiAddPermissionForm, extra=0)
update_formset_class = formset_factory(MultiEditPermissionForm, extra=0)
# extra: 想要显示表单form的数量,默认extra=1,会自动添加一行空表单
# max_num: 表单显示最大数量,可选,默认1000
注意: max_num优先级高于extra
在视图文件views.py和template里,我们可以像使用form一样使用formset
注意:前端使用formset必须引入 formset名.management_form
,例如:
{{ generate_formset.management_form }}
如果同一页面有多个formset,我们需要对其进行区分,可以根据URL设置参数?type的不同用于区别同一个页面不同的post请求,例如: