課程管理
接下來實作線上教室的核心:課程管理。師生、同學彼此之間透過相同的課程產生的關係,教師在課程下對學生指派作業,同一門課的師生間也可以相互傳訊息。把維繫各項資料模型的課程模型的框架先處理好,之後要實作其他功能就有建立連繫的依據了。
建立應用程式
如果專案正在執行中,請先按鍵盤組合鍵 Ctrl + C 中斷程式執行,再以專案管理腳本新增應用程式 course
:
python manage.py startapp course
修改專案設定檔 eclassroom/settings.py
,新增第 41 行將應用程式加入專案:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'user',
'course',
]
建立資料資料模型
開啟檔案 course/models.py
,修改為以下程式碼:
from django.db.models import *
from django.contrib.auth.models import User
class Course(Model):
name = CharField('課程名稱', max_length=32)
enroll_password = CharField('選課密碼', max_length=32)
teacher = ForeignKey(User, CASCADE)
def __str__(self):
return "{}#{}({})".format(
self.id,
self.name,
self.teacher.first_name
)
class Enroll(Model):
stu = ForeignKey(User, CASCADE)
course = ForeignKey(Course, CASCADE)
seat = IntegerField('座號', default=0)
def __str__(self):
return "{}:{}-{}-{}".format(
self.id,
self.course.name,
self.seat,
self.stu.first_name
)
這裡建立了兩個資料模型,分別是課程模型 Course
與選課模型 Enroll
。在本專案中,學生需自行選課,在選課時需輸入教師指定的選課密碼。選課模型 Enroll
需記錄哪個學生選了哪一門課程,所以模型內設計了 2 個 ForeignKey
型態的欄位來分別參考使用者帳號 User
以及課程 Course
的紀錄。
執行以下指令將變更套用到資料庫:
python manage.py makemigrations
python manage.py migrate
然後重新開啟網站服務:
python manage.py runserver 0.0.0.0:80
建立路徑規則
目前規劃在課程管理提供以下幾項功能:
路徑 |
功能 |
操作人員 |
course/ |
課程列表 |
所有人 |
course/create/ |
建立新課程 |
教師 |
course/課程編號/ |
查看課程內容 |
開課教師,選修學生,未選修學生 |
course/課程編號/edit/ |
編輯課程 |
開課教師 |
course/課程編號/enroll/ |
選修課程 |
未選修學生 |
course/課程編號/users/ |
選修學生列表 |
開課教師,已選修學生 |
course/課程編號/seat/ |
學生修改座號 |
已選修學生 |
操作人員說明:
- 教師:被加入
teacher
群組的使用者
- 開課教師:開設該課程的使用者
- 選修學生:選修該課程的使用者
- 未選修學生:未選修該課程的使用者
- 所有人:以上所有人員以及未登入的使用者
修改專案路徑規則檔 eclassroom/urls.py
,新增第 11 行來引用應用程式 course
所定義的路徑規則:
urlpatterns = [
path('admin/', admin.site.urls),
path('',
TemplateView.as_view(
template_name='home.html',
extra_context={'title': '歡迎光臨'}
),
name='home'
),
path('user/', include('user.urls')),
path('course/', include('course.urls')),
]
建立應用程式 course
的路徑規則檔 course/urls.py
,內容如下:
from django.urls import path, reverse_lazy
from .views import *
from .models import *
urlpatterns = [
path('', CourseList.as_view(), name='course_list'),
path('create/', CourseCreate.as_view(), name='course_create'),
path('<int:cid>/', CourseView.as_view(), name='course_view'),
path('<int:cid>/edit/', CourseEdit.as_view(), name='course_edit'),
path('<int:cid>/enroll/', CourseEnroll.as_view(), name='course_enroll'),
path('<int:cid>/users/', CourseUsers.as_view(), name='course_users'),
path('<int:cid>/seat/', CourseEnrollSeat.as_view(), name='course_seat'),
]
定義處理視圖
決定了路徑與功能的對應之後,接下來實作處理各項功能的視圖類別。
編輯應用程式 course
的視圖定義檔 course/views.py
內容如下,因檔案內容有點長,以下分段展示與說明。
定義協助檢查權限的混成類別
from django.urls import reverse_lazy, reverse
from django.shortcuts import get_object_or_404
from django.views.generic import *
from django.contrib.auth.mixins import *
from django.contrib import messages
from django import forms
from .models import *
class TeacherReqiuredMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser and \
not request.user.groups.filter(name='teacher').exists():
return super().handle_no_permission()
return super().dispatch(request, *args, **kwargs)
一開始先定義一個混成類別 TeacherRequiredMixin
用來協助檢查使用者是否具有教師身分(屬於 teacher
群組),要特別說明的是:
COURSE_PERM_GUEST = 0
COURSE_PERM_STUDENT = 1
COURSE_PERM_TEACHER = 2
COURSE_PERM_MEMBER = 3
class CourseAccessMixin(AccessMixin):
permission = None
extra_context = {}
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return super().handle_no_permission()
self.course = get_object_or_404(Course, id=kwargs['cid'])
user_perm = COURSE_PERM_GUEST
if self.course.teacher == request.user:
user_perm = COURSE_PERM_TEACHER
elif self.course.enroll_set.filter(stu=request.user).exists():
user_perm = COURSE_PERM_STUDENT
if not request.user.is_superuser and self.permission is not None:
is_accessible = False
if self.permission == COURSE_PERM_GUEST and \
user_perm == COURSE_PERM_GUEST:
is_accessible = True
elif (self.permission & user_perm) != 0:
is_accessible = True
if not is_accessible:
return super().handle_no_permission()
self.extra_context.update({'course': self.course})
return super().dispatch(request, *args, **kwargs)
接下來定義了另一個混成類別 CourseAccessMixin
來協助檢查使用者在課程中的身分,並同時取得該課程的紀錄:
課程列表與建立課程
class CourseList(ListView):
extra_context = {'title': '課程列表'}
model = Course
ordering = ['-id']
paginate_by = 20
class CourseCreate(TeacherReqiuredMixin, CreateView):
extra_context = {'title': '建立課程'}
model = Course
fields = ['name', 'enroll_password']
template_name = 'form.html'
success_url = reverse_lazy('course_list')
def form_valid(self, form):
form.instance.teacher = self.request.user
return super().form_valid(form)
這一段定義了 2 個處理視圖的類別,要特別說明的地方如下:
檢視課程與修改課程
class CourseView(CourseAccessMixin, DetailView):
extra_context = {'title': '檢視課程'}
model = Course
pk_url_kwarg = 'cid'
class CourseEdit(CourseAccessMixin, UpdateView):
permission = COURSE_PERM_TEACHER
extra_context = {'title': '修改課程'}
pk_url_kwarg = 'cid'
model = Course
fields = ['name', 'enroll_password']
template_name = 'form.html'
def get_success_url(self):
return reverse('course_view', args=[self.object.id])
接著定義的 CourseView
與 CourseEdit
兩個視圖類別來處理檢視課程與修改課程的請求。
學生選修課程
class CourseEnroll(CourseAccessMixin, CreateView):
permission = COURSE_PERM_GUEST
model = Enroll
fields = ['seat']
template_name = 'form.html'
def get_success_url(self):
return reverse('course_view', args=[self.course.id])
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['title'] = '選修課程:' + self.course.name
return ctx
def get_form(self):
form = super().get_form()
form.fields['password'] = forms.CharField(label='選課密碼', max_length=32)
return form
def form_valid(self, form):
if form.cleaned_data['password'] != self.course.enroll_password:
form.add_error('password', '選課密碼錯誤')
return super().form_invalid(form)
form.instance.course = self.course
form.instance.stu = self.request.user
return super().form_valid(form)
CourseEnroll
類別是用來處理學生選修課程的請求:
修課名單列表
class CourseUsers(CourseAccessMixin, ListView):
permission = COURSE_PERM_MEMBER
extra_context = {'title': '修課名單'}
template_name = 'course/user_list.html'
def get_queryset(self):
return self.course.enroll_set.select_related('stu').order_by('seat')
CourseUsers
類別負責顯示某課程內的選修學生列表:
學生變更座號
class CourseEnrollSeat(CourseAccessMixin, UpdateView):
permission = COURSE_PERM_STUDENT
extra_context = {'title': '變更座號'}
fields = ['seat']
template_name = 'form.html'
def get_success_url(self):
return reverse('course_view', args=[self.course.id])
def get_object(self):
return get_object_or_404(
Enroll,
course=self.course,
stu=self.request.user
)
頁面範本
導覽列
修改導覽列範本 templates/navbar.html
,插入第 9 - 13 行將課程列表的連結加入導覽選單:
<ul class="navbar-nav">
<li class="nav-item">
<a href="{% url 'course_list' %}" class="nav-link">
<i class="fas fa-layer-group"></i> 課程列表
</a>
</li>
{% if user.is_authenticated %}
個人選修課程
修改查看使用者範本 templates/user/user_detail.html
,插入第 10 - 19 行,列出使用者選修的課程清單。
<div class="card-body">
<div class="card-title">選修的課程</div>
<ul>
{% for enroll in tuser.enroll_set.all %}
<li>
<a href="{% url 'course_view' enroll.course.id %}">
{{ enroll.course.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
課程列表
請先建立 templates/course
資料夾,再新增課程列表範本 templates/course/course_list.html
:
{% extends "base.html" %}
{% load user_tags %}
{% block content %}
{% if user|is_teacher %}
<div class="mb-2">
<a href="{% url 'course_create' %}" class="btn btn-sm btn-primary">建立課程</a>
</div>
{% endif %}
<div id="course_list">
{% for course in course_list %}
<div class="list-group">
<div class="list-group-item d-flex">
{% if user.is_authenticated %}
<a href="{% url 'course_view' course.id %}">{{ course.name }}</a>
{% else %}
{{ course.name }}
{% endif %}
<small class="ml-auto">{{ course.teacher.first_name }} 老師</small>
</div>
</div>
{% endfor %}
</div>
{% include "pagination.html" %}
{% endblock %}
檢視課程
在檢視課程的頁面會根據使用者在課程的身分顯示不同的內容,若為開課教師,可以很簡單地透過 course.teacher
的值來測定,但要檢查是否為修課學生,則無法直接在頁面範本檢查。在這邊也透過自訂頁面範本的過濾器來傳回課程的選修名單中是否包含指定使用者,以方便在頁面範本中進行相關檢查。
直接將新的自定過濾器定義在應用程式 User
裡的 templatetag
模組就好,不需要重建一個新的模組。
請修改檔案 user/templatetags/user_tags.py
,新增第 9 - 16 行:
@register.filter(name='has_student')
def has_student(course, user):
return course.enroll_set.filter(stu=user).exists()
@register.filter(name='has_member')
def has_member(course, user):
return course.teacher == user or \
course.enroll_set.filter(stu=user).exists()
這邊新增了 2 個自訂過濾器的定義:
has_student
:課程的選修名單中是否包含指定的使用者。
has_member
:課程的成員(開課教師或選修名單)是否包含指定的使用者。
建立檢視課程範本 templates/course/course_detail.html
,內容如下:
{% extends "base.html" %}
{% load user_tags %}
{% block content %}
<div id="course_view" class="card">
{% with b1="btn btn-sm btn-primary" b2="btn btn-sm btn-secondary" %}
<div class="card-header d-flex">
<div>
<a href="{% url 'course_view' course.id %}">{{ course.name }}</a>
<small>{{ course.teacher.first_name }} 老師</small>
</div>
{% if user.is_superuser or course.teacher == user %}
<div class="ml-auto">
<span class="badge badge-light">
選課密碼: {{ course.enroll_password }}
</span>
<a href="{% url 'course_edit' course.id %}" class="{{ b1 }}">
<i class="fas fa-edit"></i> 編輯
</a>
</div >
{% endif %}
</div>
<div class="card-body">
{% block course_detail_body %}
<div id="student_op" class="btn-group">
{% if not course|has_member:user and not user.is_superuser %}
<a href="{% url 'course_enroll' course.id %}" class="{{ b1 }}">
<i class="fas fa-id-badge"></i> 選修
</a>
{% else %}
<a href="{% url 'course_users' course.id %}" class="{{ b1 }}">
<i class="fas fa-users"></i> 修課名單
</a>
{% if course|has_student:user %}
<a href="{% url 'course_seat' course.id %}" class="{{ b1 }}">
<i class="fas fa-chair"></i> 更改座號
</a>
{% endif %}
{% endif %}
</div>
{% endblock %}
</div>
{% endwith %}
</div>
{% endblock %}
修課名單
建立課程修課名單範本檔 templates/course/user_list.html
:
{% extends "course/course_detail.html" %}
{% block course_detail_body %}
<div id="course_users">
<table class="table table-sm table-hover">
<thead>
<tr>
<th>學校</th>
<th>帳號</th>
<th>座號</th>
<th>姓名</th>
<th>最近登入時間</th>
</tr>
</thead>
<tbody>
{% for enroll in enroll_list %}
<tr>
<td>{{ enroll.stu.last_name }}</td>
<td>{{ enroll.stu.username }}</td>
<td>{{ enroll.seat }}</td>
<td>{{ enroll.stu.first_name }}</td>
<td>{{ enroll.stu.last_login }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
新增/修改課程、選修、更改座號
不需建立範本檔案,直接共用表單範本 form.html
即可。
課程管理
接下來實作線上教室的核心:課程管理。師生、同學彼此之間透過相同的課程產生的關係,教師在課程下對學生指派作業,同一門課的師生間也可以相互傳訊息。把維繫各項資料模型的課程模型的框架先處理好,之後要實作其他功能就有建立連繫的依據了。
建立應用程式
如果專案正在執行中,請先按鍵盤組合鍵 Ctrl + C 中斷程式執行,再以專案管理腳本新增應用程式
course
:修改專案設定檔
eclassroom/settings.py
,新增第 41 行將應用程式加入專案:建立資料資料模型
開啟檔案
course/models.py
,修改為以下程式碼:這裡建立了兩個資料模型,分別是課程模型
Course
與選課模型Enroll
。在本專案中,學生需自行選課,在選課時需輸入教師指定的選課密碼。選課模型Enroll
需記錄哪個學生選了哪一門課程,所以模型內設計了 2 個ForeignKey
型態的欄位來分別參考使用者帳號User
以及課程Course
的紀錄。執行以下指令將變更套用到資料庫:
然後重新開啟網站服務:
建立路徑規則
目前規劃在課程管理提供以下幾項功能:
course/
course/create/
course/課程編號/
course/課程編號/edit/
course/課程編號/enroll/
course/課程編號/users/
course/課程編號/seat/
操作人員說明:
teacher
群組的使用者修改專案路徑規則檔
eclassroom/urls.py
,新增第 11 行來引用應用程式course
所定義的路徑規則:建立應用程式
course
的路徑規則檔course/urls.py
,內容如下:定義處理視圖
決定了路徑與功能的對應之後,接下來實作處理各項功能的視圖類別。
編輯應用程式
course
的視圖定義檔course/views.py
內容如下,因檔案內容有點長,以下分段展示與說明。定義協助檢查權限的混成類別
一開始先定義一個混成類別
TeacherRequiredMixin
用來協助檢查使用者是否具有教師身分(屬於teacher
群組),要特別說明的是:接下來定義了另一個混成類別
CourseAccessMixin
來協助檢查使用者在課程中的身分,並同時取得該課程的紀錄:課程列表與建立課程
這一段定義了 2 個處理視圖的類別,要特別說明的地方如下:
檢視課程與修改課程
接著定義的
CourseView
與CourseEdit
兩個視圖類別來處理檢視課程與修改課程的請求。學生選修課程
CourseEnroll
類別是用來處理學生選修課程的請求:修課名單列表
CourseUsers
類別負責顯示某課程內的選修學生列表:學生變更座號
頁面範本
導覽列
修改導覽列範本
templates/navbar.html
,插入第 9 - 13 行將課程列表的連結加入導覽選單:個人選修課程
修改查看使用者範本
templates/user/user_detail.html
,插入第 10 - 19 行,列出使用者選修的課程清單。課程列表
請先建立
templates/course
資料夾,再新增課程列表範本templates/course/course_list.html
:檢視課程
在檢視課程的頁面會根據使用者在課程的身分顯示不同的內容,若為開課教師,可以很簡單地透過
course.teacher
的值來測定,但要檢查是否為修課學生,則無法直接在頁面範本檢查。在這邊也透過自訂頁面範本的過濾器來傳回課程的選修名單中是否包含指定使用者,以方便在頁面範本中進行相關檢查。直接將新的自定過濾器定義在應用程式
User
裡的templatetag
模組就好,不需要重建一個新的模組。請修改檔案
user/templatetags/user_tags.py
,新增第 9 - 16 行:這邊新增了 2 個自訂過濾器的定義:
has_student
:課程的選修名單中是否包含指定的使用者。has_member
:課程的成員(開課教師或選修名單)是否包含指定的使用者。建立檢視課程範本
templates/course/course_detail.html
,內容如下:修課名單
建立課程修課名單範本檔
templates/course/user_list.html
:新增/修改課程、選修、更改座號
不需建立範本檔案,直接共用表單範本
form.html
即可。