python 全棧開發,Day107(CRM初始,權限組件之權限控制,權限系統表設計)

 1、CRM初始

CRM,客戶關係管理系統(Customer Relationship Management)。企業用CRM技術來管理與客戶之間的關係,以求提高企業成功的管理方式,其目的是協助企業管理銷售循環:新客戶的招徠、保留舊客戶、提供客戶服務及進一步提高企業和客戶的關係,並運用市場營銷工具,提供創新式的我的化的客戶商談和服務,輔以相應的信息系統或信息技術如數據挖掘和數據庫營銷來協調全部公司與顧客間在銷售、營銷以及服務上的交互。css

此係統主要是以教育行業爲背景,爲公司開發的一套客戶關係管理系統。考慮到各位童鞋可能處於各行各業,爲了擴大的系統使用範圍,特此將該項目開發改成組件化開發,讓同窗們能夠往後在本身公司快速搭建相似系統及新功能擴展。html

  • 權限系統,一個獨立的rbac組件。
  • stark組件,一個獨立的curd組件。
  • crm業務,以教育行業爲背景並整合以上兩個組件開發一套系統。

 

2、權限組件之權限控制

1. 問:爲何程序須要權限控制?前端

   答:生活中的權限限制,① 看災難片電影《2012》中富人和權貴有權登上諾亞方舟,窮苦老百姓只有等着災難的來臨;② 屌絲們,有沒有想過爲何那些長得漂亮身材好的姑娘在你身邊不存在呢?由於有錢人和漂亮姑娘都是珍貴稀有的,稀有的人在一塊兒玩耍和解鎖各類姿式。而你,無權擁有他們,只能本身玩本身了。
程序開發時的權限控制,對於不一樣用戶使用系統時候就應該有不一樣的功能,如:python

  • 普通員工
  • 部門主管
  • 總監
  • 總裁

因此,只要有不一樣角色的人員來使用系統,那麼就確定須要權限系統。jquery

2. 問:爲何要開發權限組件?git

   答:假設你今年25歲,從今天開始寫代碼到80歲,每一年寫5個項目,那麼你的一輩子就會寫275個項目,保守估計其中應該有150+個都須要用到權限控制,爲了之後再也不重複的寫代碼,因此就開發一個權限組件以便以後55年的歲月中使用。 親,不要太較真哦,你以爲程序員能到80歲麼,哈哈哈哈哈哈哈 
偷偷告訴你:老程序員開發速度快,其中一個緣由是經驗豐富,另一個就是他本身保留了不少組件,新系統開發時,只需把組件拼湊起來基本就能夠完成。程序員

3. 問:web開發中權限指的是什麼?github

   答:web程序是經過 url 的切換來查看不一樣的頁面(功能),因此權限指的其實就是URL,對url控制就是對權限的控制。web

結論:一我的有多少個權限就取決於他有多少個URL的訪問權限。ajax

權限表結構設計:初版

問答環節中已得出權限就是URL的結論,那麼就能夠開始設計表結構了。

  • 一個用戶能夠有多個權限。
  • 一個權限能夠分配給多個用戶。

你設計的表結構大概會是這個樣子:

如今,此時此刻是否是以爲本身設計出的表結構棒棒噠!!!

But,不管是是否認可,你仍是too young too native,由於老漢腚眼一看就有問題....

問題:假設 「老男孩」和「Alex」 這倆貨都是老闆,老闆的權限必定是很是多。那麼試想,若是給這倆貨分配權限時須要在【用戶權限關係表中】添加好多條數據。假設再次須要對老闆的權限進行修改時,又須要在【用戶權限關係表】中找到這倆人全部的數據進行更新,太他媽煩了吧!!! 相似的,若是給其餘相同角色的人來分配權限時,必然會很是繁瑣。

權限表結構設計:第二版

聰明機智的必定在上述的表述中看出了寫門道,若是對用戶進行角色的劃分,而後對角色進行權限的分配,這不就迎刃而解了麼。

  • 一我的能夠有多個角色。
  • 一個角色能夠有多我的。
  • 一個角色能夠有多個權限。
  • 一個權限能夠分配給多個角色。

表結構設計:

 此次調整以後,由原來的【基於用戶的權限控制】轉換成【基於角色的權限控制】,之後再進行分配權限時只須要給指定角色分配一次權限,給衆多用戶再次分配指定角色便可。

models.py 示例

from django.db import models


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name
View Code

小夥子,告訴你一個事實,不經意間,你竟然設計出了一個經典的權限訪問控制系統:rbac(Role-Based Access Control)基於角色的權限訪問控制。你這麼優秀,爲何不來老男孩IT教育?路飛學城也行呀! 哈哈哈哈。

注意:如今的設計還不是最終版,但以後的設計都是在此版本基礎上擴增的,爲了讓你們可以更好的理解,咱們暫且再此基礎上繼續開發,直到遇到沒法知足的狀況,再進行整改。

源碼示例猛擊下載

客戶管理之權限控制

學習知識最好的方式就是試錯,坑踩多了那麼學到的知識天然而然就多了,因此接下里下來咱們用《客戶管理》系統爲示例,提出功能並實現,而且隨着功能愈來愈多,一點點來找出問題,並解決問題。

目錄結構:

luffy_permission/
├── db.sqlite3
├── luffy_permission
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── rbac            # 權限組件,便於之後應用到其餘系統
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── templates
└── web            # 客戶管理業務
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── models.py
    ├── tests.py
    └── views.py
View Code

rbac/models.py

from django.db import models


class Permission(models.Model):
    """
    權限表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)

    def __str__(self):
        return self.title


class Role(models.Model):
    """
    角色
    """
    title = models.CharField(verbose_name='角色名稱', max_length=32)
    permissions = models.ManyToManyField(verbose_name='擁有的全部權限', to='Permission', blank=True)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """
    用戶表
    """
    name = models.CharField(verbose_name='用戶名', max_length=32)
    password = models.CharField(verbose_name='密碼', max_length=64)
    email = models.CharField(verbose_name='郵箱', max_length=32)
    roles = models.ManyToManyField(verbose_name='擁有的全部角色', to='Role', blank=True)

    def __str__(self):
        return self.name

rbac/models.py
View Code

web/models.py

from django.db import models


class Customer(models.Model):
    """
    客戶表
    """
    name = models.CharField(verbose_name='姓名', max_length=32)
    age = models.CharField(verbose_name='年齡', max_length=32)
    email = models.EmailField(verbose_name='郵箱', max_length=32)
    company = models.CharField(verbose_name='公司', max_length=32)


class Payment(models.Model):
    """
    付費記錄
    """
    customer = models.ForeignKey(verbose_name='關聯客戶', to='Customer')
    money = models.IntegerField(verbose_name='付費金額')
    create_time = models.DateTimeField(verbose_name='付費時間', auto_now_add=True)

web/models.py
View Code

《客戶管理》系統截圖:基本增刪改查和Excel導入源碼下載猛擊這裏

以上簡易版客戶管理系統中的URL有:

  • 客戶管理
    • 客戶列表:/customer/list/
    • 添加客戶:/customer/add/
    • 刪除客戶:/customer/list/(?P<cid>\d+)/
    • 修改客戶:/customer/edit/(?P<cid>\d+)/
    • 批量導入:/customer/import/
    • 下載模板:/customer/tpl/
  • 帳單管理
    • 帳單列表:/payment/list/
    • 添加帳單:/payment/add/
    • 刪除帳單:/payment/del/(?P<pid>\d+)/
    • 修改帳單:/payment/edit/<?P<pid>\d+/

那麼接下來,咱們就在權限組件中錄入相關信息:

  • 錄入權限
  • 建立用戶
  • 建立角色
  • 用戶分配角色
  • 角色分配權限

這麼一來,用戶登陸時,就能夠根據本身的【用戶】找到全部的角色,再根據角色找到全部的權限,再將權限信息放入session,之後每次訪問時候須要先去session檢查是否有權訪問。

已錄入權限數據源碼下載猛擊這裏

含用戶登陸權限源碼下載:猛擊這裏(簡易版)

含用戶登陸權限源碼下載猛擊這裏

至此,基本的權限控制已經完成,基本流程爲:

  • 用戶登陸,獲取權限信息並放入session
  • 用戶訪問,在中間件從session中獲取用戶權限信息,並進行權限驗證。

全部示例中的帳戶信息:

帳戶一:
    用戶名:alex
       密碼:123
 
帳戶二:
    用戶名:wupeiqi
       密碼:123
View Code

 

本文參考連接:

https://www.cnblogs.com/wupeiqi/articles/9178982.html

 

 

做業

1. django程序
2. 兩個app 
    - rbac,權限相關全部的東西
        - models.py(三個類5張表)
    - web,隨便寫業務處理
        - models.py 

3. 找URL並使用django admin 錄入到權限表
    urlpatterns = [
        url(r'^customer/list/$', customer.customer_list),
        url(r'^customer/add/$', customer.customer_add),
        url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
        url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
        url(r'^customer/import/$', customer.customer_import),
        url(r'^customer/tpl/$', customer.customer_tpl),

        url(r'^payment/list/$', payment.payment_list),
        url(r'^payment/add/$', payment.payment_add),
        url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
        url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
    ]
    
4. 角色和用戶管理


5. 寫代碼 
    a. 用戶登錄 
        - 獲取用戶信息放入session 
        - 獲取當前用戶全部的權限並寫入session 
    b. 編寫中間件作權限信息校驗
        - 獲取當前請求URL
        - 獲取當前用戶的全部權限
        - 權限校驗
View Code

請下載github代碼

https://github.com/987334176/luffy_permission/archive/v1.0.zip

 

錄入數據

修改rbac目錄下的admin.py,註冊表

from django.contrib import admin

# Register your models here.
from rbac import models

admin.site.register(models.Permission)
admin.site.register(models.Role)
admin.site.register(models.UserInfo)
View Code

建立超級用戶

python manage.py createsuperuser

登陸admin後臺,開始錄入數據

先增長用戶,再增長角色,最後設置權限

注意,權限來自於urls.py

添加url完成以後,綁定角色和權限

 

相關權限表關係

 

表記錄(大概)

 

測試ORM

這個做業,主要是能獲得用戶的受權url列表。若是這都不能查詢出來,那麼做業能夠放棄了!

先不着急寫頁面,用腳本測試orm

修改rbac目錄下的tests.py

from django.test import TestCase

# Create your tests here.
import os

if __name__ == "__main__":
    # 設置django環境
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_permission.settings")
    import django
    django.setup()

    from rbac import models

    # 固定用戶名和密碼
    user = 'xiao'
    pwd = '123'
    # 查詢表,用戶名和密碼是否匹配
    obj = models.UserInfo.objects.filter(name=user, password=pwd).first()
    role = obj.roles.all()  # 查詢當前用戶的全部角色
    permissions_list = []  # 空列表,用來存放用戶能訪問的url列表

    for i in role:  # 循環角色
        per = i.permissions.all()  # 查看當前用戶全部角色的全部權限
        # print(i.permissions.all())
        for j in per:
            # print(j.url)
            # 將全部受權的url添加到列表中
            permissions_list.append(j.url)

    print(permissions_list)
View Code

使用Pycharm執行輸出:

['^customer/list/$', '^customer/add/$', '^customer/edit/(?P<cid>\\d+)/$', '^customer/del/(?P<cid>\\d+)/$', '^customer/import/$', '^customer/tpl/$', '^payment/list/$', '^payment/add/$', '^payment/edit/(?P<pid>\\d+)/$', '^payment/del/(?P<pid>\\d+)/$']
View Code

注意:這裏面的url都是正則表達式!經過它來驗證用戶的url是否受權!

 

登陸頁面

先作用戶登陸

修改web目錄下的urls.py,增長路徑

from django.conf.urls import url
from web.views import customer,payment,auth,login

urlpatterns = [
    # 客戶管理
    url(r'^customer/list/$', customer.customer_list),
    url(r'^customer/add/$', customer.customer_add),
    url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
    url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
    url(r'^customer/import/$', customer.customer_import),
    url(r'^customer/tpl/$', customer.customer_tpl),
    # 帳單管理
    url(r'^payment/list/$', payment.payment_list),
    url(r'^payment/add/$', payment.payment_add),
    url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
    url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
    # 登陸相關
    url(r'^$', login.login),  # 前端
    url(r'^login/$', login.login),
    url(r'^auth/$', auth.AuthView.as_view({'post': 'login'})),  # 認證api
]
View Code

在web目錄下的views目錄下,建立文件login.py

from django.shortcuts import render, redirect,HttpResponse

def login(request):
    return render(request,"login.html")
View Code

使用session

在web目錄下的views目錄下,建立文件auth.py

from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from rbac import models
from utils.response import BaseResponse

class AuthView(ViewSetMixin,APIView):
    authentication_classes = []  # 空列表表示不認證

    def login(self,request,*args,**kwargs):
        """
        用戶登錄認證
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        response = BaseResponse()  # 默認狀態
        try:
            user = request.data.get('username')
            pwd = request.data.get('password')
            # print(user,pwd)
            # 驗證用戶和密碼
            obj = models.UserInfo.objects.filter(name=user,password=pwd).first()
            if not obj:  # 判斷查詢結果
                response.code = 1002
                response.error = '用戶名或密碼錯誤'
            else:
                role = obj.roles.all()  # 查詢當前用戶的全部角色
                permissions_list = []  # 定義空列表

                for i in role:  # 循環角色
                    per = i.permissions.all()  # 查看當前用戶全部角色的全部權限
                    # print(i.permissions.all())
                    for j in per:
                        # print(j.url)
                        # 將全部受權的url添加到列表中
                        permissions_list.append(j.url)

                # print(permissions_list)
                response.code = 1000

                # 增長session
                request.session['url'] = permissions_list

        except Exception as e:
            response.code = 10005
            response.error = '操做異常'

        # print(response.dict)
        return Response(response.dict)
View Code

 

在web目錄下的templates目錄下,建立login.html

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
    <script src="{% static 'plugins/sweetalert/sweetalert-dev.js' %} "></script>
    <link rel="stylesheet" href="{% static 'plugins/sweetalert/sweetalert.css' %}">
</head>
<body>
<div style="height: 100px;"></div>
<form action="/auth/" method="post">
    <lable>用戶名</lable>
    <input type="text" name="username" id="user">
    <lable>密碼</lable>
    <input type="password" name="password" id="pwd">
    <input type="button" id="sub" value="登陸">
</form>
<script>
    $("#sub").click(function () {
        $.ajax({
            url: "/auth/",
            type: "post",
            data: {
                username: $("#user").val(),
                password: $("#pwd").val(),
            },
            success: function (data) {
                console.log(data);
                if (data.code == 1000) { //判斷json的狀態
                    swal({
                        title: '登陸成功',
                        type: 'success',  //展現成功的圖片
                        timer: 500,  //延時500毫秒
                        showConfirmButton: false  //關閉確認框
                    }, function () {
                        window.location.href = "/customer/list/";  //跳轉
                    });
                } else {
                    swal("登陸失敗!", data.error,
                        "error");
                    {#window.location = "/backend/add_category/";#}
                }
            },
            error: function (data) {
                console.log('登陸異常');
            }
        })

    });

</script>
</body>
</html>
View Code

在web目錄下的templates目錄下,建立error.html

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
{#<link rel="stylesheet" href="http://mishengqiang.com/sweetalert/css/sweetalert.css">#}
<link rel="stylesheet" href="{% static 'plugins/sweetalert/sweetalert.css' %}">
{#<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>#}
<script src="{% static 'js/jquery-3.3.1.min.js' %}"></script>
{#<script src="http://mishengqiang.com/sweetalert/js/sweetalert-dev.js"></script>#}
<script src="{% static 'plugins/sweetalert/sweetalert-dev.js' %}"></script>
<div>
    {#獲取錯誤信息#}
    <input type="hidden" id="msg" value="{{ msg }}">
    <input type="hidden" id="url" value="{{ url }}">
</div>

<script>
    $(function () {
        var msg = $("#msg").val();
        var url = $("#url").val();
        console.log(msg);
        console.log(url);

        if (msg.length > 0) {  //判斷是否有錯誤信息
            swal({
                title: msg,
                text: "1秒後自動關閉。",
                type: 'error',
                timer: 1000,
                showConfirmButton: false
            }, function () {
                window.location.href = url;  //跳轉指定url
            });

        }


    })
</script>

</body>
</html>
View Code

 

中間件驗證

在web目錄下建立文件middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, redirect, HttpResponse
from luffy_permission import settings
import os

class AuthMD(MiddlewareMixin):  # 驗證登陸
    white_list = ['/','/login/','/auth/','/admin/' ]  # 白名單
    # black_list = ['/black/', ]  # 黑名單
    ret = {"status": 0, 'url': '', 'msg': ''}  # 默認狀態

    def process_request(self, request):  # 請求以前
        request_url = request.path_info  # 獲取請求路徑
        # get_full_path()表示帶參數的路徑
        # print(request.path_info, request.get_full_path())

        # 判斷請求路徑不在白名單中
        if request_url not in self.white_list:
            import re
            per_url = request.session.get("url")  # 獲取用戶session中的url列表
            # print(per_url)
            if per_url:
                for i in per_url:  # 循環url列表
                    # 使用正則匹配。其中i爲正則表達式,request_url.lstrip('/')表示去除左邊的'/'
                    result = re.match(i, request_url.lstrip('/'))
                    # print(result)
                    if result:  # 判斷匹配結果
                        print('受權經過',request_url)
                        return None  # return None表示能夠繼續走下面的流程
                    # else:
                    #     print('受權不經過',request_url)
                    #     # return redirect('/login/')
    

                # 錯誤頁面提示
                self.ret['msg'] = "未受權,禁止訪問!"
                self.ret['url'] = "/login/"
                path = os.path.join(settings.BASE_DIR, 'web/templates/error.html'),
                return render(request, path, self.ret)  # 渲染錯誤頁面
View Code

在中間件中,只要return none,就會進入下一個中間件!

修改settings.py,註冊中間件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'web.middlewares.AuthMD',  # 自定義中間件AuthMD
]
View Code

 

測試登陸,訪問頁面

http://127.0.0.1:8000/login/

訪問一個不存在url

完整代碼,請查看github

https://github.com/987334176/luffy_permission/archive/v1.1.zip

相關文章
相關標籤/搜索