帳號管理

這個專案是要讓教師與學生在線上進行互動,大部份的操作都需要取得使用者的身分,再決定要如何處理。所以這邊先處理使用者帳號。

建立專案與應用程式

開啟終端機或命令提示字元程式,以下面指令建立一個名為 eclassroom 的新專案:

django-admin.py startproject eclassroom

指令執行完成後,會在當前的工作資料夾產生一個名為 eclassroom 的資料夾,接下來將工作目錄切換至方才建立的 eclassroom 專案資料夾,然後在專案下新增應用程式 user

cd eclassroom python manage.py startapp user

接著修改專案設定檔,將它加入專案的應用程式中。請修改 eclassroom/settings.py,修改第 28, 58, 107, 109 行,新增第 40, 122 - 125 行

ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user', ]
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ['templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
LANGUAGE_CODE = 'zh-hant' TIME_ZONE = 'Asia/Taipei'
STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] LOGIN_URL = '/user/login/' # 使用者登入路徑 LOGIN_REDIRECT_URL = '/' # 登入後預設導向頁面

在專案中會使用到自訂 CSS 與 Javascript 檔案,所以要透過第 122 行指定靜態檔案的儲存位置。

使用者登入與登出

執行以下指令建立資料庫,並建立管理員帳號:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser

接著開啟網站服務:

python manage.py runserver 0.0.0.0:80

建立路徑規則

開啟專案路徑規則檔 eclassroom/urls.py,修改第 17 行,並新增第 18, 22 - 29 行

from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view( template_name='home.html', extra_context={'title': '歡迎光臨'} ), name='home' ), path('user/', include('user.urls')), ]

我們將使用者管理相關的功能都集中到 user 這個自訂的應用程式來實作,先新增該應用程式的路徑規則檔 user/urls.py,內容如下:

from django.urls import path from .views import * urlpatterns = [ path('', include('django.contrib.auth.urls')), ]

登入、登出,以及個人密碼變更等功能不需要在 views.py 裡自行實作,因為 Django 內建的應用程式 django.contrib.auth 已經有現成的功能可以直接使用,所以這邊直接將 django.contrib.auth.urls 定義的路徑規則引用進來即可。

頁面範本

請先在專案資料夾內建立 templates 資料夾,用來集中存放專案會使用到的頁面範本,專案會使用到自訂的 CSS 與 Javascript 檔案等靜態資源,也請在專案資料夾內建立 static 資料夾來存放靜態檔案,另外在 static 資料夾內,分別建立 cssjs 資料夾以便將 CSS 樣式表與 Javascript 程式檔分開放置。

至目前為止,專案資料夾的結構大致如下:

.
├── eclassroom/
├── static/
│   ├── css/
│   └── js/
├── templates/
└── user/
網站基底範本

如果覺得 Bootstrap 的原始配色看得有點膩了,又不曉得怎麼自己調整配色的話,可以上 Bootswatch(https://bootswatch.com/) 網站挑一個別人配好的順眼樣式來替換 Bootstrap 原始的設定:

這裡以 Minty 為例,點按該卡片下方的「DOWNLOAD」按鈕後會下載一個 bootstrap.min.css 的檔案,將其複製到專案的 static/css/ 資料夾內。

新增網站基底範本 templates/base.html,內容如下:

{% load static %}<!DOCTYPE html> <html lang="zh-hant"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"> <link rel="stylesheet" href="{% static 'css/custom.css' %}"> <title>線上教室</title> </head> <body> {% include "navbar.html" %} <div class="container my-3"> {% if messages %} <section id="messages"> <ul class="messages pl-0"> {% for msg in messages %} <li class="alert alert-{{ msg.tags }}">{{ msg }}</li> {% endfor %} </ul> </section> {% endif %} <section id="main-content"> {% if title %} <h1 class="title">{{ title }}</h1> {% endif %} {% block content %}{% endblock %} </section> </div> <!--FOOTER--> <footer class="border-top p-1"> <div class="container"> <small class="text-muted">Copyright 2020 線上教室</small> </div> </footer> <!--/FOOTER--> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script> <script src="{% static 'js/custom.js' %}"></script> {% block custom_scripts %}{% endblock %} </body> </html>

新增自訂的 Javascript 程式檔 static/js/custom.js,在裡面撰寫自訂的工具函式:

function add_css_class(filter, ...classes) { document.querySelectorAll(filter).forEach(function(item) { item.classList.add(...classes); }); } add_css_class('.alert-error', 'alert-danger');

新增自訂的 CSS 樣式檔 static/css/custom.css,填入後面會用到的自訂 CSS 規則組:

#work-detail input[name="score"] { width: 100px; } #work-list .work { padding: .75rem 1.25rem; margin: .25rem 0; border: 1px solid #eee; border-left: .25rem solid #eee; border-radius: .25rem; } #work-list .work-memo { margin-top: .5rem; } #work-list .work-meta { display: flex; justify-content: space-between; } #work-list .work-score { font-size: 200%; color: #FF7851; } .bd-callout-warning { border-left-color: #f0ad4e; } .bd-callout-info { border-left-color: #5bc0de; } .bd-callout-danger { border-left-color: #FF7851; } #work-detail .score { margin: 5pt; font-size: 20pt; color: red; border-bottom: 3px double red; text-align: right; } #work-list .score { color: red; border-bottom: 3px double red; }

接著新增導覽列範本 templates/navbar.html

<nav class="navbar navbar-expand-md navbar-dark bg-primary"> <div class="container"> <a class="navbar-brand" href="/">線上教室</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarMenu" aria-controls="navbarMenu" aria-expanded="false" aria-label="Toggle navigation"> <i class="navbar-toggler-icon"></i> </button> <div class="collapse navbar-collapse" id="navbarMenu"> <ul class="navbar-nav"> {% if user.is_authenticated %} <li class="nav-item dropdown"> <a href="#" class="nav-link dropdown-toggle" id="user-dropdown" data-toggle="dropdown"> <i class="fas fa-user"></i> {{ user.username }} </a> <div class="dropdown-menu" aria-labelledby="user-dropdown"> <a href="{% url 'password_change' %}" class="dropdown-item"> <i class="fas fa-lock"></i> 變更密碼 </a> <a href="{% url 'logout' %}" class="dropdown-item"> <i class="fas fa-sign-out-alt"></i> 登出 </a> </div> </li> {% else %} <li class="nav-item"> <a href="{% url 'login' %}" class="nav-link"> <i class="fas fa-sign-in-alt"></i> 登入 </a> </li> {% endif %} </ul> </div> </div> </nav>

首頁範本

建立首頁範本 templates/home.html

{% extends "base.html" %} {% block content %} <p>歡迎光臨線上教室!</p> {% endblock %}

先放個簡單的訊息就好。

登入頁面範本

建立 templates/registration 資料夾,接著新增登入範本檔 templates/registration/login.html

{% extends "base.html" %} {% block content %} {% if form.errors %} <div class="alert alert-danger"> {{ form.errors }} </div> {% endif %} <form action="" method="POST"> {% csrf_token %} <div id="login-form" class="form-inline"> <input type="text" name="username" placeholder="請輸入您的帳號" autofocus> <input type="password" name="password" placeholder="請輸入您的密碼"> <input type="submit" class="btn btn-primary" value="登入"> </div> </form> {% endblock %} {% block custom_scripts %} <script> add_css_class('#login-form input[placeholder]', 'form-control', 'mr-2'); </script> {% endblock %}

add_css_class() 來新增頁面上的 HTML 元素所套用的 CSS 類別,藉以調整其外觀。

登出頁面範本

建立登出範本 templates/registration/logged_out.html

{% extends "base.html" %} {% block content %} <p class="alert alert-info">您已登出!!</p> <a href="{% url 'login' %}" class="btn btn-primary">請按此處重新登入</a> {% endblock %}

變更個人密碼頁面範本

建立變更密碼範本 templates/registration/password_change_form.html

{% extends "base.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="card"> <div class="card-body"> <table class="table table"> {{ form.as_table }} </table> </div> <div class="card-footer"> <input type="submit" value="送出" class="btn btn-primary"> </div> </div> </form> {% endblock %} {% block custom_scripts %} <script> add_css_class('form table input', 'form-control'); add_css_class('.errorlist', 'alert', 'alert-danger'); add_css_class('.helptext ul', 'alert', 'alert-light'); </script> {% endblock %}

:bulb: 範本裡的表單欄位自行撰寫好,還是自動展開得好?

{{ form.as_table }}{{ form.as_p }}{{ form.as_ul }} 在範本檔中自動展開表單欄位的話,在表單發生驗證失敗的狀況時,會自動發生錯誤的欄位加上錯誤訊息。如果沒有特殊需要的話,建議盡量以自動展開表單的方式為優先考量,避免自行手刻表單欄位的 HTML 原始碼,這樣會比較省事。

個人密碼變更完成頁面範本

變更密碼還需要變更完成的頁面範本,請建立範本檔 templates/registration/password_change_done.html

{% extends "base.html" %} {% block content %} <p class="alert alert-info">密碼已成功變更!</p> {% endblock %}

使用者帳號管理

接下來要實作帳號列表、註冊新帳號、修改使用者帳號資料、修改使用者密碼,以及切換使用者的教師角色等帳號管理相關功能。

新增路徑規則

修改應用程式 user 的路徑規則檔 user/urls.py

urlpatterns = [ path('', include('django.contrib.auth.urls')), path('', UserDashboard.as_view(), name='user_dashboard'), path('list/', UserList.as_view(), name='user_list'), path('register/', UserRegister.as_view(), name='user_register'), path('<int:pk>/', UserView.as_view(), name='user_view'), path('<int:pk>/edit/', UserEdit.as_view(), name='user_edit'), path('<int:pk>/password/', UserPasswordUpdate.as_view(), name='user_password'), path('<int:uid>/teacher/', UserTeacherToggle.as_view(), name='user_teacher_toggle'), ]

新增處理視圖

開啟 user/views.py,來實作各功能的處理視圖:

管理員才允許操作的混成類別

接下來實作的大部份都是管理員才允許操作的功能,為了方便程式撰寫,先定義一個協助檢查是否為管理員的混成類別:

from django.views.generic import * from django.urls import reverse_lazy from django.contrib.auth.mixins import * from django.contrib.auth.models import User, Group from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q, Subquery from django import forms from .models import * # 限定管理員才允許操作的混成類別 class SuperuserRequiredMixin(AccessMixin): def dispatch(self, request, *args, **kwargs): if not request.user.is_superuser: return self.handle_no_permission() return super().dispatch(request, *args, **kwargs)
使用者註冊
# 使用者註冊 class UserRegister(CreateView): extra_context = {'title': '使用者註冊'} model = User fields = ['username', 'first_name', 'last_name', 'email', 'password'] template_name = 'form.html' success_url = reverse_lazy('home') # 註冊完成返回首頁 # 取得要顯示的表單內容 def get_form(self): form = super().get_form() form.fields['first_name'].label = '真實姓名' form.fields['first_name'].required = True form.fields['last_name'].label = '學校名稱' form.fields['last_name'].required = True form.fields['password2'] = forms.CharField(label='密碼驗證', max_length=255) return form # 表單驗證 def form_valid(self, form): user = form.save(commit=False) pw1 = form.cleaned_data['password'] pw2 = form.cleaned_data['password2'] if pw1 != pw2: form.add_error('password2', '密碼與驗證密碼不相符') return super().form_invalid(form) user.set_password(pw1) return super().form_valid(form)

使用者註冊這個功能所做的,實際上就是在 User 這個資料模型新增一筆紀錄,所以可以處理視圖可以繼承 CreateView 類別來實作:

使用者列表
# 使用者列表 class UserList(SuperuserRequiredMixin, ListView): extra_context = {'title': '使用者列表'} model = User ordering = ['username'] paginate_by = 20 template_name = 'user/user_list.html' def get_queryset(self): return super().get_queryset().prefetch_related('groups')
檢視使用者資料
class UserView(SuperuserRequiredMixin, DetailView): model = User template_name = 'user/user_detail.html' context_object_name = 'tuser'

帳號管理

這個專案是要讓教師與學生在線上進行互動,大部份的操作都需要取得使用者的身分,再決定要如何處理。所以這邊先處理使用者帳號。

建立專案與應用程式

開啟終端機或命令提示字元程式,以下面指令建立一個名為 eclassroom 的新專案:

django-admin.py startproject eclassroom

指令執行完成後,會在當前的工作資料夾產生一個名為 eclassroom 的資料夾,接下來將工作目錄切換至方才建立的 eclassroom 專案資料夾,然後在專案下新增應用程式 user

cd eclassroom python manage.py startapp user

接著修改專案設定檔,將它加入專案的應用程式中。請修改 eclassroom/settings.py,修改第 28, 58, 107, 109 行,新增第 40, 122 - 125 行

ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user', ]
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ['templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
LANGUAGE_CODE = 'zh-hant' TIME_ZONE = 'Asia/Taipei'
STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] LOGIN_URL = '/user/login/' # 使用者登入路徑 LOGIN_REDIRECT_URL = '/' # 登入後預設導向頁面

在專案中會使用到自訂 CSS 與 Javascript 檔案,所以要透過第 122 行指定靜態檔案的儲存位置。

使用者登入與登出

執行以下指令建立資料庫,並建立管理員帳號:

python manage.py makemigrations python manage.py migrate python manage.py createsuperuser

接著開啟網站服務:

python manage.py runserver 0.0.0.0:80

建立路徑規則

開啟專案路徑規則檔 eclassroom/urls.py,修改第 17 行,並新增第 18, 22 - 29 行

from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view( template_name='home.html', extra_context={'title': '歡迎光臨'} ), name='home' ), path('user/', include('user.urls')), ]

我們將使用者管理相關的功能都集中到 user 這個自訂的應用程式來實作,先新增該應用程式的路徑規則檔 user/urls.py,內容如下:

from django.urls import path from .views import * urlpatterns = [ path('', include('django.contrib.auth.urls')), ]

登入、登出,以及個人密碼變更等功能不需要在 views.py 裡自行實作,因為 Django 內建的應用程式 django.contrib.auth 已經有現成的功能可以直接使用,所以這邊直接將 django.contrib.auth.urls 定義的路徑規則引用進來即可。

頁面範本

請先在專案資料夾內建立 templates 資料夾,用來集中存放專案會使用到的頁面範本,專案會使用到自訂的 CSS 與 Javascript 檔案等靜態資源,也請在專案資料夾內建立 static 資料夾來存放靜態檔案,另外在 static 資料夾內,分別建立 cssjs 資料夾以便將 CSS 樣式表與 Javascript 程式檔分開放置。

至目前為止,專案資料夾的結構大致如下:

.
├── eclassroom/
├── static/
│   ├── css/
│   └── js/
├── templates/
└── user/
網站基底範本

如果覺得 Bootstrap 的原始配色看得有點膩了,又不曉得怎麼自己調整配色的話,可以上 Bootswatch(https://bootswatch.com/) 網站挑一個別人配好的順眼樣式來替換 Bootstrap 原始的設定:

這裡以 Minty 為例,點按該卡片下方的「DOWNLOAD」按鈕後會下載一個 bootstrap.min.css 的檔案,將其複製到專案的 static/css/ 資料夾內。

新增網站基底範本 templates/base.html,內容如下:

{% load static %}<!DOCTYPE html> <html lang="zh-hant"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css"> <link rel="stylesheet" href="{% static 'css/custom.css' %}"> <title>線上教室</title> </head> <body> {% include "navbar.html" %} <div class="container my-3"> {% if messages %} <section id="messages"> <ul class="messages pl-0"> {% for msg in messages %} <li class="alert alert-{{ msg.tags }}">{{ msg }}</li> {% endfor %} </ul> </section> {% endif %} <section id="main-content"> {% if title %} <h1 class="title">{{ title }}</h1> {% endif %} {% block content %}{% endblock %} </section> </div> <!--FOOTER--> <footer class="border-top p-1"> <div class="container"> <small class="text-muted">Copyright 2020 線上教室</small> </div> </footer> <!--/FOOTER--> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script> <script src="{% static 'js/custom.js' %}"></script> {% block custom_scripts %}{% endblock %} </body> </html>

新增自訂的 Javascript 程式檔 static/js/custom.js,在裡面撰寫自訂的工具函式:

function add_css_class(filter, ...classes) { document.querySelectorAll(filter).forEach(function(item) { item.classList.add(...classes); }); } add_css_class('.alert-error', 'alert-danger');

新增自訂的 CSS 樣式檔 static/css/custom.css,填入後面會用到的自訂 CSS 規則組:

#work-detail input[name="score"] { width: 100px; } #work-list .work { padding: .75rem 1.25rem; margin: .25rem 0; border: 1px solid #eee; border-left: .25rem solid #eee; border-radius: .25rem; } #work-list .work-memo { margin-top: .5rem; } #work-list .work-meta { display: flex; justify-content: space-between; } #work-list .work-score { font-size: 200%; color: #FF7851; } .bd-callout-warning { border-left-color: #f0ad4e; } .bd-callout-info { border-left-color: #5bc0de; } .bd-callout-danger { border-left-color: #FF7851; } #work-detail .score { margin: 5pt; font-size: 20pt; color: red; border-bottom: 3px double red; text-align: right; } #work-list .score { color: red; border-bottom: 3px double red; }

接著新增導覽列範本 templates/navbar.html

<nav class="navbar navbar-expand-md navbar-dark bg-primary"> <div class="container"> <a class="navbar-brand" href="/">線上教室</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarMenu" aria-controls="navbarMenu" aria-expanded="false" aria-label="Toggle navigation"> <i class="navbar-toggler-icon"></i> </button> <div class="collapse navbar-collapse" id="navbarMenu"> <ul class="navbar-nav"> {% if user.is_authenticated %} <li class="nav-item dropdown"> <a href="#" class="nav-link dropdown-toggle" id="user-dropdown" data-toggle="dropdown"> <i class="fas fa-user"></i> {{ user.username }} </a> <div class="dropdown-menu" aria-labelledby="user-dropdown"> <a href="{% url 'password_change' %}" class="dropdown-item"> <i class="fas fa-lock"></i> 變更密碼 </a> <a href="{% url 'logout' %}" class="dropdown-item"> <i class="fas fa-sign-out-alt"></i> 登出 </a> </div> </li> {% else %} <li class="nav-item"> <a href="{% url 'login' %}" class="nav-link"> <i class="fas fa-sign-in-alt"></i> 登入 </a> </li> {% endif %} </ul> </div> </div> </nav>

首頁範本

建立首頁範本 templates/home.html

{% extends "base.html" %} {% block content %} <p>歡迎光臨線上教室!</p> {% endblock %}

先放個簡單的訊息就好。

登入頁面範本

建立 templates/registration 資料夾,接著新增登入範本檔 templates/registration/login.html

{% extends "base.html" %} {% block content %} {% if form.errors %} <div class="alert alert-danger"> {{ form.errors }} </div> {% endif %} <form action="" method="POST"> {% csrf_token %} <div id="login-form" class="form-inline"> <input type="text" name="username" placeholder="請輸入您的帳號" autofocus> <input type="password" name="password" placeholder="請輸入您的密碼"> <input type="submit" class="btn btn-primary" value="登入"> </div> </form> {% endblock %} {% block custom_scripts %} <script> add_css_class('#login-form input[placeholder]', 'form-control', 'mr-2'); </script> {% endblock %}

add_css_class() 來新增頁面上的 HTML 元素所套用的 CSS 類別,藉以調整其外觀。

登出頁面範本

建立登出範本 templates/registration/logged_out.html

{% extends "base.html" %} {% block content %} <p class="alert alert-info">您已登出!!</p> <a href="{% url 'login' %}" class="btn btn-primary">請按此處重新登入</a> {% endblock %}

變更個人密碼頁面範本

建立變更密碼範本 templates/registration/password_change_form.html

{% extends "base.html" %} {% block content %} <form action="" method="post"> {% csrf_token %} <div class="card"> <div class="card-body"> <table class="table table"> {{ form.as_table }} </table> </div> <div class="card-footer"> <input type="submit" value="送出" class="btn btn-primary"> </div> </div> </form> {% endblock %} {% block custom_scripts %} <script> add_css_class('form table input', 'form-control'); add_css_class('.errorlist', 'alert', 'alert-danger'); add_css_class('.helptext ul', 'alert', 'alert-light'); </script> {% endblock %}

:bulb: 範本裡的表單欄位自行撰寫好,還是自動展開得好?

{{ form.as_table }}{{ form.as_p }}{{ form.as_ul }} 在範本檔中自動展開表單欄位的話,在表單發生驗證失敗的狀況時,會自動發生錯誤的欄位加上錯誤訊息。如果沒有特殊需要的話,建議盡量以自動展開表單的方式為優先考量,避免自行手刻表單欄位的 HTML 原始碼,這樣會比較省事。

個人密碼變更完成頁面範本

變更密碼還需要變更完成的頁面範本,請建立範本檔 templates/registration/password_change_done.html

{% extends "base.html" %} {% block content %} <p class="alert alert-info">密碼已成功變更!</p> {% endblock %}

使用者帳號管理

接下來要實作帳號列表、註冊新帳號、修改使用者帳號資料、修改使用者密碼,以及切換使用者的教師角色等帳號管理相關功能。

新增路徑規則

修改應用程式 user 的路徑規則檔 user/urls.py

urlpatterns = [ path('', include('django.contrib.auth.urls')), path('', UserDashboard.as_view(), name='user_dashboard'), path('list/', UserList.as_view(), name='user_list'), path('register/', UserRegister.as_view(), name='user_register'), path('<int:pk>/', UserView.as_view(), name='user_view'), path('<int:pk>/edit/', UserEdit.as_view(), name='user_edit'), path('<int:pk>/password/', UserPasswordUpdate.as_view(), name='user_password'), path('<int:uid>/teacher/', UserTeacherToggle.as_view(), name='user_teacher_toggle'), ]

新增處理視圖

開啟 user/views.py,來實作各功能的處理視圖:

管理員才允許操作的混成類別

接下來實作的大部份都是管理員才允許操作的功能,為了方便程式撰寫,先定義一個協助檢查是否為管理員的混成類別:

from django.views.generic import * from django.urls import reverse_lazy from django.contrib.auth.mixins import * from django.contrib.auth.models import User, Group from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q, Subquery from django import forms from .models import * # 限定管理員才允許操作的混成類別 class SuperuserRequiredMixin(AccessMixin): def dispatch(self, request, *args, **kwargs): if not request.user.is_superuser: return self.handle_no_permission() return super().dispatch(request, *args, **kwargs)
- ++第 13 行++,`request.user` 代表著目前正進行操作的使用者,它的 `is_superuser` 屬性的意思是該使用者是否為管理員。
使用者註冊
# 使用者註冊 class UserRegister(CreateView): extra_context = {'title': '使用者註冊'} model = User fields = ['username', 'first_name', 'last_name', 'email', 'password'] template_name = 'form.html' success_url = reverse_lazy('home') # 註冊完成返回首頁 # 取得要顯示的表單內容 def get_form(self): form = super().get_form() form.fields['first_name'].label = '真實姓名' form.fields['first_name'].required = True form.fields['last_name'].label = '學校名稱' form.fields['last_name'].required = True form.fields['password2'] = forms.CharField(label='密碼驗證', max_length=255) return form # 表單驗證 def form_valid(self, form): user = form.save(commit=False) pw1 = form.cleaned_data['password'] pw2 = form.cleaned_data['password2'] if pw1 != pw2: form.add_error('password2', '密碼與驗證密碼不相符') return super().form_invalid(form) user.set_password(pw1) return super().form_valid(form)

使用者註冊這個功能所做的,實際上就是在 User 這個資料模型新增一筆紀錄,所以可以處理視圖可以繼承 CreateView 類別來實作:

使用者列表
# 使用者列表 class UserList(SuperuserRequiredMixin, ListView): extra_context = {'title': '使用者列表'} model = User ordering = ['username'] paginate_by = 20 template_name = 'user/user_list.html' def get_queryset(self): return super().get_queryset().prefetch_related('groups')
檢視使用者資料
class UserView(SuperuserRequiredMixin, DetailView): model = User template_name = 'user/user_detail.html' context_object_name = 'tuser'

準備工作都完成了,然後就可以撰寫頁面範本了。請先建立 templates/user 資料夾,然後新增帳號列表範本 templates/user/user_list.html

{% extends "base.html" %} {% load user_tag %} {% block content %} <div class="card"> <div id="user-list" class="card-body"> <table class="table table-sm"> <thead> <tr> <th>帳號</th> <th>姓名</th> <th>學校</th> <th>教師</th> <th>操作</th> </tr> </thead> <tbody> {% for user in user_list %} <tr> <td> <a href="{% url 'user_view' user.id %}">{{ user.username }}</a> </td> <td>{{ user.first_name }}</td> <td>{{ user.last_name }}</td> <td>{% if user|is_teacher %}<i class="fas fa-check"></i>{% endif %}</td> <td> <a href="{% url 'user_edit' user.id %}" class="btn btn-sm btn-primary">修改</a> <a href="{% url 'user_password' user.id %}" class="btn btn-sm btn-secondary">密碼</a> <a href="{% url 'user_teacher_toggle' user.id %}?next={{ request.path|urlencode }}" class="btn btn-sm btn-light">切換教師</a> </td> </tr> {% endfor %} </tbody> </table> </div> <div class="card-footer"> {% include "pagination.html" %} </div> </div> {% endblock %}

列表的頁面常會用到分頁的功能,所以還得新增分頁控制項的範本檔 templates/pagination.html,內容可以直接複製前面幾個案例的內容:

{% if is_paginated %} {% with btn_class="btn btn-sm btn-primary" %} <div class="mt-1"> {% if page_obj.has_previous %} <a href="?page={{ page_obj.previous_page_number }}" class="{{ btn_class }}"> <i class="fas fa-chevron-circle-left"></i>上一頁 </a> {% endif %} {% for page in paginator.page_range %} {% if page == page_obj.number %} <button class="{{ btn_class }}" disabled>{{ page }}</button> {% else %} <a href="?page={{ page }}" class="{{ btn_class }}">{{ page }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="?page={{ page_obj.next_page_number }}" class="{{ btn_class }}"> 下一頁<i class="fas fa-chevron-circle-right"></i> </a> {% endif %} </div> {% endwith %} {% endif %}
查看使用者

新增查看使用者頁面範本 templates/user/user_detail.html,內容如下:

{% extends "base.html" %} {% block content %} <div class="card"> <div class="card-header"> <h5 class="card-title">{{ tuser.username }}</h5> <h6 class="card-subtitle text-muted">{{ tuser.first_name }}</h6> </div> <div class="card-body"> </div> </div> {% endblock %}

共用的表單範本

新增、修改資料時,如果沒有特別需要的話,通常表單頁面範本的內容都差不多,所以可以共用同一個表單範本。

新增 templates/form.html

{% extends "base.html" %} {% block content %} <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <div class="card"> <div class="card-body"> <table class="table table"> {{ form.as_table }} </table> </div> <div class="card-footer"> <input type="submit" value="送出" class="btn btn-primary"> </div> </div> </form> {% endblock %} {% block custom_scripts %} <script> add_css_class('form table input', 'form-control'); add_css_class('.errorlist', 'alert', 'alert-danger'); add_css_class('.helptext ul', 'alert', 'alert-light'); </script> {% endblock %}


修改登入頁面範本

修改登入頁面範本 templates/registration/login.html,新增第 15, 23 行以加入註冊按鈕:

{% extends "base.html" %} {% block content %} {% if form.errors %} <div class="alert alert-danger"> {{ form.errors }} </div> {% endif %} <form action="" method="POST"> {% csrf_token %} <div id="login-form" class="form-inline"> <input type="text" name="username" placeholder="請輸入您的帳號" autofocus> <input type="password" name="password" placeholder="請輸入您的密碼"> <input type="submit" class="btn btn-primary" value="登入"> <a href="{% url 'user_register' %}" class="btn btn-light">註冊</a> </div> </form> {% endblock %} {% block custom_scripts %} <script> add_css_class('#login-form input[placeholder]', 'form-control', 'mr-2'); add_css_class('#login-form .btn', 'mr-2'); </script> {% endblock %}