Django 项目实践 —— 可重用注册登录系统 (2)

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 库

验证是否正确

信息错误时(提示相应的提示)

 

 


邮箱注册


 

发送邮件功能测试


配置邮件信息

#  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 库

 

至此一个可重用注册登录系统完成......

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值