Django 小實例S1 簡易學生選課管理系統 第5節——實現注冊功能
點擊查看教程總目錄
作者自我介紹:b站小UP主,時常直播編程+紅警三,python1對1輔導老師。
本文涉及到的新的額外知識點:
Class-based views
沒有這部分基礎的讀者,建議一邊閱讀本文一邊查閱相關知識
這里推薦我的專欄:Django自學筆記 相關章節內容
1 添加注冊頁面模板(template)
在templates/user
下新建register.html
如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
Register
</title>
</head>
<body>
<div class="register-container">
<div class="register-title">注冊</div>
<form method="post" class="form">
{% csrf_token %}
{{form.as_p}}
<p><input type="submit" value="注冊" class="submit-button"/></p>
</form>
</div>
</body>
2 添加對應的視圖(view)方法
這里先思考,要完成注冊功能,視圖方法應該實現怎樣的功能。
注冊一個新的學生賬號,就是在student數據庫表中添加一個新的記錄。
對應到Django項目,則是通過新建一個學生模型(model)實例。 (教師同理)
Django為 model 類實現了一些功能強大的視圖類,使你能夠快速完成一個為model進行增刪查改等等操作的視圖類,同時使用視圖類的特定方法生成視圖。
這樣的視圖類一般稱為CBV(Class-based views)
在這里我們直接使用為model類進行新增實例的CreateView
。
方便我們直接根據指定的字段生成前端表單,該生成的表單自帶檢查字段格式的功能,同時也方便我們在后端接受表單請求后按照表單數據生成對應的實例。
如果不使用CBV,上面介紹的繁瑣的過程都需要我們手動一步一步實現,這是很痛苦麻煩低效的。
cbv本身只能夠指定根據哪些字段生成對應的前端表單。
但我們這里需要實現一個略微復雜一點的功能: 注冊頁面除了需要填寫密碼還需要確認密碼(即再填寫一遍密碼),同時提交時,需要先檢查這兩個是否一致。
要完成這個功能,我們需要實現一個定制化的表單
實現定制化的表單
在user/forms.py
文件中,添加代碼
class StuRegisterForm(forms.ModelForm):
confirm_password = forms.CharField(widget=forms.PasswordInput(), label="確認密碼")
class Meta:
model = Student
fields = ('grade',
'name',
'password',
'confirm_password',
'gender',
'birthday',
'email',
'info')
def clean(self):
cleaned_data = super(StuRegisterForm, self).clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if confirm_password != password:
self.add_error('confirm_password', 'Password does not match.')
class TeaRegisterForm(forms.ModelForm):
confirm_password = forms.CharField(widget=forms.PasswordInput(), label="確認密碼")
class Meta:
model = Teacher
fields = ('name',
'password',
'confirm_password',
'gender',
'birthday',
'email',
'info')
def clean(self):
cleaned_data = super(TeaRegisterForm, self).clean()
password = cleaned_data.get('password')
confirm_password = cleaned_data.get('confirm_password')
if confirm_password != password:
self.add_error('confirm_password', 'Password does not match.')
然后為學生和老師這兩種model都添加下對應的視圖類
實現CBV
新建user/cbvs.py
如下
from django.shortcuts import reverse, redirect
from django.views.generic import CreateView
from user.forms import StuRegisterForm, TeaRegisterForm
from user.models import Student, Teacher
import random
class CreateStudentView(CreateView):
model = Student
form_class = StuRegisterForm
# fields = "__all__"
template_name = "user/register.html"
success_url = "login"
def form_valid(self, form):
# 學生注冊時選定年級自動生成學號
grade = form.cleaned_data["grade"]
# order_by默認升序排列,number前的負號表示降序排列
student_set = Student.objects.filter(grade=grade).order_by("-number")
if student_set.count() > 0:
last_student = student_set[0]
new_number = str(int(last_student.number) + 1)
for i in range(6 - len(new_number)):
new_number = "0" + new_number
else:
new_number = "000001"
# Create, but don't save the new student instance.
new_student = form.save(commit=False)
# Modify the student
new_student.number = new_number
# Save the new instance.
new_student.save()
# Now, save the many-to-many data for the form.
form.save_m2m()
self.object = new_student
uid = grade + new_number
from_url = "register"
base_url = reverse(self.get_success_url(), kwargs={'kind': 'student'})
return redirect(base_url + '?uid=%s&from_url=%s' % (uid, from_url))
class CreateTeacherView(CreateView):
model = Teacher
form_class = TeaRegisterForm
template_name = "user/register.html"
success_url = "login"
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
# 老師注冊時隨機生成院系號, 院系號范圍為[0,300)
department_no = random.randint(0, 300)
# 把非三位數的院系號轉換為以0填充的三位字符串,如1轉換為'001'
department_no = '{:0>3}'.format(department_no)
teacher_set = Teacher.objects.filter(department_no=department_no).order_by("-number")
if teacher_set.count() > 0:
last_teacher = teacher_set[0]
new_number = int(last_teacher.number) + 1
new_number = '{:0>7}'.format(new_number)
else:
new_number = "0000001"
# Create, but don't save the new teacher instance.
new_teacher = form.save(commit=False)
# Modify the teacher
new_teacher.department_no = department_no
new_teacher.number = new_number
# Save the new instance.
new_teacher.save()
# Now, save the many-to-many data for the form.
form.save_m2m()
self.object = new_teacher
uid = department_no + new_number
from_url = "register"
base_url = reverse(self.get_success_url(), kwargs={'kind': 'teacher'})
return redirect(base_url + '?uid=%s&from_url=%s' % (uid, from_url))
這里介紹下上面的業務邏輯,在本項目S1的第三章第一節說過:
學生年級號為4位數字組成的字符串,年級下子學號為6位數字組成的字符串。
這兩個連接起來組成學生的唯一學號,該學號也為其登錄使用的賬號。
比如學生李大爽,年級號為"2020"
,子學號為"000001"
,其學號為"2020000001"
。
其中年級號是學生注冊時自己選擇的,子學號是注冊時按照其年級內注冊的先后順序生成的。
同一年級,第一個注冊的是"000001"
,第二個是"000002"
,依次類推。
這部分功能是在上面的CreateStudentView
中的form_valid
方法中實現的,該方法會返回一個HttpResponseRedirect
對象,對應的效果是學生注冊成功后,會返回到該重定向頁面所指向的網頁,這里對應的是注冊詳情頁。
一般來說轉么做一個注冊成功頁面會好些,不過這是個小項目,這里我就懶得去專門再搞個新頁面了。
由於注冊后的賬號是后台生成的,注冊者並不知道,所以重定向后需要將賬號展示給注冊者看。
這里采用的技術是通過url來傳參,傳到注冊詳情頁展示給注冊者看。
而對於老師
說明:老師院系號為3位數字組成的字符串,院內編號為7位數字組成的字符串。
這兩個連接起來組成老師的唯一教師號,該教師號也為其登錄使用的賬號。
比如老師牛有力,院系號為"266"
,院內編號為"0000001"
,其教師號為"2660000001"
。
其中院系號目前是隨機生成的(最早是想做由院系模塊,后來覺得工作量大就先放棄了,如果有人想做的話可以自行拓展)
院內編號是注冊時按照其院內注冊的先后順序生成的。
同一院系,第一個注冊的是"0000001"
,第二個是"0000002"
,依次類推。
這部分功能是在上面的CreateTeacherView
中的form_valid
方法中實現的,該方法會返回一個HttpResponseRedirect
對象,對應的效果是老師注冊成功后,會返回到該重定向頁面所指向的網頁,這里對應的是注冊詳情頁。
實現注冊視圖方法
一般來說,實現CBV后,使用CBV自帶的as_view()就可以生成需要的view方法了。
但是我們這里有些不同,由於有老師和學生兩種注冊,我想要用同一個視圖方法來處理這兩種請求。
那么視圖方法
- 需要接收個參數,該參數需要標明是老師注冊與學生注冊中的哪一種
- 內部用條件判斷語句,針對不同的種類,返回不同的視圖結果
邏輯理清,在user/views.py
中,繼續添加代碼如下
# 在開頭導入視圖類
from user.cbvs import CreateStudentView, CreateTeacherView
def register(request, kind):
func = None
if kind == "student":
func = CreateStudentView.as_view()
elif kind == "teacher":
func = CreateTeacherView.as_view()
if func:
return func(request)
else:
return HttpResponse(INVALID_KIND)
好了,到這里,注冊部分的視圖方法就算完成了
3 更新url
在user/urls.py
文件中,
給urlpatterns列表添加一行元素:
path('register/<slug:kind>', views.register, name="register")
再修改下templates/user/login_detail.html
,為注冊功能添加對應的進入鏈接
即修改第17行,修改前應該是
<a href="">注冊</a>
修改后為
<a href="{% url 'register' kind%}">注冊</a>
4 展示注冊后的賬號信息
最后,修改下登錄詳情頁部分代碼,使其能夠展示注冊得到的賬號信息,該信息是通過url來進行傳參的。
更新user/views.py
中的login
方法如下
def login(request, *args, **kwargs):
if not kwargs or kwargs.get("kind", "") not in ["student", "teacher"]:
return HttpResponse(INVALID_KIND)
kind = kwargs["kind"]
context = {'kind': kind}
if request.method == 'POST':
if kind == "teacher":
form = TeaLoginForm(data=request.POST)
else:
form = StuLoginForm(data=request.POST)
if form.is_valid():
uid = form.cleaned_data["uid"]
temp_res = "hello, %s" % uid
return HttpResponse(temp_res)
else:
context['form'] = form
elif request.method == 'GET':
if request.GET.get('uid'):
uid = request.GET.get('uid')
context['uid'] = uid
data = {"uid":uid, 'password': '12345678'}
if kind == "teacher":
form = TeaLoginForm(data)
else:
form = StuLoginForm(data)
else:
if kind == "teacher":
form = TeaLoginForm()
else:
form = StuLoginForm()
context['form'] = form
if request.GET.get('from_url'):
context['from_url'] = request.GET.get('from_url')
return render(request, 'user/login_detail.html', context)
再更新templates/user/login_detail.html
如下
{% extends "user/background.html" %}
{% block welcome_message %}
{% if from_url == "register" %}
<div class="welcome-message">注冊成功,你的{% if kind == "student" %}學號{% else %}賬號{% endif %}是 {{ uid }}</div>
{% else %}
<div class="welcome-message">歡迎</div>
{% endif %}
{% endblock %}
{% block login_container %}
{% if kind == "student" %}
<div class="login-kind-title">我是學生</div>
{% else %}
<div class="login-kind-title">我是老師</div>
{% endif %}
<div class = "form">
<form method="post">
{% csrf_token %}
{{form.as_p}}
<div class="submit-button">
<input type="submit" value="登錄"/>
<a href="{% url 'register' kind%}">注冊</a>
</div>
</form>
<div class="return-button"><a href="{% url 'login' %}">返回上一頁</a></div>
</div>
{% endblock %}
然后運行項目,瀏覽器打開http://127.0.0.1:8000/user/register/student
,效果如圖
按如下圖信息(其中密碼為zhang333)
注冊賬號后,效果如下
5 后續優化之添加返回按鈕
后續發現注冊頁面缺乏返回按鈕,這里補充上。
在templates/user/register.html
的表單下,即</form>
之后添加
<div class="return-button">
<a href="{% url 'login' kind %}">返回上一頁</a>
</div>
這時模板需要kind這個變量。我們需要在參數里面加上。
在user/cbvs.py
中,分別給CreateStudentView
類、CreateTeacherView
類重寫一下get_context_data
方法,如下
### 在CreateStudentView類中重寫:
def get_context_data(self, **kwargs):
context = super(CreateStudentView, self).get_context_data(**kwargs)
context["kind"] = "student"
return context
### 在CreateTeacherView類中重寫:
def get_context_data(self, **kwargs):
context = super(CreateTeacherView, self).get_context_data(**kwargs)
context["kind"] = "teacher"
return context