Django實戰1-權限管理功能實現-03:用戶認證

1 權限管理模型設計

1.1 建立app

新建一個app, 名稱叫system包含用戶管理、菜單管理和權限管理等系統基礎模塊。html

  • 使用pycharm打開咱們的項目,右鍵項目根目錄,選擇 New → Python Package, 在彈出的窗口輸入apps,這個包就用來存放項目中建立的全部app.
  • 選擇pycharm上方Tools,點擊Run manage.py Task..., 這時在pycharm下方會打開一個窗口,輸入startapp system 回車建立app, 以下圖:

image

  • 將剛剛建立的system 移動到 apps下
  • 爲了可以順利訪問到咱們新建的app,右鍵apps,選擇Mark Directory as → Sources root
  • 修改sandboxMP/sandboxMP/settings.py 加入以下內容:
import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
複製代碼

1.2 建立權限認證模型

sandboxMP項目使用的是自定義權限認證模型,模型說明:前端

Menu: 菜單管理,用來存儲系統可用的URL
Role: 角色組,經過外鍵關聯Menu,角色組中的用戶將繼承Role關聯菜單的訪問權限
Structure:組織架構,包含單位和部門信息
UserProfile: 自定義用戶認證模型,替換系統原有的User模型
複製代碼

下面內容就是權限認證的模型詳細內容,將以下內容複製到apps/system/models.pypython

from django.db import models
from django.contrib.auth.models import AbstractUser


class Menu(models.Model):
    """ 菜單 """
    name = models.CharField(max_length=30, unique=True, verbose_name="菜單名")  # unique=True, 這個字段在表中必須有惟一值.
    parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父菜單")
    icon = models.CharField(max_length=50, null=True, blank=True, verbose_name="圖標")
    code = models.CharField(max_length=50, null=True, blank=True, verbose_name="編碼")
    url = models.CharField(max_length=128, unique=True, null=True, blank=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = '菜單'
        verbose_name_plural = verbose_name

    @classmethod
    def get_menu_by_request_url(cls, url):
        return dict(menu=Menu.objects.get(url=url))


class Role(models.Model):
    """ 角色:用於權限綁定 """
    name = models.CharField(max_length=32, unique=True, verbose_name="角色")
    permissions = models.ManyToManyField("menu", blank=True, verbose_name="URL受權")
    desc = models.CharField(max_length=50, blank=True, null=True, verbose_name="描述")


class Structure(models.Model):
    """ 組織架構 """
    type_choices = (("unit", "單位"), ("department", "部門"))
    name = models.CharField(max_length=60, verbose_name="名稱")
    type = models.CharField(max_length=20, choices=type_choices, default="department", verbose_name="類型")
    parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父類架構")

    class Meta:
        verbose_name = "組織架構"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class UserProfile(AbstractUser):
    name = models.CharField(max_length=20, default="", verbose_name="姓名")
    birthday = models.DateField(null=True, blank=True, verbose_name="出生日期")
    gender = models.CharField(max_length=10, choices=(("male", "男"), ("female", "女")),
                              default="male", verbose_name="性別")
    mobile = models.CharField(max_length=11, default="", verbose_name="手機號碼")
    email = models.EmailField(max_length=50, verbose_name="郵箱")
    image = models.ImageField(upload_to="image/%Y/%m", default="image/default.jpg",
                              max_length=100, null=True, blank=True)
    department = models.ForeignKey("Structure", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="部門")
    post = models.CharField(max_length=50, null=True, blank=True, verbose_name="職位")
    superior = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="上級主管")
    roles = models.ManyToManyField("role", verbose_name="角色", blank=True)

    class Meta:
        verbose_name = "用戶信息"
        verbose_name_plural = verbose_name
        ordering = ['id']

    def __str__(self):
        return self.name

複製代碼

1.3 使用模型

定義好模型後,還要告訴Django使用這些模型,咱們須要修改settings.py文件,在INSTALLED_APPS中添加models.py所在應用的名稱:git

INSTALLED_APPS = [
     ...原內容省略...
     'system',
]
複製代碼

想要使用自定義的認證模型UserProfile, 還須要在setting.py中添加下面內容:github

AUTH_USER_MODEL = 'system.UserProfile'
複製代碼

注意:
在定義用戶模型的時候使用到了ImageField字段類型,在執行makemigrations前須要安裝依賴包:pillow,打開CMD窗口,進入本項目的python虛擬環境,而後安裝pillow:web

C:\Users\RobbieHan>workon sandboxMP
(sandboxMP) C:\Users\RobbieHan>pip install pillow
複製代碼

也能夠在pycharm 的Terminal終端窗口執行安裝命令: pip install pillow數據庫

最後執行makemigrations 和 migrate來生成數據表, 使用pycharm Tools,點擊Run manage.py Task..., 在manage.py窗口輸入下面命令:django

makemigrations
migrate
複製代碼

1.4 模型(Models)相關知識點

字段類型:
後端

在權限認證模型中使用到的字段類型以下:瀏覽器

CharField: 用來存儲字符串,必須制定一個參數 max_length用來限定字段最大長度
Foreignkey: 是一個關聯字段,建立多表之間的多對一關係,若是建立同表之間的遞歸關聯關係,可使用models.ForeignKey('self')
ManyToManyField: 用來實現多對多的關聯關係
DateField: 日期時間字段
EmailField: email字段,用來檢查email地址是否合法
ImageField: 圖片字段,用來定義圖片上傳和圖片檢查,須要安裝pillow庫
複製代碼

字段選項:

unique: 設置爲True, 則表示這個字段必須有惟一值,這是從數據庫級別來強制數據惟一,後面咱們還會介紹經過form驗證來確保數據輸入的惟一
verbose_name:
blank: 默認值是False, 設置爲True,則該字段潤許爲空
null: 默認值是False,若是爲True,Django會在數據庫中將空值轉存爲NULL
choices: 是一個可迭代結構(元祖),每一個元組中的第一個元素,是存儲在數據庫中的值;第二個元素是令人容易理解的描述。
複製代碼

on_delete :在django2.0版本之前,定關聯字段時,on_delete選項並非必須的,而在django2.0版本後,在定義關聯字段時on_delete是必需要定義的,經常使用參數以下:

on_delete=models.CASCADE,     # 刪除關聯數據,與之關聯也刪除
on_delete=models.DO_NOTHING,  # 刪除關聯數據,什麼也不作
on_delete=models.PROTECT,     # 刪除關聯數據,引起錯誤ProtectedError
on_delete=models.SET_NULL,    # 刪除關聯數據,與之關聯的值設置爲null
on_delete=models.SET_DEFAULT, # 刪除關聯數據,與之關聯的值設置爲默認值
複製代碼

須要注意的是在使用SET_NULL的時候,該字段在模型定義的時候須要設置可爲空,例如:

user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
複製代碼

一樣在使用SET_DEFAULT的時候,須要預先定義default:

user = models.ForeignKey(User, on_delete=models.SET_DEFAULT, default='默認值')
複製代碼

更多字段類型和字段選項請參考:
docs.djangoproject.com/en/1.11/ref…

2 用戶認證和訪問限制

用戶登陸認證的需求以下:

  • 用戶登錄系統才能夠訪問某些頁面,
  • 若是用戶沒有登錄而直接訪問就會跳轉到登錄界面,
  • 用戶在跳轉的登錄界面中完成登錄後,自動訪問跳轉到以前訪問的地址,
  • 用戶可使用用戶名、手機號碼或者其餘字段做爲登錄用戶名。

在pycharm中,選中sandboxMP/apps/system,右鍵,選擇 New → Python File, 在彈出的窗口輸入名稱:views_user,在剛建立的頁面中導入須要的模塊:

from django.shortcuts import render
from django.views.generic.base import View
from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login, logout
from django.urls import reverse
複製代碼

說明: 如下建立的視圖,都是寫在sandboxMP/apps/system/views_user.py文件中

2.1 建立index頁面視圖

index頁面視圖,是本項目建立的第一個視圖:

class IndexView(View):

    def get(self, request):
        return render(request, 'index.html')
複製代碼

知識點介紹:

一、視圖: Django官方文檔對「視圖」的介紹是用來封裝處理用戶請求和返回響應的邏輯。
咱們能夠定義視圖函數,用來接受Web請求並返回Web響應,也可使用基於類的視圖對象,本項目的視圖實現都是基於類建立的視圖,和基於函數的視圖相比據有必定的區別和優點:

  • 能夠經過單獨的方法編寫與HTTP方法相關的代碼(GET, POST等),無需經過條件分支來判斷HTTP方法
  • 可將代碼分解成可重用的組件,例如Mixin(多繼承),發揮面向對象技術優點,使用更加靈活,易於擴展

二、render函數: Django的快捷函數,結合給定的模板和一個給定的上下文字典,並返回一個選而後的HttpRespose對象,語法:render(request, template_name, context=None, content_type=None, status=None, using=None),其中 request 和template_name必須參數,其它爲可選參數。

2.2 建立用戶登錄視圖

在建立用戶登錄視圖前,先建立一個sandboxMP/apps/system/forms.py文件,用來作登錄用戶的輸入驗證,內容以下:

from django import forms


class LoginForm(forms.Form):
    username = forms.CharField(required=True, error_messages={"requeired": "請填寫用戶名"})
    password = forms.CharField(required=True, error_messages={"requeired": "請填寫密碼"})

複製代碼

建立用戶登錄視圖:

from .forms import LoginForm


class LoginView(View):

    def get(self, request):
        if not request.user.is_authenticated:
            return render(request, 'system/users/login.html')
        else:
            return HttpResponseRedirect('/')

    def post(self, request):
        redirect_to = request.GET.get('next', '/')
        login_form = LoginForm(request.POST)
        ret = dict(login_form=login_form)
        if login_form.is_valid():
            user_name = request.POST['username']
            pass_word = request.POST['password']
            user = authenticate(username=user_name, password=pass_word)
            if user is not None:
                if user.is_active:
                    login(request, user)
                    return HttpResponseRedirect(redirect_to)
                else:
                    ret['msg'] = '用戶未激活!'
            else:
                ret['msg'] = '用戶名或密碼錯誤!'
        else:
            ret['msg'] = '用戶和密碼不能爲空!'
        return render(request, 'system/users/login.html', ret)
複製代碼

知識點介紹:
Django使用會話和中間件來攔截認證系統中的請求對象。它們在每個請求上提供一個request.user屬性,表示當前的用戶。若是當前的用戶沒有登入,該屬性將設置成AnonymousUser的一個實例,不然將會是User實例。
一、request.user.is_authenticated: 用來判斷用戶是否登入,如LoginView中:

# 當用戶訪問登錄頁面時,判斷用戶若是未登入則訪問登錄頁面,若是登入則跳轉到首頁
if not request.user.is_authenticated:
    return render(request, 'system/users/login.html')
else:
    return HttpResponseRedirect('/')
複製代碼

二、is_valid(): Form實力的一個方法,用來作字段驗證,當輸入字段值合法時,它將返回True,同時將表單的數據存放到cleaned_data屬性中。
三、authenticate(request=None, **credentials): 用來認證用戶,credentials爲關鍵字參數,默認爲username和password,若是經過認證後端檢查,則返回一個User對象。
四、login(request, user, backend=None): 用來從視圖中登錄一個用戶,同時將用戶的ID保存在session表中。注意:在調用login()以前必須使用authenticate()成功認證這個用戶。
五、HttpResponseRedirect[source]: 用來重定向訪問,參數是重定向的地址,能夠是完整的URL,也能夠相想讀與項目的絕對路徑。

2.3 建立用戶登出視圖

class LogoutView(View):

    def get(self, request):
        logout(request)
        return HttpResponseRedirect(reverse('login'))
複製代碼

知識點介紹:
一、logout(request): 登出用戶。
二、reverse(viewname): 根據url name來進行url的反向解析。

2.4 配置用戶URL路由

想要經過URL來訪問視圖應用,還須要配置URL路由,修改sandboxMP/sandboxMP/urls.py:

from django.contrib import admin
from django.urls import path

from system.views_user import IndexView, LoginView, LogoutView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', IndexView.as_view(), name='index'),
    path('login/', LoginView.as_view(), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
]

複製代碼

2.5 建立認證用戶

在pycharm選擇 Tools,點擊Run manage.py Task..., 在打開的窗口中輸入createsuperuser,根據提示輸入用戶名,郵箱和密碼,操做過程以下:

manage.py@sandboxMP > createsuperuser
"C:\Program Files\JetBrains\PyCharm2017.3.2\bin\runnerw.exe" C:\Users\RobbieHan\Envs\sandboxMP\Scripts\python.exe "C:\Program Files\JetBrains\PyCharm2017.3.2\helpers\pycharm\django_manage.py" createsuperuser D:/ProjectFile/sandboxMP
用戶名:  admin
郵箱:  robbie_han@outlook.com
Warning: Password input may be echoed.
Password:  !qaz@wsx
Warning: Password input may be echoed.
Password (again):  !qaz@wsx
Superuser created successfully.

複製代碼

運行項目,訪問系統:http://127.0.0.1:8000,咱們並無登入用戶,直接能夠訪問首頁,這和咱們的要求不符。接下來實現頁面訪問限制,要求必須登入用戶才能訪問。

2.6 頁面訪問限制

頁面訪問限制的實現需求:

  • 用戶登陸系統才能夠訪問某些頁面
  • 若是用戶沒有登錄而直接訪問就會跳轉到登錄界面
  • 用戶在跳轉的登錄頁面完成登錄後,自動訪問跳轉前的訪問地址

新建sandboxMP/apps/system/mixin.py,寫入以下內容:

from django.contrib.auth.decorators import login_required


class LoginRequiredMixin(object):
    @classmethod
    def as_view(cls, **init_kwargs):
        view = super(LoginRequiredMixin, cls).as_view(**init_kwargs)
        return login_required(view)
複製代碼

修改sandboxMP/sandboxMP/settings.py, 加入LOGIN_URL

LOGIN_URL = '/login/'
複製代碼

須要登入用戶才能訪問的視圖,只須要繼承LoginRequiredMixin便可,修改後的IndexView視圖以下:

from .mixin import LoginRequiredMixin

class IndexView(LoginRequiredMixin, View): 

    def get(self, request):
        return render(request, 'index.html')
複製代碼

注意:LoginRequiredMixin位於繼承列表最左側位置

重啓項目,咱們再次訪問首頁,打開瀏覽器,輸入http://127.0.0.1:8000,這時咱們會發現,瀏覽器中的URL會變成:http://127.0.0.1:8000/login/?next=/, 須要咱們先登錄後纔會跳轉到首頁。
使用咱們在2.5小節中建立的用戶:admin,密碼: !qaz@wsx登錄系統

2.7 媒體文件的訪問

儘管在建立用戶時設置了默認頭像,而且已經放置了默認頭像使用的圖片,可是用戶登陸後仍是沒法顯示頭像,因此還須要配置媒體文件的訪問。

image

媒體文件是由用戶上傳的文件,路徑是變化的,好比用戶上傳的頭像文件。
設置文件上傳目錄

修改sandboxMP/sandboxMP/settings.py文件,添加以下配置:

MEDIA_URL = '/media/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
複製代碼

打開sandboxMP/sandboxMP/urls.py,新增以下配置:

from django.conf import settings
from django.urls import re_path
from django.views.static import serve

if settings.DEBUG:
    urlpatterns += [
        re_path(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
    
    ]
複製代碼

刷新頁面就能夠看到用戶頭像了,須要注意的是,這裏之因此使用if settings.DEBUG,是由於這種配置模式應該僅限用於開發模式,在生產環境應該經過web前端來處理這些媒體文件的訪問。

最新最全文檔發佈在知識星球,能夠經過微信搜索公衆號「知識星球」,直接回復"52824366"得到訪問入口
本節文檔對應源碼版本: github.com/RobbieHan/s…

很是歡迎感興趣的朋友,到個人Github或掘金上作客,閒暇之餘給個贊或Star,贈人玫瑰手留餘香
文檔配套項目地址:github.com/RobbieHan/s…
輕量級辦公管理系統項目開源地址:github.com/RobbieHan/g…

相關文章
相關標籤/搜索