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中再导入此功能函数即可;