权限组件(8):一级菜单的展示、增删改查和保留原参数

效果图:

一、路由配置

rbac/urls.py

from django.urls import re_path
from rbac.views import menu urlpatterns = [ ... # 菜单管理 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/edit/(?P<pk>d+)/$', menu.menu_edit, name='menu_edit'), re_path(r'^menu/del/(?P<pk>d+)/$', menu.menu_del, name='menu_del'), ... ]

二、forms表单验证

rbac/forms/menu.py

from django import forms
from django.utils.safestring import mark_safe

from rbac import models

ICON_LIST = [
    ['fa-hand-scissors-o', '<i aria-hidden="true" class="fa fa-hand-scissors-o"></i>'],
    ['fa-hand-spock-o', '<i aria-hidden="true" class="fa fa-hand-spock-o"></i>'],
    ['fa-hand-stop-o', '<i aria-hidden="true" class="fa fa-hand-stop-o"></i>'],
    ['fa-handshake-o', '<i aria-hidden="true" class="fa fa-handshake-o"></i>'],
    ['fa-hard-of-hearing', '<i aria-hidden="true" class="fa fa-hard-of-hearing"></i>'],
    ['fa-hashtag', '<i aria-hidden="true" class="fa fa-hashtag"></i>'],
    ['fa-hdd-o', '<i aria-hidden="true" class="fa fa-hdd-o"></i>'],
    ['fa-headphones', '<i aria-hidden="true" class="fa fa-headphones"></i>'],
    ['fa-heart', '<i aria-hidden="true" class="fa fa-heart"></i>'],
    ['fa-heart-o', '<i aria-hidden="true" class="fa fa-heart-o"></i>'],
    ['fa-heartbeat', '<i aria-hidden="true" class="fa fa-heartbeat"></i>'],
    ['fa-history', '<i aria-hidden="true" class="fa fa-history"></i>'],
    ['fa-home', '<i aria-hidden="true" class="fa fa-home"></i>'],
    ['fa-hotel', '<i aria-hidden="true" class="fa fa-hotel"></i>'],
    ['fa-hourglass', '<i aria-hidden="true" class="fa fa-hourglass"></i>'],
    ['fa-hourglass-1', '<i aria-hidden="true" class="fa fa-hourglass-1"></i>'],
    ['fa-hourglass-2', '<i aria-hidden="true" class="fa fa-hourglass-2"></i>'],
    ['fa-hourglass-3', '<i aria-hidden="true" class="fa fa-hourglass-3"></i>'],
    ['fa-hourglass-end', '<i aria-hidden="true" class="fa fa-hourglass-end"></i>'],
    ['fa-hourglass-half', '<i aria-hidden="true" class="fa fa-hourglass-half"></i>'],
    ['fa-hourglass-o', '<i aria-hidden="true" class="fa fa-hourglass-o"></i>'],
    ['fa-hourglass-start', '<i aria-hidden="true" class="fa fa-hourglass-start"></i>'],
    ['fa-i-cursor', '<i aria-hidden="true" class="fa fa-i-cursor"></i>'],
    ['fa-id-badge', '<i aria-hidden="true" class="fa fa-id-badge"></i>'],
    ['fa-id-card', '<i aria-hidden="true" class="fa fa-id-card"></i>'],
    ['fa-id-card-o', '<i aria-hidden="true" class="fa fa-id-card-o"></i>'],
    ['fa-image', '<i aria-hidden="true" class="fa fa-image"></i>'],
    ['fa-mail-reply-all', '<i aria-hidden="true" class="fa fa-mail-reply-all"></i>'],
    ['fa-reply', '<i aria-hidden="true" class="fa fa-reply"></i>'],
    ['fa-reply-all', '<i aria-hidden="true" class="fa fa-reply-all"></i>'],
    ['fa-retweet', '<i aria-hidden="true" class="fa fa-retweet"></i>'],
    ['fa-wrench', '<i aria-hidden="true" class="fa fa-wrench"></i>']]

"""
 mark_safe:
 Explicitly mark a string as safe for (HTML) output purposes. The returned
 object can be used everywhere a string is appropriate.
"""
for item in ICON_LIST:
    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'}
            )
        }

三、视图函数

from django.shortcuts import HttpResponse, render, redirect, reverse

from rbac import models
from rbac.forms.menu import MenuModelForm
from rbac.service.urls import memory_reverse


def menu_list(request):
    """
    菜单和权限列表
    :param request:
    :return:
    """
    menu_queryset = models.Menu.objects.all()

    menu_id = request.GET.get('mid')

    context = {
        'menu_list': menu_queryset,
        'menu_id': menu_id
    }

    return render(request, 'rbac/menu_list.html', context)


def menu_add(request):
    """
     菜单和权限列表
    :param request:
    :return:
    """
    if request.method == 'GET':
        forms = MenuModelForm()
        return render(request, 'rbac/change.html', {'forms': forms})

    forms = MenuModelForm(data=request.POST)
    if forms.is_valid():
        forms.save()
        url = memory_reverse(request, 'rbac:menu_list')
        return redirect(url)

    return render(request, 'rbac/change.html', {'forms': forms})


def menu_edit(request, pk):
    """
    编辑一级菜单
    :param request:
    :param pk:
    :return:
    """
    menu_obj = models.Menu.objects.filter(id=pk).first()

    if not menu_obj:
        return HttpResponse('菜单不存在')

    if request.method == 'GET':
        forms = MenuModelForm(instance=menu_obj)
        return render(request, 'rbac/change.html', {'forms': forms})

    forms = MenuModelForm(data=request.POST, instance=menu_obj)
    if forms.is_valid():
        forms.save()
        url = memory_reverse(request, 'rbac:menu_list')
        return redirect(url)
    return render(request, 'rbac/change.html', {'forms': forms})


def menu_del(request, pk):
    """
    删除一级菜单
    :param request:
    :param pk:
    :return:
    """
    menu_list_url = memory_reverse(request, 'rbac:menu_list')

    if request.method == 'GET':
        return render(request, 'rbac/delete.html', {'cancel': menu_list_url})

    models.Menu.objects.filter(id=pk).delete()

    return redirect(menu_list_url)

memory_reverse的功能是当用户完成增删改返回列表页的时候,还带有原参数,这样回列表页的时候还会默认选中用户刚刚选中的参数

 

四、保留原参数

rbac/templatestags/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:
    :param args:
    :param kwargs:
    :return:
    """
    return urls.memory_url(request, name, *args, **kwargs)
...

rbac/service/urls.py

from django.http import QueryDict

from django.shortcuts import reverse


def memory_url(request, name, *args, **kwargs):
    # reverse用法:reverse('name', kwargs={'pk': 1})
    # reverse用法:reverse('name', args=(1,))
    basic_url = reverse(name, args=args, kwargs=kwargs)
    # 当前url中无参数
    if not request.GET:
        return basic_url

    old_params = request.GET.urlencode()  # 获取url中的参数

    query_dict = QueryDict(mutable=True)  # 提供转义功能
    query_dict['_filter'] = old_params

    # urlencode帮我们自动转义。
    # 如果不用urlencode,&符号会把这个参数分割成两个参数:_filter=mid=2 和 age=99
    return '%s?%s' % (basic_url, query_dict.urlencode())  # _filter=mid=2&age=99


def memory_reverse(request, name, *args, **kwargs):
    """
    反向生成URL
        http://127.0.0.1:8000/rbac/menu/edit/1/?_filter=mid%3D4
        1. 在URL获取原来的搜索条件获取(filter后的值)
        2. reverse生成原来的URL,如:/menu/list/
        3. /menu/list/?mid%3D4

    :param request:
    :param name:
    :param args:
    :param kwargs:
    :return:
    """

    url = reverse(name, args=args, kwargs=kwargs)
    original_parmas = request.GET.get('_filter')
    if original_parmas:
        url = '%s?%s' % (url, original_parmas)
    return url

由于需要传的参数超过了两个,所以需要用simple_tag。memory_url在模板用的。memory_reverse是在视图函数中反向解析的时候用的。

 

五、模板

模板层新增了菜单列表的模板,增、改页面由于新增了许多图标,所以也有一些小的变动。
rbac/templates/rbac/menu_list.html

{% extends 'layout.html' %}
{% load rbac %}

<style>
    tr.active {
        border-left: 3px solid #fdc00f;
    }
</style>

{% block content %}
    <div class="luffy-container">
        <div class="col-md-3">
            <div class="panel panel-default">
                <!-- Default panel contents -->
                <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 -->
                <table class="table">
                    <thead>
                    <tr>
                        <th>名称</th>
                        <th>图标</th>
                        <th>选项</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for menu in menu_list %}
                        <!-- 管道符可以将后端传来的整型,转换成字符串 -->
                        <tr class="{% if menu.id|safe == menu_id %}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; font-size:18px"
                                   href="{% memory_url request 'rbac:menu_edit' pk=menu.id %}">
                                    <i class="fa fa-edit" aria-hidden="true"></i>
                                </a>

                                <a style="color: red; font-size:18px"
                                   href="{% memory_url request 'rbac:menu_del' pk=menu.id %}">
                                    <i class="fa fa-trash-o" aria-hidden="true"></i>
                                </a>
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>

{% endblock content %}

需要用模板语法判断用户选中的菜单并给其一个active的样式

rbac/templates/rbac/change.html

{% extends 'layout.html' %}

{% block 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 css %}

{% block content %}

    <div class="luffy-container">
        <form class="form-horizontal" action="" method="post" novalidate>
            {% csrf_token %}
            {% for field in forms %}
                <div class="form-group">
                    <label class="col-sm-2 control-label" for="{{ field.auto_id }}">{{ field.label }}</label>
                    <div class="col-sm-8">
                        {{ field }}
                        <span style="color:red;">{{ field.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 content %}
原文地址:https://www.cnblogs.com/lshedward/p/10505622.html