Django 權限管理-后台根據用戶權限動態生成菜單


Django權限管理

實現目標:

1、管理用戶,添加角色,用戶關聯角色

2、添加權限、角色關聯權限

3、添加動作、權限關聯動作

4、添加菜單、權限關聯菜單

實現動態生成用戶權限菜單(可設置多級菜單嵌套)、根據前台URL自動選中菜單並折疊其余菜單

 

最終實現類似這樣的效果:

菜單一
  菜單1.1
  菜單1.2
    菜單1.2.1
       訂單管理
      分類 管理

菜單二

 

 

一、首先是建立表格

models

from django.db import models


# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

    class Meta:
        verbose_name_plural = '用戶表'

    def __str__(self):
        return self.username


class Role(models.Model):
    role = models.CharField(max_length=32)

    class Meta:
        verbose_name_plural = '角色表'

    def __str__(self):
        return self.role


class User2Role(models.Model):
    u = models.ForeignKey(User, on_delete=models.CASCADE)
    r = models.ForeignKey(Role, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = '用戶分配角色'

    def __str__(self):
        return '%s-%s' % (self.u.username, self.r.role)


class Menu(models.Model):
    caption = models.CharField(max_length=32)
    parent = models.ForeignKey('self', related_name='p', null=True, blank=True, on_delete=models.CASCADE)

    def __str__(self):
        return '%s' % (self.caption,)


class Permission(models.Model):
    caption = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    menu = models.ForeignKey(Menu, null=True, blank=True, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = 'URL表'

    def __str__(self):
        return '%s-%s' % (self.caption, self.url)


class Action(models.Model):
    caption = models.CharField(max_length=32)
    code = models.CharField(max_length=32)

    class Meta:
        verbose_name_plural = '操作表'

    def __str__(self):
        return self.caption


class Permission2Action(models.Model):
    p = models.ForeignKey(Permission, on_delete=models.CASCADE)
    a = models.ForeignKey(Action, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = '權限表'

    def __str__(self):
        return '%s-%s:-%s?t=%s' % (self.p.caption, self.a.caption, self.p.url, self.a.code)


class Permission2Action2Role(models.Model):
    p2a = models.ForeignKey(Permission2Action, on_delete=models.CASCADE)
    r = models.ForeignKey(Role, on_delete=models.CASCADE)

    class Meta:
        verbose_name_plural = '角色分配權限'

    def __str__(self):
        return '%s=>%s' % (self.r.role, self.p2a)

 

建立表后,用django的admin在表中添加一些數據

1、用戶表:建立幾個用戶

2、角色表:建立幾個角色,如:CEO\CTO\開發\客服\業務員

3、給用戶分配角色

4、URL表:建立幾個管理菜單,如:分類管理\報表管理\訂單管理\用戶管理

5、操作表:增\刪\改\查

6、權限表:給URL添加操作內容

7、角色分配權限:

8、菜單表:設置三層菜單,如:菜單一:菜單1.1:菜單1.1.1

 

 

二、url

添加數據后建立url

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('logout/', views.logout),
    path('index/', views.index),
]

 

三、login

編輯login,登錄成功后,將用戶信息保存在session中,並通過MenuHelper類獲取用戶的權限和菜單,也保存在session中,實例化MenuHelper類

def login(request):
    if request.method == "GET":
        return render(request, 'login.html')
    else:
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        obj = models.User.objects.filter(username=username, password=pwd).first()
        if obj:
            # 登錄成功,獲取當前用戶信息
            # 放到session中
            request.session['user_info'] = {'nid': obj.id, 'username': obj.username}

            # 獲取當前用戶的所有權限,獲取所有菜單,獲取在菜單中顯示的權限(葉子節點)
            # 放到session中
            MenuHelper(request, obj.username)
            return redirect('/index')
        else:
            return redirect('/login')

 

四、MenuHelper類

首先獲取當前用戶,通過reques.path_info獲取當前用戶訪問的url

調用類的session_data方法:判斷該用戶當前session中是否已經有內容,如果有內容則與取出session中的內容,否則通過用戶名分別獲取當前用戶的角色列表、權限列表、最終顯示的菜單列表以及所有菜單,隨后跳轉至index

class MenuHelper(object):
    def __init__(self, request, username):
        # 當前請求的request對象
        self.request = request
        # 當前用戶名
        self.username = username
        # 當前url,如用戶訪問127.0.0.1:8000/index.html?p=123 會獲得:index.html
        self.current_url = request.path_info

        # 當前用戶的所有權限
        self.permission2action_dict = None
        # 當前用戶菜單中顯示的所有權限(葉子節點)
        self.menu_leaf_list = None
        # 所有菜單
        self.menu_list = None

        self.session_data()

    def session_data(self):
        permission_dict = self.request.session.get('permission_info')
        if permission_dict:
            self.permission2action_dict = permission_dict['permission2action_dict']
            self.menu_leaf_list = permission_dict['menu_leaf_list']
            self.menu_list = permission_dict['menu_list']
        else:
            # 獲取當前用戶的角色列表
            role_list = models.Role.objects.filter(user2role__u__username=self.username)

            # 獲取當前用戶的權限列表(url+action)
            permission2action_list = models.Permission2Action.objects.\
                filter(permission2action2role__r__in=role_list).\
                values('p__url', 'a__code').distinct()

            permission2action_dict = {}
            for item in permission2action_list:
                if item['p__url'] in permission2action_dict:
                    permission2action_dict[item['p__url']].append(item['a__code'])
                else:
                    permission2action_dict[item['p__url']] = [item['a__code'], ]

            # 獲取菜單的葉子節點,即:菜單的最后一層應該顯示的權限
            menu_leaf_list = list(models.Permission2Action.objects.
                                  filter(permission2action2role__r__in=role_list).
                                  exclude(p__menu__isnull=True).
                                  values('p_id', 'p__url', 'p__caption', 'p__menu').distinct())

            # 獲取所有菜單列表
            menu_list = list(models.Menu.objects.values('id', 'caption', 'parent_id'))

            self.request.session['permission_info'] = {
                'permission2action_dict': permission2action_dict,
                'menu_leaf_list': menu_leaf_list,
                'menu_list': menu_list,
            }

            # self.permission2action_list = permission2action_list
            # self.menu_leaf_list = menu_leaf_list
            # self.menu_list = menu_list

    def menu_data_list(self):
        # 設置一個空的葉子節點字典
        menu_leaf_dict = {}
        # 首先設置葉子父id節點為空
        open_left_parent_id = None

        for item in self.menu_leaf_list:
            # 將獲取的葉子節點列表的每一個值轉換為字典形式,並重新設置key,添加child,status,open字段
            item = {
                'id': item['p_id'],
                'url': item['p__url'],
                'caption': item['p__caption'],
                'parent_id': item['p__menu'],
                'child': [],
                'status': True,  # 是否顯示
                'open': False,  # 是否打開
            }
            # 判斷每一個葉子節點的父節點,將每個葉子節點的內容添加至父節點id作為的key中
            # 判斷父節點id作為的key是否在葉子節點字典中存在,如果存在,則將item值append進入
            if item['parent_id'] in menu_leaf_dict:
                menu_leaf_dict[item['parent_id']].append(item)
            # 如果不存在,則直接在列表中生成一個key是葉子節點父節點id的,值為item的數據
            else:
                menu_leaf_dict[item['parent_id']] = [item, ]

            # 判斷用戶輸入的url是否與現在的url匹配,item['url']可以寫成一個正則表達式,用match進行匹配
            # 如果匹配上,將葉子節點的open置為true,並將葉子節點的父節點id進行賦值
            import re
            if re.match(item['url'], self.current_url):
                item['open'] = True
                open_left_parent_id = item['parent_id']

        # 設置一個菜單空字典
        menu_dict = {}

        # 將菜單列表轉換為字典,並增加child,status,open字段
        # 將列表中的id作為key,列表中的值作為值
        for item in self.menu_list:
            item['child'] = []
            item['status'] = False
            item['open'] = False
            menu_dict[item['id']] = item

        # 循環葉子字典,設置菜單字典中對應的child內容為葉子字典的值
        for k, v in menu_leaf_dict.items():
            menu_dict[k]['child'] = v
            # 設置菜單字典的parent_id的值為葉子字典的key(也就是葉子中的parent)
            parent_id = k
            # 設置菜單字典中的status狀態為True,並循環設置父級菜單的status為True
            while parent_id:
                menu_dict[parent_id]['status'] = True
                parent_id = menu_dict[parent_id]['parent_id']

        # 判斷葉子父級id,將open設置為True,並循環設置父級菜單的open為True
        while open_left_parent_id:
            menu_dict[open_left_parent_id]['open'] = True
            open_left_parent_id = menu_dict[open_left_parent_id]['parent_id']

        # print('循環權限用戶url字典,將用戶權限取得的id匹配菜單列表id並設置["child"]值為用戶權限內容')
        # print('設置parent_id變量為:用戶權限url的id')
        # print('如果有,菜單id的["status"]設置為True')
        # print('並且將parent_id的值設置為:菜單字典中菜單id的["parent"],等待下一次循環')
        # for k, v in menu_dict.items():
        #     print(k, v)
        # #####################處理菜單的等級關系#########################
        # menu_dict 應用:多級評論,多級菜單

        result = []
        # 按父子關系,將菜單列表中的值,層疊放入一個result中
        # 這里需要注意的是,只需要尋找一層的父id,並將自己放入,無需一層一層尋找到上一層的父節點。
        for row in menu_dict.values():
            if not row['parent_id']:
                result.append(row)
            else:
                menu_dict[row['parent_id']]['child'].append(row)

        return result

    def menu_content(self, child_list):

        response = ''
        tpl = """
            <div class="item %s">
                <div class="title">%s</div>
                <div class="content">%s</div>
            </div>
        """
        for row in child_list:
            if not row['status']:
                continue
            active = ''
            if row['open']:
                active = 'active'
            if 'url' in row:
                response += '<a class="%s" href="%s">%s</a>' % (active, row['url'], row['caption'])
            else:
                title = row['caption']
                content = self.menu_content(row['child'])
                response += tpl % (active, title, content)

        return response

    def menu_tree(self):
        response = ''
        tpl = """
            <div class="item %s">
                <div class="title">%s</div>
                <div class="content">%s</div>
            </div>
        """
        for row in self.menu_data_list():
            if not row['status']:
                continue
            active = ''
            if row['open']:
                active = 'active'
            title = row['caption']
            content = self.menu_content(row['child'])
            response += tpl % (active, title, content)

        return response

    def action(self):
        """
        檢查當前用戶是否對當前URL有訪問權,並獲取對當前URL有什么權限
        :return:
        """

        action_list = []

        for k, v in self.permission2action_dict.items():
            if re.match(k, self.current_url):
                action_list = v
                break

        return action_list

 

五、index

index使用了一個裝飾器,判斷用戶session中是否有用戶信息,如果有用戶信息,使用MenuHelper類實例化一個對象,調用對象的action方法,獲得action_list

如果列表為空則返回無權訪問

否則返回菜單樹(調用了類的menu_data_list方法,循環遞歸的生成菜單樹,返回的是后台生成的html代碼

以及權限列表

最后通過權限列表中的內容分別進行操作,並返回至前台(這個位置沒有編寫完成,僅僅寫了一個舉例)

def permission(func):
    def inner(request, *args, **kwargs):
        user_info = request.session.get('user_info')
        if not user_info:
            return redirect('/login.html')
        obj = MenuHelper(request, user_info['username'])
        action_list = obj.action()
        if not action_list:
            return HttpResponse('無權限訪問')
        kwargs['menu_string'] = obj.menu_tree()
        kwargs['action_list'] = action_list
        return func(request, *args, **kwargs)
    return inner


@permission
def index(request, *args, **kwargs):
    actions_list = kwargs.get('actions_list')
    menu_string = kwargs.get('menu_string')
    if "GET" in actions_list:
        result = models.User.objects.all()
    else:
        result = []
    return render(request, 'index.html', {
        'menu_string': menu_string,
        'actions_list': actions_list,
        'result': result,
    })

 

六、login.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="POST">
        {% csrf_token %}
        <input type="text" name="username" />
        <input type="text" name="pwd" />
        <input type="submit" value="提交" />
    </form>
</body>
</html>

 

 

 

七、index.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .content{
            margin-left: 20px;
            display: none;
        }
        .content a{
            display: block;
        }
        .active > .content{
            display: block;
        }
    </style>
</head>
<body>
    <div style="float: left;width: 20%;">
        {{ menu_string|safe }}
    </div>
    <div style="float: left;width: 80%;">
    </div>

</body>
</html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM