Django 中自定義用戶模型及集成認證受權功能總結

1. 概述

Django 中的 django.contrib.auth 應用提供了完整的用戶及認證受權功能。html

Django 官方推薦基於內置 User 數據模型建立新的自定義用戶模型,方便添加 birthday 等新的用戶字段和功能。python

本文包含的內容有:django

  • 介紹在 Django 中如何自定義用戶模型,並集成到系統。
  • 定製 django.contrib.auth 應用使用的模板文件。
  • 在系統中集成認證與受權功能。

如下全部示例在 Python 3.8.2 + Django 2.1 中實現。bash

2. 自定義用戶模型

2.1. 建立認證與受權相關的單獨應用 accounts

$ python manage.py startapp accounts

將應用添加到項目中:app

# project_dir/settings.py
INSTALLED_APPS = [
    # Local
    'accounts.apps.AccountsConfig',
    #...
]

2.2. 建立自定義用戶模型 CustomUser

Django 官方文檔中推薦基於 AbstractBaseUser 建立自定義用戶模型,可是通常基於 AbstractUser 建立再方便。post

本命中的自定義 CustomUser 中新增了字段 birthday。ui

# accounts/models.py

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

class CustomUser(AbstractUser):

    birthday = models.DateField(null=True, blank=True)

2.3. 集成自定義用戶模型

經過 AUTH_USER_MODEL 告訴系統新的用戶模型:url

# project_dir/settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'

以後可經過 get_user_model() 獲取該自定義用戶模型:spa

# in view or model files
from django.contrib.auth import get_user_model

CustomUser = get_user_model()

django.contrib.auth 應用已實現了完整的 login, logout 功能,並已在 django.contrib.auth.urls 中定義了 login, logout, password_change, password_change_done, password_reset, password_reset_done, password_reset_confirm, password_reset_complete 等 URL。code

django.contrib.auth.urls 集成到項目中:

# project_dir/urls.py
from django.urls import path, include
urlpatterns = [
   path('accounts/',  include('django.contrib.auth.urls'),
   #...
]

集成後,便可訪問 /accounts/login/, /accounts/logout/, /accounts/password_change/ 等功能,同時時視圖和模板中也可訪問這個 URL 定義:

<!-- in template files -->
<a href="{% url 'login' %}">Login URL</a>
# in view files
from django.urls import reverse, reverse_lazy

login_url = reverse('login')

#in Class Based View:
login_url = reverse_lazy('login')

2.4. 集成自定義用戶模型到後臺管理界面

後臺管理界面中,添加新用戶時呈現的表單由 django.contrib.auth.forms.UserCreationForm 提供,而更新用戶時呈現的表單由 django.contrib.auth.forms.UserChangeForm 提供。

爲自定義用戶模型定製這兩個表單:

# accounts/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):


    class Meta(UserCreationForm.Meta):

        model = CustomUser
        fields = ('username', 'email', 'birthday', )


class CustomUserChangeForm(UserChangeForm):


    class Meta(UserChangeForm.Meta):

        model = CustomUser
        fields = UserChangeForm.Meta.fields + ( 'birthday', )

註冊到 admin 中:

# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import CustomUser
from .forms import CustomUserCreationForm, CustomUserChangeForm

class CustomUserAdmin(UserAdmin):

    model = CustomUser

    add_form = CustomUserCreationForm
    form = CustomUserChangeForm

    list_display = ['email', 'username', 'birthday', 'is_staff']

admin.site.register(CustomUser, CustomUserAdmin)

3. 定製 django.contrib.auth 應用使用的模板文件

3.1. 定義模板文件

django.contrib.auth 中 login, logout, password_change, password_change_done, password_reset, password_reset_done, password_reset_confirm, password_reset_complete 等視圖,訪問的相應模板需保存在registration/ 目錄下,模板文件有: login.html, password_change_done.html, password_change_form.html, password_reset_complete.html, password_reset_confirm.html, password_reset_done.html, password_reset_form.html 等。

默認配置下,模板文件需保存在 <app-name>/templates/<app-name>/registration/ 目錄下,如 accounts/templates/accounts/registration/login.html

對於小項目,能夠將模板目錄設置爲扁平化:

# project_dir/settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], # new
        #...
    }
]

從而模板能夠保存在 templates/ 目錄中,如 templates/registration/login.html

模板中可以使用 form 變量,例如:

<!-- templates/registration/login.html -->
{% extends 'base.html' %}

{% block title %}Login{% endblock title %}

{% block content %}
<h2>Login</h2>
<form method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <button class="btn btn-success ml-2" type="submit">Login</button>
</form>
{% endblock content %}

3.2. 建立註冊功能

django.contrib.auth 沒有實現 sign up 功能。

基於 CreateView 建立 SignUpView 視圖:

# accounts/views.py
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy

from .models import CustomUser
from .forms import CustomUserCreationForm

class SignupView(CreateView):
    #model = CustomUser
    form_class = CustomUserCreationForm
    template_name = 'signup.html'

    success_url = reverse_lazy('login')

添加模板文件:

<!-- templates/signup.html -->
{% extends 'base.html' %}

{% load crispy_forms_tags %}
{% block title %}Signup{% endblock title %}

{% block content %}
<h2>Signup</h2>
<form method="post">
  {% csrf_token %}
  {{ form|crispy }}
  <button class="btn btn-success" type="submit">Signup</button>
</form>
{% endblock content %}

添加應用級別的 URL 配置:

# accounts/urls.py
from django.urls import path

from .views import SignUpView

urlpatterns = [
    path('signup/', SignUpView.as_view(), name='signup'),
]

集成到項目級別的 URL 配置中:

# project_dir/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('accounts.urls')),
    path('accounts/', include('django.contrib.auth.urls')),
    #...
]

4. 在系統中集成認證與受權功能

4.1. 認證:要求登陸後才能訪問

視圖應繼承加入 LoginRequiredMixin,屬性值 login_url 設置當沒有登陸時,將轉向的登陸頁面地址或 URL name:

# in view files

from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied

from .models import Article

class ArticleDeleteView(LoginRequiredMixin, DeleteView):

    model = Article
    template_name = 'article_delete.html'
    success_url = reverse_lazy('article_list')
    login_url = 'login'

4.2. 受權:只有特定的用戶或權限才能訪問

CBV 中,代碼調用入口是 dispatch() 方法,能夠在該方法中實現權限驗證,當權限不夠時拋出異常:

# in view files

from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied

from .models import Article

class ArticleDeleteView(LoginRequiredMixin, DeleteView):

    model = Article
    template_name = 'article_delete.html'
    success_url = reverse_lazy('article_list')
    login_url = 'login'

    def dispatch(self, request, *args, **kwargs):
        obj = self.get_object()
        if obj.author != request.user:
            raise PermissionDenied()

        return super().dispatch(request, *args, **kwargs)

資源

相關文章
相關標籤/搜索