rbac组件权限分配之一级菜单
示例:
第一步:路由创建
rbac/urls.py
from django.urls import re_path from rbac.views import role from rbac.views import user from rbac.views import menu urlpatterns = [ # 角色管理 re_path(r'^role/list/$', role.role_list, name="role_list"), re_path(r'^role/add/$', role.role_add, name="role_add"), re_path(r'^role/edit/(?P\d+)/$ ', role.role_edit, name="role_edit"), re_path(r'^role/del/(?P\d+)/$ ', role.role_del, name="role_del"), # 用户管理 re_path(r'^user/list/$', user.user_list, name="user_list"), re_path(r'^user/add/$', user.user_add, name="user_add"), re_path(r'^user/edit/(?P\d+)/$ ', user.user_edit, name="user_edit"), re_path(r'^user/del/(?P\d+)/$ ', user.user_del, name="user_del"), re_path(r'^user/reset/password/(?P\d+)/$ ', user.user_reset_pwd, name="user_reset_pwd"), # 一级菜单管理 re_path(r'^menu/list/$', menu.menu_list, name="menu_list"), re_path(r'^menu/add/$', menu.menu_add, name="menu_add"), re_path(r'^menu/eidt/(?P\d+)/$ ', menu.menu_edit, name="menu_edit"), re_path(r'^menu/del/(?P\d+)/$ ', menu.menu_del, name="menu_del"), ]
第二步:ModelForm书写
rbac/forms/menu.py
from django import forms from rbac import models from django.utils.safestring import mark_safe ICON_LIST = [ ["fa-address-book", ''], ["fa-adjust", ''], ["fa-area-chart", ''], ["fa-ban", ''], ["fa-at", ''], ["fa-car", ''], ["fa-university", ''], ["fa-bar-chart", ''], ["fa-bell", ''], ["fa-bell-slash", ''], ["fa-birthday-cake", ''], ["fa-book", ''], ["fa-bookmark", ''], ["fa-bug", ''], ["fa-calendar-check-o", ''], ["fa-cart-plus", ''], ["fa-check-circle", ''], ["fa-times", ''], ["fa-cloud-download", ''], ["fa-cog", ''], ["fa-comments", ''], ["fa-copyright", ''], ["fa-database", ''], ["fa-envelope", ''], ["fa-folder-open", ''], ["fa-thumbs-up", ''], ["fa-thumbs-down", ''], ["fa-users", ''], ["fa-star", ''], ["fa-handshake-o", ''], ["fa-camera-retro", ''], ["fa-fire", ''], ["fa-user-circle", ''], ] for item in ICON_LIST: # mark_safe()也可以让前端页面直接渲染标签 item[1] = mark_safe(item[1]) class MenuModelForm(forms.ModelForm): class Meta: model = models.Menu fields = ["title", "icon"] widgets = { "title": forms.TextInput(attrs={"class": "form-control"}), "icon": forms.RadioSelect(choices=ICON_LIST, attrs={"class": "clearfix"}), } """ icon字段,使用的是单选输入框,choices会循环一个可迭代对象,每个循环元素的第一个值作为单选框的value的值,第二个值 作为前端显示值,如:前端显示值 """
第三步:生成带搜索条件的url和反向解析带搜索条件的url
rbac/service/urls.py
from django.urls import reverse from django.http import QueryDict def memory_reverse(request, name, *args, **kwargs): """ 反向生成URL http://127.0.0.1:8000/rbac/menu/add/?_filter=mid%3D1 1. 在url中将原来的搜索条件获取,如:_filter后的值 2. reverse生成原来的url,如:/menu/list/ 3. http://127.0.0.1:8000/rbac/menu/list/?mid=1 :param request: :param name: :param args: :param kwargs: :return: """ url = reverse(name, args=args, kwargs=kwargs) origin_params = request.GET.get("_filter") if origin_params: url = "%s?%s" % (url, origin_params) return url def memory_url(request, name, *args, **kwargs): """ 生成带有原搜索条件的URL(替代了模板中的url) http://127.0.0.1:8000/rbac/menu/list/?mid=1 http://127.0.0.1:8000/rbac/menu/add/?_filter=mid%3D1 :param request: :param name: :return: """ basic_url = reverse(name, args=args, kwargs=kwargs) # 当前url无参数 if not request.GET: return basic_url query_dict = QueryDict(mutable=True) query_dict["_filter"] = request.GET.urlencode() # 如果直接在request.GET中设置,是会报错的,所以需要用到QueryDict url = "%s?%s" % (basic_url, query_dict.urlencode()) return url
第四步:simple_tag的创建,再次封装memory_url,让其在模板中能够使用
rbac/templatetags/rbac.py
from django.template import Library from rbac.service import urls register = Library() @register.simple_tag def memory_url(request, name, *args, **kwargs): """ 生成带有原搜索条件的URL(替代了模板中的url) :param request: :param name: :return: """ return urls.memory_url(request, name, *args, **kwargs)
第五步:视图函数的书写
rbac/views/menu.py
from django.shortcuts import render, redirect, HttpResponse from rbac import models from rbac.forms.menu import MenuModelForm from rbac.service.urls import memory_reverse def menu_list(request): """ 菜单和权限列表 :param request: :return: """ menus = models.Menu.objects.all().order_by("pk") # menu_list.html的菜单名称是a标签,此a标签的href在当前url后面加了mid参数 # 取到该参数后,传给模板,模板与所循环一级菜单的id进行if比较,如果相等,那么加上'active'表示选中 menu_id = request.GET.get("mid") return render(request, "rbac/menu_list.html", {"menus": menus, "mid": menu_id}) def menu_add(request): """ 添加一级菜单 :param request: :return: """ if request.method == "GET": form = MenuModelForm() return render(request, "rbac/change.html", {"form": form}) form = MenuModelForm(data=request.POST) if form.is_valid(): form.save() # memory_reverse将之前带有原搜索条件的url取出并拼接 # /rbac/menu/add/?_filter=mid%3D1 ----> /rbac/menu/list/?mid=1 return redirect(memory_reverse(request, "rbac:menu_list")) return render(request, "rbac/change.html", {"form": form}) 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/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")) return render(request, "rbac/change.html", {"form": form}) def menu_del(request, pk): """ 删除一级菜单 :param request: :param pk: :return: """ url = memory_reverse(request, "rbac:menu_list") if request.method == "GET": return render(request, "rbac/delete.html", {"cancel_url": url}) models.Menu.objects.filter(id=pk).delete() return redirect(url)
第六步:模板的创建
rbac/templates/rbac/menu_list.html
{% extends 'layout.html' %} {% load rbac %} {% block css %} <style> {# 选中样式css #} tr.active { border-left: 3px solid #fdc00f; } style> {% endblock %} {% block content %} <div class="luffy-container"> <div class="col-md-3"> <div class="panel panel-default"> <div class="panel-heading"> <i class="fa fa-book" aria-hidden="true">i> 一级菜单 <a href="{% memory_url request 'rbac:menu_add' %}" class="right btn btn-success btn-xs" style="padding: 2px 8px; margin: -3px;"> <i class="fa fa-plus-circle" aria-hidden="true">i> 新建 a> div> <table class="table"> <thead> <tr> <th>名称th> <th>图标th> <th>选项th> tr> thead> <tbody> {% for menu in menus %} {# safe也可以转换类型,将数值类型转为了字符串类型 #} <tr class="{% if menu.id|safe == mid %}active{% endif %}"> <td> <a href="?mid={{ menu.id }}">{{ menu.title }}a> td> <td> <i class="fa {{ menu.icon }}" aria-hidden="true">i> td> <td> <a style="color: #333333;" href="{% memory_url request 'rbac:menu_edit' pk=menu.id %}"> <i class="fa fa-edit" aria-hidden="true">i>a> <a style="color: #d9534f;" href="{% memory_url request 'rbac:menu_del' pk=menu.id %}"><i class="fa fa-trash-o">i>a> td> tr> {% endfor %} tbody> table> div> div> div> {% endblock %}
第七步:将渲染的图标进行样式更改
rbac/templates/rbac/change.html
{% extends 'layout.html' %} {% block css %} {# 图标样式css #} <style> ul { list-style-type: none; padding: 0; } ul li { float: left; padding: 10px; padding-left: 0; width: 80px; } ul li i { font-size: 18px; margin-left: 5px; color: #6d6565; } style> {% endblock %} {% block content %} <div class="luffy-container"> <form class="form-horizontal" method="post" novalidate> {% csrf_token %} {% for foo in form %} <div class="form-group"> <label class="col-sm-2 control-label">{{ foo.label }}label> <div class="col-sm-8"> {{ foo }} <span style="color: red">{{ foo.errors.0 }}span> div> div> {% endfor %} <div class="form-group"> <div class="col-sm-offset-2 col-sm-8"> <input type="submit" value="保存" class="btn btn-primary"> div> div> form> div> {% endblock %}
总结:
- 此次的难点在于默认选中这个功能,将带有搜索条件的url打包成一个指定参数,然后跳转到指定页面时,取出搜索条件将其默认选中效果展示;搜索功能自己想了一下(点击分页按钮搜索条件没有了),后面有时间验证是否正确
""" 搜索如何保留原搜索条件? book/list/ 视图函数: 先判断select是否有值,如果有--利用select的值进行查询得到queryset分页传给前端,并把select的值也传给前端 如果没有--那么查询全部的queryset分页传给前端即可 模板: 搜索的input框使用get请求,如:name=select,value=搜索值;url:book/list/?select=搜索值 分页按钮的a标签,?page=变量&select=后端传入变量 重置按钮的url可以是/book/list/ """
- 将生成带有搜索条件的url和解析带有搜索条件的url两个功能函数放到了一块,但是生成有搜索条件的url功能函数需要在模板中调用,所以写成了simple_tag,在simple_tag中再导入此功能函数即可;