Django 項目實踐 —— 可重用注冊登入系統 (2)
目錄
Django 項目實踐 —— 可重用注冊登入系統 (2)
圖檔驗證
Django表單
郵箱注冊
發送郵件功能測試
基本的注冊功能實作
系統資料庫單
實作注冊視圖
注冊添加密碼加密功能
郵件注冊确認
建立模型
處理郵件确認請求
修改登入規則
圖檔驗證
為了防止機器人頻繁登入網站或者破壞分子惡意登入,很多使用者登入和注冊系統都提供了圖形驗證碼功 能。 在Django中實作圖檔驗證碼功能非常簡單,有現成的第三方庫可以使用,我們不必自己開發(不必重複 造輪子)。這個庫叫做django-simple-captcha。 具體安裝教程: 戳我
Django表單
我們前面都是手工在HTML檔案中編寫表單form元素,然後在views.py的視圖函數中接收表單中的使用者 資料,再編寫驗證代碼進行驗證,最後使用ORM進行資料庫的增删改查。這樣費時費力,整個過程比較 複雜,而且有可能寫得不太恰當,資料驗證也比較麻煩。設想一下,如果我們的表單擁有幾十上百個數 據字段,有不同的資料特點,如果也使用手工的方式,其效率和正确性都将無法得到保障。有鑒于此, Django在内部內建了一個表單功能,以面向對象的方式,直接使用Python代碼生成HTML表單代碼,專 門幫助我們快速處理表單相關的内容。 Django的表單給我們提供了下面三個主要功能:
- 準備和重構資料用于頁面渲染;
- 為資料建立HTML表單元素;
- 接收和處理使用者從表單發送過來的資料。
編寫Django的form表單,非常類似我們在模型系統裡編寫一個模型。在模型中,一個字段代表資料表 的一列,而form表單中的一個字段代表<form>中的一個 <input> 元素
戳我了解更多Django表單操作
建立表單模型
# /login/forms.py(建立的檔案)
from captcha.fields import CaptchaField
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(label='使用者名', required=True,min_length=4,max_length=128)
password = forms.CharField(label="密碼", required=True,min_length=4, max_length=10)
captcha = CaptchaField(label="驗證碼")
視圖邏輯優化
# login/views.py
def login(request):
# 請求方法為POST送出
if request.method == 'POST':
# 修改1: 執行個體化表單對象
login_form = LoginForm(request.POST)
# 修改2: 驗證表單資料的合法性
if login_form.is_valid():
# 修改3:擷取表單填寫的資料,資料清洗
username = login_form.cleaned_data.get('username')
password = login_form.cleaned_data.get('password')
user = SiteUser.objects.filter(name=username,password=password).first()
if user:
request.session['is_login'] = True
request.session['user_id'] = user.id
request.session['username'] = user.name
return redirect('/index/')
else:
message = "使用者名或者密碼錯誤"
# 修改4: locals()以字典方式傳回目前所有的變量
# eg:{'message':'xxxx', 'login_form':'xxx'}
return render(request, 'login/login.html', locals())
else:
message = "填寫的登入資訊不合法"
return render(request, 'login/login.html', locals())
# 請求方法是GET請求
login_form = LoginForm()
return render(request, 'login/login.html', locals())
Template頁面優化
# templates/login/login.html(部分修改)
<h3 style="text-align: center">使用者登入</h3>
# 修改1: 不同的報錯,提示不同的資訊
{% if login_form.captcha.errors %}
<div class="alert alert-warning" role="alert">
<strong>登入失敗!</strong> 驗證碼不正确
</div>
{% elif message %}
<div class="alert alert-warning" role="alert">
<strong>登入失敗!</strong> {{ message }}
</div>
{% endif %}
<div class="form-group">
# 修改2:
<label>{{ login_form.username.label }}</label>
<input type="text" class="form-control" name="username">
</div>
<div class="form-group">
# 修改3:
<label>{{ login_form.password.label }}</label>
<input type="password" class="form-control" name="password">
<small class="form-text text-muted">密碼必須是字母、數字或者特殊符号組成.</small>
</div>
# 修改4: 最重要的,添加驗證碼表單
<div class="form-group">
<label>{{ login_form.captcha.label }}</label>
{{ login_form.captcha }}
</div>
儲存到本地git 庫
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL0cmaNBTR650MRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1YzMzITN0IjM3AzMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
驗證是否正确
資訊錯誤時(提示相應的提示)
郵箱注冊
發送郵件功能測試
配置郵件資訊
# LoginRegister/settings.py
# 添加部分如下
# mail configure
EMAIL_HOST = 'smtp.163.com' # 'smtp.qq.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = '自己的郵箱位址'
EMAIL_HOST_PASSWORD = '開啟服務後,提示的授權碼'
EMAIL_USE_SSL = False
# 确認郵件失效的時間
CONFIRM_DAYS = 3
如何擷取授權碼
互動式環境中測試發送郵件是否成功?(安裝一個ipython,友善互動式的使用,可以補齊。pip install ipython )
Terminal輸入指令> python manage.py shell
In [1]: from django.core.mail import send_mail
In [2]: from loginRegister.settings import EMAIL_HOST_USER
In [3]: send_mail("測試郵件", "content", EMAIL_HOST_USER,['目的郵箱位址',])
驗證是否成功? 檢視是否受到郵件?
基本的注冊功能實作
系統資料庫單
實作注冊視圖
- 如果使用者已經登入,則不能注冊跳轉到首頁。
- 如果是GET請求,傳回使用者注冊的html頁面。
- 如果是POST請求, 先驗證送出的資料是否通過,清洗資料。 接下來判斷使用者名和郵箱是否已經被注冊, 将注冊的資訊存儲到資料庫,跳轉到登入界面。
- 額外功能: 為了資料的安全性注冊時,密碼存儲到資料庫不是明文存儲,而是先加密再存儲。
# login/views.py
def register(request):
# 如果使用者已經登入,則不能注冊跳轉到首頁。
if request.session.get('is_login', None):
return redirect('/index/')
# 如果是POST請求
if request.method == 'POST':
print(request.POST)
register_form = RegisterForm(request.POST)
message = "請檢查填寫的内容!"
# 先驗證送出的資料是否通過
if register_form.is_valid():
# 清洗資料
username = register_form.cleaned_data.get('username')
password1 = register_form.cleaned_data.get('password1')
password2 = register_form.cleaned_data.get('password2')
email = register_form.cleaned_data.get('email')
print(locals())
# 接下來判斷使用者名和郵箱是否已經被注冊
same_name_user = SiteUser.objects.filter(name=username)
print(same_name_user)
if same_name_user:
message = '使用者名已經存在'
return render(request, 'login/register.html', locals())
same_email_user = SiteUser.objects.filter(email=email)
if same_email_user:
message = '該郵箱已經被注冊了!'
return render(request, 'login/register.html', locals())
# 将注冊的資訊存儲到資料庫,跳轉到登入界面
new_user = SiteUser(name=username, password=password1, email=email)
new_user.save()
return redirect('/login/')
# 如果是GET請求,傳回使用者注冊的html頁面。
register_form = RegisterForm()
return render(request, 'login/register.html', locals())
Template模闆的更改
# templates/login/register.html
<h3 style="text-align: center">使用者注冊</h3>
{% if register_form.captcha.errors %}
<div class="alert alert-warning" role="alert">
<strong>注冊失敗!</strong> 驗證碼不正确
</div>
{% elif message %}
<div class="alert alert-warning" role="alert">
<strong>注冊失敗!</strong> {{ message }}
</div>
{% endif %}
<form action="/register/" method="post">
{% csrf_token %}
<div class="form-group">
<label>{{ register_form.username.label }}</label>
<input type="text" class="form-control" name="username">
</div>
<div class="form-group">
<label>{{ register_form.email.label }}</label>
<input type="email" class="form-control" name="email">
</div>
<div class="form-group">
<label>{{ register_form.password1.label }}</label>
<input type="password" class="form-control" name="password1">
<small class="form-text text-muted">密碼必須是字母、數字或者特殊符号組成。</small>
</div>
<div class="form-group">
<label>{{ register_form.password2.label }}</label>
<input type="password" class="form-control" name="password2">
<small class="form-text text-muted">密碼必須是字母、數字或者特殊符号組成.</small>
</div>
<div class="form-group">
<label>{{ register_form.captcha.label }}</label>
{{ register_form.captcha }}
</div>
<a href="/login/" target="_blank" rel="external nofollow" class="text-success">
<ins>使用者登入</ins>
</a>
<button type="submit" class="btn btn-primary float-right">注冊</button>
</form>
測試是否成功
資料庫是否寫入
儲存到本地git庫
注冊添加密碼加密功能
對于如何加密密碼,有很多不同的途徑,其安全程度也高低不等。這裡我們使用Python内置的hashlib 庫,使用哈希值的方式加密密碼,可能安全等級不夠高,但足夠簡單,友善使用。
在 login/utils.py(建立) 中編寫一個hash函數:
def hash_code(s, salt='mysite'):# 加點鹽
h = hashlib.sha256()
s += salt
h.update(s.encode()) # update方法隻接收bytes類型
return h.hexdigest()
在 login/views.py 中修改login和register視圖
def register(request):
# .....省略部分代碼
new_user = SiteUser(name=username, password=hash_code(password1),email=email)
# .....省略部分代碼
def login(request):
# .....省略部分代碼
user = SiteUser.objects.filter(name=username,password=hash_code(password)).first()
# .....省略部分代碼
測試,新注冊使用者,資料庫的密碼是否加密
儲存到本地 git庫
郵件注冊确認
很自然地,我們會想到如果能用郵件确認的方式對新注冊使用者進行審查,既安全又正式,也是目前很多 站點的做法。
建立模型
既然要區分通過和未通過郵件确認的使用者,那麼必須給使用者添加一個是否進行過郵件确認的屬性。 另外,我們要建立一張新表,用于儲存使用者的确認碼以及注冊送出的時間。
# /login/models.py
class SiteUser(models.Model):
# .......
has_confirmed = models.BooleanField(default=False, verbose_name="是否郵箱驗證")
class ConfirmString(models.Model):
code = models.CharField(max_length=256, verbose_name="确認碼")
user = models.OneToOneField('SiteUser', on_delete=models.CASCADE)
create_time = models.DateTimeField(auto_now_add=True, verbose_name="建立時間")
def __str__(self):
return self.user.name + ":" + self.code
class Meta:
ordering = ["-create_time"]
verbose_name = "确認碼"
verbose_name_plural = "确認碼"
資料庫模型更改,一定要生成遷移腳本和寫入資料庫。
python manage.py makemigrations
python manage.py migrate
順便修改一下admin.py檔案,友善我們在背景修改和觀察資料
# login/admin.py
admin.site.register(ConfirmString)
修改視圖
# login/views.py
def register(request):
# ................
code = make_confirm_string(new_user)
send_email(email, code)
message = '請前往郵箱進行确認!'
# ..................
make_confirm_string() 是建立确認碼對象的方法,代碼如下
# login/utils.py
import datetime
def make_confirm_string(user):
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
code = hash_code(user.name, now)
models.ConfirmString.objects.create(code=code, user=user,)
return code
注: 這裡确認碼是日期時間,需要将 settings.py 中 USE_TZ = True 改為 USE_TZ = False,否則可能會出錯
send_email(email, code) 方法接收兩個參數,分别是注冊的郵箱和前面生成的哈希值,代碼如下:
def send_email(email, code):
print('send mail.........')
subject = '注冊确認郵件'
text_content = '''感謝注冊,這裡是登入注冊系統網站!\
如果你看到這條消息,說明你的郵箱伺服器不提供HTML連結功能,請聯系管理者!'''
html_content = '''
<p>感謝注冊<a href="http://{}/confirm/?code={}" target="_blank" rel="external nofollow" target=blank>點選驗證</a>,\
這裡是登入注冊系統網站!</p>
<p>請點選站點連結完成注冊确認!</p>
<p>此連結有效期為{}天!</p>
'''.format('127.0.0.1:9999', code, settings.CONFIRM_DAYS)
send_mail(subject, text_content,settings.EMAIL_HOST_USER, [email, ], html_message=html_content)
最後的有效期天數為設定在settings中的 CONFIRM_DAYS 。下面是郵件相關的settings配置:
LoginRegister/settings.py
# 注冊有效期天數
CONFIRM_DAYS = 3
測試:注冊一個使用者,判斷是否能收到确認郵件。
儲存到本地 git 庫
處理郵件确認請求
在login子應用的 urls.py 中添加一條url:
path('confirm/', views.user_confirm,name='confirm'),
其次,在 login/views.py 中添加一個 user_confirm 視圖。
- 擷取确認碼資訊
- 資料庫中是否有該确認碼,如果沒有, 傳回說是無效的請求
- 資料庫中是否有該确認碼,如果有, 判斷是否過期? 如果過期,删除使用者資訊,否則更新使用者資訊。
# login/views.py
# 郵件驗證
def user_confirm(request):
code = request.GET.get('code', None)
message = ''
try:
confirm = ConfirmString.objects.get(code=code)
except:
message = '無效的确認請求!'
return render(request, 'login/confirm.html', locals())
create_time = confirm.create_time
now = datetime.now()
print(now, create_time, create_time + timedelta(settings.CONFIRM_DAYS))
if now > create_time + timedelta(settings.CONFIRM_DAYS):
confirm.user.delete()
message = '您的郵件已經過期!請重新注冊!'
else:
confirm.user.has_confirmed = True
confirm.user.save()
confirm.delete()
message = '感謝确認,請使用賬戶登入!'
return render(request, 'login/confirm.html', locals())
需要一個 confirm.html 頁面,我們将它建立在 /login/templates/login/ 下面: 頁面中通過JS代碼,設定2秒後自動跳轉到登入頁面,可根據自己的需要去除或者美化。
# login/templates/login/confirm.html
<h1 style="margin-left: 100px;">{{ message }}</h1>
<script>
window.setTimeout("window.location='/login/'",2000);
</script>
修改登入規則
既然未進行郵件确認的使用者不能登入,那麼我們就必須修改登入規則,如下所示:
def login(request):
......
if user:
# 修改的部分
# 使用者的code (确認碼)是否存在,has_confirmed 預設 False
if not user.has_confirmed:
message = '該使用者還未經過郵件确認!'
return render(request, 'login/login.html', locals())
......
測試,通過郵箱驗證注冊一個新使用者
未郵箱驗證的賬戶登入,會提示,使用者為經過郵箱驗證
在郵箱裡進行驗證
驗證成功後再次登入
添加到本地 git 庫
至此一個可重用注冊登入系統完成......