第 11 章    使用者認證

▸ 登記 account App

blog/settings.py
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',
    'account',
    'article',
    'main',     
]

▸ 帳號 URL mapping

account/urls.py
from django.urls import path
from account import views

app_name = 'account'
urlpatterns = [

]

▸ 至專案直屬 App 中加入 URL mapping

blog/urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls', namespace='account')),
    path('article/', include('article.urls', namespace='article')),
    ...
]

▸ 客製化 User model

account/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    fullName = models.CharField(max_length=128)
    website = models.URLField(blank=True, null=True)
    address = models.CharField(max_length=128, blank=True, null=True)

    def __str__(self):
        return self.fullName + ' (' + self.username + ')'

▸ 設定客製化使用者 Model

blog/settings.py
...
STATIC_URL = '/static/'

AUTH_USER_MODEL = 'account.User'

▸ 修改資料填充程式

populate/admin.py
from populate import base
from django.contrib.auth.models import User
from account.models import User


def populate():
    ...
populate/users.py
from populate import base
from django.contrib.auth.models import User
from account.models import User


def populate():
    ...

▸ 將 User model 匯入及登記至 admin 頁面:

account/admin.py
from django.contrib import admin
from account.models import User


admin.site.register(User)

▸ 訪客註冊表單類別

account/forms.py
from django import forms
from account.models import User


class UserForm(forms.ModelForm):
    username = forms.CharField(label='帳號')
    password = forms.CharField(label='密碼', widget=forms.PasswordInput)
    password2 = forms.CharField(label='確認密碼', widget=forms.PasswordInput)
    fullName = forms.CharField(label='姓名', max_length=128)
    website = forms.URLField(label='個人網址', max_length=128)
    address = forms.CharField(label='住址', max_length=128)
    
    class Meta:
        model = User
        fields = ['username', 'password', 'password2', 'fullName', 'website', 'address']

    def clean_password2(self):
        password = self.cleaned_data.get('password')
        password2 = self.cleaned_data.get('password2')
        if password and password2 and password!=password2:
            raise forms.ValidationError('密碼不相符')
        return password2

    def save(self):
        user = super().save(commit=False)
        user.set_password(user.password)
        user.save()
        return user

▸ 訪客註冊函式

account/views.py
from django.shortcuts import render, redirect
from django.contrib import messages

from account.forms import UserForm


def register(request):
    '''
    Register a new user
    '''
    template = 'account/register.html'
    if request.method == 'GET':
        return render(request, template, {'userForm':UserForm()})

    # POST
    userForm = UserForm(request.POST)
    if not userForm.is_valid():
        return render(request, template, {'userForm':userForm})

    userForm.save()
    messages.success(request, '歡迎註冊')
    return redirect('main:main')

▸ 訪客註冊 URL mapping

account/urls.py
from django.urls import path
from account import views


urlpatterns = [
    path('register/', views.register, name='register'),
]

▸ 註冊連結

main/templates/main/menu.html
<ul id="menu">
  ...
  <li><a href="{% url 'article:article' %}">部落格</a></li>
  <li><a href="{% url 'account:register' %}">註冊</a></li>
</ul>

▸ 註冊範本

account/templates/account/register.html
{% extends "main/base.html" %}
{% block heading %}註冊{% endblock %}
{% block content %}
<form method="post" action="{% url 'account:register' %}">
  {% csrf_token %}
  {{ userForm.as_p }}
  <input class="btn" type="submit" value="送出">
</form>
{% endblock %}

▸ 使用者登入函式

account/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.auth import login as auth_login

from account.forms import UserForm


def register(request):
    ...


def login(request):
    '''
    Login an existing user
    '''
    template = 'account/login.html'
    if request.method == 'GET':
        return render(request, template)

    # POST
    username = request.POST.get('username')
    password = request.POST.get('password')
    if not username or not password:    # Server-side validation
        messages.error(request, '請填資料')
        return render(request, template)

    user = authenticate(username=username, password=password)
    if not user:    # authentication fails
        messages.error(request, '登入失敗')
        return render(request, template)

    # login success
    auth_login(request, user)
    messages.success(request, '登入成功')
    return redirect('main:main')

▸ 使用者登入 URL mapping

account/urls.py
...

urlpatterns = [
    path('register/', views.register, name='register'),
    path('login/', views.login, name='login'),
]

▸ 使用者登入連結

main/templates/main/menu.html
<ul id="menu">
  ...
  <li><a href="{% url 'account:register' %}">註冊</a></li>
  <li><a href="{% url 'account:login' %}">登入</a></li>
</ul>

▸ 使用者登入範本

account/templates/account/login.html
{% extends "main/base.html" %}
{% block heading %}登入{% endblock %}
{% block content %}
<form method="post" action="{% url 'account:login' %}">
  {% csrf_token %}
  <p>使用者名稱:<input type="text" name="username"></p>
  <p>密碼:<input type="password" name="password"></p>
  <p><input class="btn" type="submit" value="送出"></p>
</form>
{% endblock %}

▸ 使用者登出函式

account/views.py
...
from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout

from account.forms import UserForm, UserProfileForm


def login(request):
    ...


def logout(request):
    '''
    Logout the user
    '''
    auth_logout(request)
    messages.success(request, '歡迎再度光臨')
    return redirect('main:main')

▸ 使用者登出 URL mapping

account/urls.py
...

urlpatterns = [
    ...
    path('login/', views.login, name='login'),
    path('logout/', views.logout, name='logout'),
]

▸ 使用者登出連結

main/templates/main/menu.html
...
  <li><a href="{% url 'article:article' %}">部落格</a></li>
  {% if user.is_authenticated %}
    <li><a href="{% url 'account:logout' %}">登出 ({{ user.username }})</a></li>
  {% else %}
    <li><a href="{% url 'account:register' %}">註冊</a></li>
    <li><a href="{% url 'account:login' %}">登入</a></li>
  {% endif %}
</ul>

▸ 本章完成專案:blog11.zip