Django 项目实践 —— 可重用注册登录系统 (2)
目录
图片验证
为了防止机器人频繁登录网站或者破坏分子恶意登录,很多用户登录和注册系统都提供了图形验证码功 能。 在Django中实现图片验证码功能非常简单,有现成的第三方库可以使用,我们不必自己开发(不必重复 造轮子)。这个库叫做django-simple-captcha。 具体安装教程: 戳我
Django表单
我们前面都是手工在HTML文件中编写表单form元素,然后在views.py的视图函数中接收表单中的用户 数据,再编写验证代码进行验证,最后使用ORM进行数据库的增删改查。这样费时费力,整个过程比较 复杂,而且有可能写得不太恰当,数据验证也比较麻烦。设想一下,如果我们的表单拥有几十上百个数 据字段,有不同的数据特点,如果也使用手工的方式,其效率和正确性都将无法得到保障。有鉴于此, Django在内部集成了一个表单功能,以面向对象的方式,直接使用Python代码生成HTML表单代码,专 门帮助我们快速处理表单相关的内容。 Django的表单给我们提供了下面三个主要功能:
- 准备和重构数据用于页面渲染;
- 为数据创建HTML表单元素;
- 接收和处理用户从表单发送过来的数据。
编写Django的form表单,非常类似我们在模型系统里编写一个模型。在模型中,一个字段代表数据表 的一列,而form表单中的一个字段代表<form>中的一个 <input> 元素
创建表单模型
# /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 库
验证是否正确
信息错误时(提示相应的提示)
邮箱注册
发送邮件功能测试
配置邮件信息
# 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/" 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>点击验证</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 库
至此一个可重用注册登录系统完成......