Django学习笔记(9)—— 开发用户注册与登录系统

一,项目题目: 开发用户注册与登录系统

  该项目主要练习使用Django开发一个用户注册与登录的系统,通过这个项目然后巩固自己这段时间所学习的Django知识。

  在Django学习笔记(16)——扩展Django自带User模型,实现用户注册与登录,这篇博文中,我完成了使用Django自带的Auth模型下的User模型而重新建立了自己的用户模型。有兴趣的可以看一下。

二,项目需求:

开发一个简单的用户登录与注册系统
 
要求:
    有用户注册页面
    有用户登录页面
    有用户登录成功的页面
    有注册和登录的验证码提示

  

三,编码规范需求:

编码规范需求:15%
1. 代码规范遵守pep8 (https://python.org/dev/peps/pep-0008/)
2. 函数有相应的注释
3. 程序有文档说明文件(README.md参考:https://github.com/csrftoken/vueDrfDemo)
4. 程序的说明文档必须包含的内容:程序的开发环境(django版本)、程序的实现的功能、程序的启动方式、登录用户信息、程序的运行效果
5. 程序设计的流程图:

  

四,项目思路

4.1:设计数据模型

  1. 数据库模式设计
  2. 设置数据库为MySQL
  3. 数据库迁移

4.2:admin后台

  1. 在admin中注册模型
  2. 创建超级管理员

4.3:url路由和视图

  1. 路由设计
  2. 架构初步视图
  3. 创建HTML页面文件

4.4:前端页面设计

  1. 原生HTML页面
  2. 引入Bootstrap
  3. 创建base.html模板
  4. 创建页面导航条
  5. 使用Bootsrap静态文件
  6. 设计登录页面

4.5:登录视图

  1. 登录视图
  2. 数据验证
  3. 添加提示信息

4.6:Django表单

  1. 创建表单模型
  2. 修改视图
  3. 修改login界面
  4. 手动渲染表单

4.7:图片验证码

  1. 安装captcha
  2. 添加url路由
  3. 修改myforms.py
  4. 修改login.html

4.8:session会话

  1. 使用session
  2. 完善页面

4.9:注册视图

  1. 创建myforms
  2. 完善register.html
  3. 注册视图
  4. 密码加密

 

五,注意事项

5.1:创建项目和APP

1,创建项目和APP
    django-admin  startproject  mysite

    python  manage.py startapp  project1

2,设置时区和语言
    Django默认使用美国时间和英语,在项目的settings文件中,如下图所示:

LANGUAGE_CODE = 'en-us'
 
TIME_ZONE = 'UTC'
 
USE_I18N = True
 
USE_L10N = True
 
USE_TZ = True
    
    我们将其改为 亚洲/上海  时间和中文

LANGUAGE_CODE = 'zh-hans'
 
TIME_ZONE = 'Asia/Shanghai'
 
USE_I18N = True
 
USE_L10N = True
 
USE_TZ = False

3,启动工程
     (当完成Django的配置文件设置,最后启动工程)
    python manage.py runserver

  

5.2 关于Bootstrap 和JS

  Bootstrap3.3.7的下载地址:请点击我

  JQuery 的下载地址:请点击我

  注意:{% static  '相对路径' %}  这个Django为我们提供的静态文件加载方法,可以将页面与静态文件链接起来。

简要说明:

通过页面顶端的{% load staticfiles %}加载后,才可以使用static方法;

通过{% block title %}base{% endblock %},设置了一个动态的页面title块;

通过{% block css %}{% endblock %},设置了一个动态的css加载块;

通过{% block content %}{% endblock %},为具体页面的主体内容留下接口;

通过{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}将样式文件指
向了我们的实际静态文件。

  

5.3  表单验证的小技巧

  Python内置了一个locals()函数,它返回当前所有的本地变量字典,我们可以偷懒将这作为render函数的数据字典参数值,就不用费劲去构造一个形如{'message':message, 'login_form':login_form} 的字典了。这样做的好处就是大大的方便了我们,但是同时也可能往模板传入了一些多余的变量数据,造成了数据冗余降低效率。

return render(request, 'user19/login.html', {'message':message, 'login_form':login_form}

大约等同于

return render(request, 'user19/login.html', locals())

 

5.4  图片验证码

     为了防止机器人频繁登陆网站或者破坏分子恶意登陆,很多用户登录和注册系统都提供了图形验证码功能。

  验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。可以防止恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。

  图形验证码的历史比较悠久,到现在已经有点英雄末路的味道了。因为机器学习、图像识别的存在,机器人已经可以比较正确的识别图像内的字符了。但不管怎么说,作为一种防御手段,至少还是可以抵挡一些低级入门的攻击手段,抬高了攻击者的门槛。

  在Django中实现图片验证码功能非常简单,有现成的第三方库可以使用,我们不必自己开发(但是也需要自己能开发的出来)。这个库叫做django-simple-captcha.

安装captcha

  直接使用pip按照

pip  install django--simple-captcha

  Django 自动帮我们安装了相关的依赖库 six, olefile 和 pillow ,其中Pillow是大名鼎鼎的绘图模块。

 5.5  Session会话

  因为因特网HTTP协议的特性,每一次来自于用户浏览器的请求(request)都是无状态的,独立的。通俗的说,就是无法保存用户状态,后台服务器根本就不知道当前请求和以前以及以后请求是否来自同一用户。对于静态网站,这可能不是个问题,但是对于动态网站,尤其是京东,天猫,银行等购物或者金融网站,无法识别用户并保持用户状态是致命的,根本就无法提供服务。你可以尝试将浏览器的cookie功能关闭,你会发现将无法在京东登录和购物。

  为了实现连接状态的保持功能,网站会通过用户的浏览器在用户机器内被限定的硬盘位置中写入一些数据,也就是所谓的Cookie,通过Cookie可以保存一些诸如用户名,浏览记录,表单记录,登录和注销等各种数据。但是这种方式非常不安全,因为Cookie可以保存一些诸如用户名,浏览记录,表单记录,登录和注销等各种数据。但是这种方式非常不安全,因为Cookie保存在用户的机器上,如果Cookie被伪造,篡改或者修改,就会造成极大的安全威胁。因此,现在网站设计通常将Cookie用来保存一些不重要的内容,实际的用户数据和状态还是以Session会话的方式保存在服务器端。

  Session依赖Cookie! 但是与Cookie不同的地方在于Session将所有的数据都放在服务器端,用户浏览器的Cookie中只会保存一个非明文的识别信息,比如哈希值。

  Django提供了一个通用的Session框架,并且可以使用多种session数据的保存方式:

  • 保存在数据库内
  • 保存到缓存
  • 保存到文件内
  • 保存到cookie内

  通常情况下,没有特别需求的话,请使用保存在数据库内的方式,尽量不要保存在Cookie内。

   Django的session框架默认启动,并且已经注册在APP设置内,如果真的没有启用,那么参考下面的内容添加有说明的那两行,再执行migrate命令创建数据表,就可以使用session了。

# Application definition
 
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',    # 这一行
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
 
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  # 这一行
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

  当session启用后,传递给视图request参数的HTTPRequest对象将包含一个session属性,就像一个字典对象一样,我们可以在Django的任何地方读写request.sesssion属性,或者多次编辑使用它。

  下面是session使用参考:

class backends.base.SessionBase
        # 这是所有会话对象的基类,包含标准的字典方法:
        __getitem__(key)
            Example: fav_color = request.session['fav_color']
        __setitem__(key, value)
            Example: request.session['fav_color'] = 'blue'
        __delitem__(key)
            Example: del request.session['fav_color']  # 如果不存在会抛出异常
        __contains__(key)
            Example: 'fav_color' in request.session
        get(key, default=None)
            Example: fav_color = request.session.get('fav_color', 'red')
        pop(key, default=__not_given)
            Example: fav_color = request.session.pop('fav_color', 'blue')

  类似于字典数据类型的内置方法:

# 类似字典数据类型的内置方法
        keys()
        items()
        setdefault()
        clear()
 
 
        # 它还有下面的方法:
        flush()
            # 删除当前的会话数据和会话cookie。经常用在用户退出后,删除会话。
 
        set_test_cookie()
            # 设置一个测试cookie,用于探测用户浏览器是否支持cookies。由于
cookie的工作机制,你只有在下次用户请求的时候才可以测试。

        test_cookie_worked()
            # 返回True或者False,取决于用户的浏览器是否接受测试cookie。你
必须在之前先调用set_test_cookie()方法。

        delete_test_cookie()
            # 删除测试cookie。

        set_expiry(value)
            # 设置cookie的有效期。可以传递不同类型的参数值:
        • 如果值是一个整数,session将在对应的秒数后失效。例如
request.session.set_expiry(300) 将在300秒后失效.
        • 如果值是一个datetime或者timedelta对象, 会话将在指定的日期失效
        • 如果为0,在用户关闭浏览器后失效
        • 如果为None,则将使用全局会话失效策略
        失效时间从上一次会话被修改的时刻开始计时。
 
        get_expiry_age()
            # 返回多少秒后失效的秒数。对于没有自定义失效时间的会话,这等同于
SESSION_COOKIE_AGE.
            # 这个方法接受2个可选的关键字参数
        • modification:会话的最后修改时间(datetime对象)。默认是当前时间。
        •expiry: 会话失效信息,可以是datetime对象,也可以是int或None
 
        get_expiry_date()
            # 和上面的方法类似,只是返回的是日期
 
        get_expire_at_browser_close()
            # 返回True或False,根据用户会话是否是浏览器关闭后就结束。
 
        clear_expired()
            # 删除已经失效的会话数据。
        cycle_key()
            # 创建一个新的会话秘钥用于保持当前的会话数据。
django.contrib.auth.login() 会调用这个方法。

  

5.6  遗留问题

  如何才能在登录后,显示 当前。。在线,  登出,

  到现在没发现代码的问题。

六,笔记

6.1:设计数据模型

6.1.1  数据库模型设计

  作为一个用户登录和注册项目,需要保存的都是各种用户的相关信息。很显然,我们至少需要一个用户表User,在用户表里需要保存下面的信息:

  • 用户名
  • 密码
  • 邮箱地址
  • 性别
  • 创建时间

进入user19/models.py,写入代码,代码如下:

from django.db import models
 
 
class User(models.Model):
    '''用户表'''
 
    gender = (
        ('male','男'),
        ('female','女'),
    )
 
    name = models.CharField(max_length=128,unique=True)
    password = models.CharField(max_length=256)
    email = models.EmailField(unique=True)
    sex = models.CharField(max_length=32,choices=gender,default='男')
    c_time = models.DateTimeField(auto_now_add=True)
 
    def __str__(self):
        return self.name
 
    class Meta:
        ordering = ['c_time']
        verbose_name = '用户'
        verbose_name_plural = '用户'

  

各字段含义:

  • name必填,最长不超过128个字符,并且唯一,也就是不能有相同名字;
  • password必填,最长不超过256个字符(实际上可能不需要这么长);
  • email使用Django内置的邮箱类型,并且唯一;
  • 性别使用了一个choice,只能选择男女,默认为男;
  • 使用__str__帮助人性化显示对象信息;
  • 元数据里定义用户按创建时间的反序排列,也就是最近的最先显示;

 注意:这里的用户名指的是网络上注册的用户名,不要等同于现实中的真实姓名,所以采用了唯一机制。如果是现实中可以重复的人名,那么肯定不能设置unique的。

6.1.2  设置数据库为MySQL

  在settings中修改DATABASES:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django',        #数据库名字
        'USER': 'root',          #账号
        'PASSWORD': '123456',      #密码
        'HOST': '127.0.0.1',    #IP
        'PORT': '3306',                   #端口
    }

  在__init__.py里面导入pymysql模块

import pymysql
pymysql.install_as_MySQLdb()

  

6.1.3  数据库迁移

  注册APP(Mysite_project/settings.py)

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user19',
]

  迁移到数据库

python manage.py makemigrations

python manage.py migrate

  

6.2:URl路由和视图函数

   前面我们已经创建好数据模型了,下面我们就要设计好站点URL路由,对应的处理视图函数以及使用的前端模板了。

6.2.1  路由设计

  初步设想需要下面的四个URL:

Django学习笔记(9)—— 开发用户注册与登录系统_第1张图片

  这里采用的是二级路由的方法(并没有直接在根路由下直接编写路由条目):

Mysite_project/urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('user19/', include('user19.urls')),
    # 增加验证码的路径
    path('captcha', include('captcha.urls'))
]

  

user19/urls.py

from django.urls import path, include
from user19 import views
from django.contrib import admin

app_name = 'user19'

urlpatterns = [
    path('admin', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('register/', views.register),
    path('logout/', views.logout),

]

  

6.2.2  架构初步视图

  路由写好了,我们就进入视图文件user19/views.py编写视图的框架,代码如下:

from django.shortcuts import render,redirect
 
def index(request):
    pass
    return render(request,'user19/index.html')
 
def login(request):
    pass
    return render(request,'user19/login.html')
 
def register(request):
    pass
    return render(request,'user19/register.html')
 
def logout(request):
    pass
    return redirect('/user19/index/')

  我们先不着急完成视图内部的具体细节,而是先把框架搭起来。

6.2.3  创建HTML页面文件

  在项目的根路径下创建一个template目录,再在template目录里创建一个login目录,在里面创建三个文件夹,如下:

Django学习笔记(9)—— 开发用户注册与登录系统_第2张图片

index.html




    
    首页


首页

  

login.html




    
    登录


登录页面

  

register.html




    
    注册


注册页面

  

6.3:admin 后台

6.3.1  在admin中注册模型(我这里是user19/admin.py)

from django.contrib import admin
from . import models
 
admin.site.register(models.User)

  

6.3.2  创建超级管理员

python  manmage.py createsuperuser

  我们创建好之后,可以进入后台增加测试用户。

进入后台的方法(前提是项目URL等东西要配置好):

1,启动项目
    python manage.py runserver

2,在浏览器中打开地址:
    127.0.0.1:8000/user19/admin

  然后我们可以增加几个测试用户

Django学习笔记(9)—— 开发用户注册与登录系统_第3张图片

6.4:前端页面设计

6.4.1  原生HTML页面

  login.html 文件中的内容,写入下面的代码:




    
    登录


     

欢迎登录!

  页面如下:

Django学习笔记(9)—— 开发用户注册与登录系统_第4张图片

6.4.2  引入Bootstrap

   根目录下新建一个static目录,并将解压后的bootstrap-3.3.7目录,整体拷贝到static目录中,而且由于Bootstrap依赖于JQuery,所以我们需要引入JQuery,并且在static目录下,新建一个CSS和JS目录,作为以后的样式文件和JS文件的存放地,最后如下图所示:

Django学习笔记(9)—— 开发用户注册与登录系统_第5张图片

  然后打开项目的settings文件,在最下面添加配置,用于指定静态文件的搜索目录:

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

  

6.4.3  创建base.html模板

  既然要将前端页面做的像个样子,那么就不能和前面一页,每个页面都各写各的,单打独斗。一个网站要有自己的统一风格和公共部分,可以将这部分内容集中到一个基础模板base.html中。现在,在根目录下的template中新建一个base.html文件作为站点的基础模板。

  在Bootstrap文档中,为我们提供了一个非常简单而且又实用的基本模板,代码如下:



  
    
    
    
    
    Bootstrap 101 Template
 
    
    
 
    
    
    
  
  
    

你好,世界!

  将其整体拷贝到base.hml文件中。

6.4.4  创建页面导航条

  Bootstrap提供了现成的导航条组件

  其中有一部分,比如搜索框是我们目前还不需要的,需要将多余的内容裁减掉。同时,有一些名称和URL地址等需要按我们的实际内容修改。最终导航条的代码如下:


  

6.4.5  使用Bootstrap静态文件

  {% static  '相对路径' %}  这个Django为我们提供的静态文件加载方法,可以将页面与静态文件链接起来。

  最后,base.html内容如下:

{% load staticfiles %}
 


  
    
    
    
    
    {% block title %}base{% endblock %}
 
    
    
 
    
    
    
    {% block css %}{% endblock %}
  
  
    
 
    {% block content %}{% endblock %}
 
 
    
    
    
    
  

  

6.4.6  设计登录页面

  Bootstrap 提供了一个基本的表单样式,代码如下:

Example block-level help text here.

  我们结合Bootstrap和前面自己写的form表单,修改自己的login.html,最后修改的代码如下:

{% extends 'login/base.html' %}
{% load staticfiles %}
{% block title %}登录{% endblock %}
{% block css %}
    
{% endblock %}
 
 
{% block content %}
    
{% endblock %}

  代码说明:

  1. 通过{% extends 'base.html'} 继承了 'base.html' 模板的内容
  2. 通过{% block  title%}登录 {%endblock%}设置了专门的title
  3. 通过block css 引入了针对性的 login.css样式文件
  4. 添加了一个重置按钮。

  在static/css 目录下新建一个login.css样式文件,这里简单的写了一些样式:

body {
  background-color: #eee;
}
.form-login {
  max-width: 330px;
  padding: 15px;
  margin: 0 auto;
}
.form-login .form-control {
  position: relative;
  height: auto;
  -webkit-box-sizing: border-box;
     -moz-box-sizing: border-box;
          box-sizing: border-box;
  padding: 10px;
  font-size: 16px;
}
.form-login .form-control:focus {
  z-index: 2;
}
.form-login input[type="text"] {
  margin-bottom: -1px;
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}
.form-login input[type="password"] {
  margin-bottom: 10px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

  

6.5:登录视图

6.5.1  登录视图

  根据我们在路由中的设计,用户通过login.html中的表单填写的用户名和密码,并以POST的方式发送到服务器的/login/地址。服务器通过user19/views.py中的login()视图函数,接受并处理这一请求。

  我们可以通过下面的方法接收和处理请求:

def login(request):
    if request.method == "POST":
        username = request.POST.get('username')
        password = request.POST.get('password')        
        return redirect('/index/')

    return render(request, 'login/login.html')

  还需要在前端页面的form表单内添加一个{% csrf_token%} 标签:

  进入登录页面,输入用户名,密码。然后跳转到index页面。

6.5.2  数据验证

  通过唯一的用户名,使用Django的ORM去数据库中查询用户数据,如果有匹配项,则进行密码对比,如果没有匹配项,说明用户名不存在。如果密码比对错误,说明密码不正确。

def login(request):
    if request.method == "POST":
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        if username and password:  # 确保用户名和密码都不为空
            username = username.strip()
            # 用户名字符合法性验证
            # 密码长度验证
            # 更多的其它验证.....
            try:
                user = models.User.objects.get(name=username)
            except:
                return render(request, 'login/login.html')
            if user.password == password:
                return redirect('/index/')
    return render(request, 'user19/login.html')

  

6.5.3  添加提示信息

  上面的代码还缺少很重要的一部分内容,提示信息!无论是登录成功还是失败,用户都没有得到任何提示信息,这显然不行。

 修改login视图:

def login(request):
    if request.method == "POST":
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        message = "所有字段都必须填写!"
        if username and password:  # 确保用户名和密码都不为空
            username = username.strip()
            # 用户名字符合法性验证
            # 密码长度验证
            # 更多的其它验证.....
            try:
                user = models.User.objects.get(name=username)
                if user.password == password:
                    return redirect('/index/')
                else:
                    message = "密码不正确!"
            except:
                message = "用户名不存在!"
        return render(request, 'user19/login.html', {"message": message})
    return render(request, 'user19/login.html')

  增加了message变量,用于保存提示信息。当有错误信息的时候,将错误信息打包成一个字典,然后作为第三个参数提供给render()方法。这个数据字典在渲染模板的时候会传递到模板里供你调用。

  为了在前端页面显示信息,还需要对login.html进行修改:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}登录{% endblock %}
{% block css %}
    
{% endblock %}
 
 
{% block content %}
    
{% endblock %}

  将index.html 主页模板也修改一下,删除原有内容,添加下面的代码:

{% extends 'login/base.html' %}
{% block title %}主页{% endblock %}
{% block content %}
    

欢迎回来!

{% endblock %}

  

6.6  Django表单

  Django的表单给我们提供了下面三个主要功能:

  • 准备和重构数据用于页面渲染
  • 未数据创建HTML表单元素
  • 接受和处理用户从表单发送过来的数据

6.6.1  创建表单模型

  在user19下面创建一个myforms.py文件,在里面添加下面代码:

from django import forms
 
 
class UserForm(forms.Form):
    username = forms.CharField(label="用户名", max_length=128)
    password = forms.CharField(label="密码", max_length=256, widget=forms.PasswordInput)

  说明:

  1. 要先导入forms模板
  2. 所有的表单类都要继承forms.Form类
  3. 每个表单字段都有自己的字段类型比如CharField,它们分别对应一种HTML语言中
    内的一个input元素。这一点和Django模板系统的设计非常相似。
  4. label参数用于设置
  5. max_length 限制字段输入的最大长度。它同时起到两个作用,一是在浏览器页面限制用户输入不可超过字符数,二是在后端服务器验证用户输入的长度也不可超过。
  6. widget = forms.PasswordInput 用于指定该字段在form表单里表现为 ,也就是密码输入框。

6.6.2  修改视图

  使用Django的表单后,就要在视图中进行相应的修改:

from django.shortcuts import render,redirect
from . import models
from .forms import UserForm
 
def index(request):
    pass
    return render(request,'user19/index.html')
 
def login(request):
    if request.method == "POST":
        login_form = UserForm(request.POST)
        message = "请检查填写的内容!"
        if login_form.is_valid():
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            try:
                user = models.User.objects.get(name=username)
                if user.password == password:
                    return redirect('/index/')
                else:
                    message = "密码不正确!"
            except:
                message = "用户不存在!"
        return render(request, 'user19/login.html', locals())
 
    login_form = UserForm()
    return render(request, 'user19/login.html', locals())

  说明:

  • 对于非POST方法发送数据时,比如GET方法请求页面,返回空的表单,让用户可以填入数据
  • 对于POST方法,接收表单数据,并验证
  • 使用表单类自带的is_valid() 方法一步完成数据验证工作
  • 验证成功后可以从表单对象的cleaned_data数据字典中获取表单的具体值
  • 如果验证不通过,则返回一个包含先前数据的表单给前端页面,方便用户修改。也就是说,他会帮你保留先前填写的数据内容,而不是返回一个空表!

6.6.3  修改login界面

  Django的表单很重要的一个功能就是自动生成HTML的form表单内容。现在我们需要修改一下原来的login.html 文件:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}登录{% endblock %}
{% block css %}{% endblock %}
 
 
{% block content %}
    
{% if message %}
{{ message }}
{% endif %} {% csrf_token %}

欢迎登录

{{ login_form }}
{% endblock %}

  这样,启动程式,打开浏览器。则会生成新的HTML源码。(如果想看源码,则打开浏览器,刷新页面,进入源码查看)。

6.6.4  手动渲染表单

  直接{{ login_form }} 虽然好,啥都不用操心,但是界面真的不尽人意,往往不是我们想要的,如果要使用CSS , JS ,Bootstrap框架怎么办? 其实不用担心,这些都需要对表单内的input元素进行额外控制,所以需要我们手动渲染字段。

  可以通过{{ login_form.name_of_field }} 获取每一个字段,然后分别渲染,如下例所示:

{{ login_form.username.label_tag }} {{ login_form.username}}
{{ login_form.password.label_tag }} {{ login_form.password }}

  然后,在form类里添加Attr属性即可,如下所示修改 user19.myforms.py

from django import forms
 
class UserForm(forms.Form):
    username = forms.CharField(label="用户名", max_length=128, 
widget=forms.TextInput(attrs={'class': 'form-control'}))

    password = forms.CharField(label="密码", max_length=256, 
widget=forms.PasswordInput(attrs={'class': 'form-control'}))

  再次刷新页面,就显示正常了。

6.7  图片验证码功能

6.7.1  注册captcha

  在settings.py中,将‘captcha’注册到APP列表里:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user19',
    'captcha',
]

  captcha需要在数据库中建立自己的数据表,所以需要执行migrate命令生成数据表:

python manage.py migrate

  

6.7.2  添加URL路由

  根目录下的urls.py文件添加captcha对应的网址:

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('user19/', include('user19.urls')),
    # 增加验证码的路径
    path('captcha', include('captcha.urls'))
]

  

6.7.3  修改myforms.py

  如果上面都OK了,就可以直接在我们的myforms.py文件中添加CaptchaField了。

from django import forms
from captcha.fields import CaptchaField
 
class UserForm(forms.Form):
    username = forms.CharField(label="用户名", max_length=128, 
widget=forms.TextInput(attrs={'class': 'form-control'}))

    password = forms.CharField(label="密码", max_length=256, 
widget=forms.PasswordInput(attrs={'class': 'form-control'}))

    captcha = CaptchaField(label='验证码')

  需要提前导入form captcha.fields import CaptchaField ,然后就像写普通的form字段一样添加一个captcha字段就可以了。

6.7.4  修改login.html文件

  由于我们前面是手动生成的form表单,所以还是需要修改一下,添加captcha的相关内容,如下所示:

{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}登录{% endblock %}
{% block css %}
    
{% endblock %}
 
 
{% block content %}
    
{% endblock %}

  这里额外的添加了一条 {{ login_form.captcha.errors }} 用于明确指示用户,你的验证码不正确。其中验证图形码是否正确的工作都是在后台自动完成的。只需要使用is_valid()这个myforms内置的验证方法就一起进行了,完全不需要再视图函数中添加任何的验证代码,非常方便快捷!

6.8  Session的使用

6.8.1 使用session

   首先,修改login/views.py中的login()视图函数:

def login(request):
    if request.session.get('is_login',None):
        return redirect('/index')
 
    if request.method == "POST":
        login_form = UserForm(request.POST)
        message = "请检查填写的内容!"
        if login_form.is_valid():
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            try:
                user = models.User.objects.get(name=username)
                if user.password == password:
                    request.session['is_login'] = True
                    request.session['user_id'] = user.id
                    request.session['user_name'] = user.name
                    return redirect('/index/')
                else:
                    message = "密码不正确!"
            except:
                message = "用户不存在!"
        return render(request, 'user/login.html', locals())
 
    login_form = UserForm()
    return render(request, 'user/login.html', locals())

  通过下面的if语句,我们不允许重复登录:

if request.session.get('is_login',None):
    return redirect("/index/")

  通过下面的语句,我们往session字典内写入用户状态和数据:

request.session['is_login'] = True
request.session['user_id'] = user.id
request.session['user_name'] = user.name

  我们完全可以往里面写任何数据,不仅仅限于用户相关!

  既然有了session记录用户登录状态,那么就可以完善我们的登出视图函数了:

def logout(request):
    if not request.session.get('is_login', None):
        # 如果本来就未登录,也就没有登出一说
        return redirect("/index/")
    request.session.flush()
    # 或者使用下面的方法
    # del request.session['is_login']
    # del request.session['user_id']
    # del request.session['user_name']
    return redirect("/index/")

  flush() 方法是比较安全的一种做法,而且一次性将session中的所有内容全部清空,确保不留后患。但是也有不好的地方,那就是如果你在session中夹带了一点“私货”,会被一并删除,这一点一定要注意。

6.8.2  完善页面

   有了用户状态,就可以根据用户登录与否,展示不同的页面,比如导航条内容。

  首先,修改base.html文件:


      

  通过if判断,当登录时,显示当前用户名和登出按钮。未登录时,显示登录和注册按钮。

  注意其中的模板语言,{{ request }} 这个变量会被默认传入模板中,可以通过原点的调用方式,获取它内部的{{ request.session }} ,再进一步的获取session中的内容。其实{{ request }} 的数据远不知此,例如 {{ request.apth }} 就可以获取先前的URL地址。

  再修改一下index.html 页面,根据登录与否的不同,显示不同的内容:

{% extends 'base.html' %}
{% block title %}主页{% endblock %}
{% block content %}
    {% if request.session.is_login %}
    

你好,{{ request.session.user_name }}!欢迎回来!

{% else %}

你尚未登录,只能访问公开内容!

{% endif %} {% endblock %}

  

6.9  注册视图

6.9.1  创建myforms.py

  在/user19/myforms.py中添加一个新的表单类:

class RegisterForm(forms.Form):
    gender = (
        ('male', "男"),
        ('female', "女"),
    )
    username = forms.CharField(label="用户名", max_length=128, 
widget=forms.TextInput(attrs={'class': 'form-control'}))

    password1 = forms.CharField(label="密码", max_length=256, 
widget=forms.PasswordInput(attrs={'class': 'form-control'}))

    password2 = forms.CharField(label="确认密码", max_length=256, 
widget=forms.PasswordInput(attrs={'class': 'form-control'}))

    email = forms.EmailField(label="邮箱地址", 
widget=forms.EmailInput(attrs={'class': 'form-control'}))

    sex = forms.ChoiceField(label='性别', choices=gender)

    captcha = CaptchaField(label='验证码')

  说明:

  • gender和User模型中的一样,其实可以拉出来作为常量共用,为了直观,特意重写一遍
  • password1和password2,用于输入两遍密码,并进行比较,防止误输入密码
  • email是一个邮箱输入框
  • sex是一个select下拉框

6.9.2  完善register.html

  同样的,类似于login.html文件,我们再register.html中编写forms相关条目:

{% extends 'login/base.html' %}
 
{% block title %}注册{% endblock %}
{% block content %}
    
{% if message %}
{{ message }}
{% endif %} {% csrf_token %}

欢迎注册

{{ register_form.username.label_tag }} {{ register_form.username}}
{{ register_form.password1.label_tag }} {{ register_form.password1 }}
{{ register_form.password2.label_tag }} {{ register_form.password2 }}
{{ register_form.email.label_tag }} {{ register_form.email }}
{{ register_form.sex.label_tag }} {{ register_form.sex }}
{{ register_form.captcha.errors }} {{ register_form.captcha.label_tag }} {{ register_form.captcha }}
{% endblock %}

  

6.9.3  注册视图

  进入/user19/views.py 文件,现在来完善我们的register()视图:

def register(request):
    if request.session.get('is_login', None):
        # 登录状态不允许注册。你可以修改这条原则!
        return redirect("/index/")
    if request.method == "POST":
        register_form = RegisterForm(request.POST)
        message = "请检查填写的内容!"
        if register_form.is_valid():  # 获取数据
            username = register_form.cleaned_data['username']
            password1 = register_form.cleaned_data['password1']
            password2 = register_form.cleaned_data['password2']
            email = register_form.cleaned_data['email']
            sex = register_form.cleaned_data['sex']
            if password1 != password2:  # 判断两次密码是否相同
                message = "两次输入的密码不同!"
                return render(request, 'user19/register.html', locals())
            else:
                same_name_user = models.User.objects.filter(name=username)
                if same_name_user:  # 用户名唯一
                    message = '用户已经存在,请重新选择用户名!'
                    return render(request, 'user19/register.html', locals())
                same_email_user = models.User.objects.filter(email=email)
                if same_email_user:  # 邮箱地址唯一
                    message = '该邮箱地址已被注册,请使用别的邮箱!'
                    return render(request, 'user19/register.html', locals())
 
                # 当一切都OK的情况下,创建新用户
 
                new_user = models.User.objects.create()
                new_user.name = username
                new_user.password = password1
                new_user.email = email
                new_user.sex = sex
                new_user.save()
                return redirect('/login/')  # 自动跳转到登录页面
    register_form = RegisterForm()
    return render(request, 'user19/register.html', locals())

  从大体逻辑上,也是先实例化一个RegisterForm的对象,然后使用 is_valid()验证数据,再从cleaned_data中获取数据。

  重点在于注册逻辑,首先两次输入的密码必须相同,其次不能存在相同用户名和邮箱,最后如果条件都满足,利用ORM的API,创建一个用户实例,然后保存到数据库内。

 6.10  密码加密

  用户注册的密码应该加密才对

  对于如何加密密码,有很多不同的途径,其安全程度也高低不等。这里我们使用Python内置的hashlib库,使用哈希值的方式加密密码,可能安全等级不够高,但足够简单,方便使用。

  首先, user19/views.py 中编写一个hash函数:

import hashlib
 
def hash_code(s, salt='mysite'):# 加点盐
    h = hashlib.sha256()
    s += salt
    h.update(s.encode())  # update方法只接收bytes类型
    return h.hexdigest()

  然后,我们还要对login()和register()视图进行一下修改:

#login.html
 
if user.password == hash_code(password):  # 哈希值和数据库内的值进行比对
 
#register.html
 
new_user.password = hash_code(password1)  # 使用加密密码

  user19/views.py

from django.shortcuts import render,redirect
from . import models
from .forms import UserForm,RegisterForm
import hashlib
 
def index(request):
    pass
    return render(request,'user19/index.html')
 
def login(request):
    if request.session.get('is_login', None):
        return redirect("/index/")
    if request.method == "POST":
        login_form = UserForm(request.POST)
        message = "请检查填写的内容!"
        if login_form.is_valid():
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            try:
                user = models.User.objects.get(name=username)
                if user.password == hash_code(password):  # 哈希值和数据库内的值进行比对
                    request.session['is_login'] = True
                    request.session['user_id'] = user.id
                    request.session['user_name'] = user.name
                    return redirect('/index/')
                else:
                    message = "密码不正确!"
            except:
                message = "用户不存在!"
        return render(request, 'user19/login.html', locals())
 
    login_form = UserForm()
    return render(request, 'user19/login.html', locals())
 
 
def register(request):
    if request.session.get('is_login', None):
        # 登录状态不允许注册。你可以修改这条原则!
        return redirect("/index/")
    if request.method == "POST":
        register_form = RegisterForm(request.POST)
        message = "请检查填写的内容!"
        if register_form.is_valid():  # 获取数据
            username = register_form.cleaned_data['username']
            password1 = register_form.cleaned_data['password1']
            password2 = register_form.cleaned_data['password2']
            email = register_form.cleaned_data['email']
            sex = register_form.cleaned_data['sex']
            if password1 != password2:  # 判断两次密码是否相同
                message = "两次输入的密码不同!"
                return render(request, 'user19/register.html', locals())
            else:
                same_name_user = models.User.objects.filter(name=username)
                if same_name_user:  # 用户名唯一
                    message = '用户已经存在,请重新选择用户名!'
                    return render(request, 'user19/register.html', locals())
                same_email_user = models.User.objects.filter(email=email)
                if same_email_user:  # 邮箱地址唯一
                    message = '该邮箱地址已被注册,请使用别的邮箱!'
                    return render(request, 'user19/register.html', locals())
 
                # 当一切都OK的情况下,创建新用户
 
                new_user = models.User.objects.create()
                new_user.name = username
                new_user.password = hash_code(password1)  # 使用加密密码
                new_user.email = email
                new_user.sex = sex
                new_user.save()
                return redirect('/login/')  # 自动跳转到登录页面
    register_form = RegisterForm()
    return render(request, 'user19/register.html', locals())
 
def logout(request):
    if not request.session.get('is_login',None):
        return redirect('/index/')
    request.session.flush()
 
    return redirect('/index/')
 
def hash_code(s, salt='mysite_login'):
    h = hashlib.sha256()
    s += salt
    h.update(s.encode())  # update方法只接收bytes类型
    return h.hexdigest()

  重启服务器,进入注册页面,新建一个用户,就可以进入后台查看用户密码情况。然后我们可以再使用该用户登录。

 

七,结果展示

1,展示整个项目的结构

Django学习笔记(9)—— 开发用户注册与登录系统_第6张图片

Django学习笔记(9)—— 开发用户注册与登录系统_第7张图片

Django学习笔记(9)—— 开发用户注册与登录系统_第8张图片

 

2,后台展示我们管理的界面

Django学习笔记(9)—— 开发用户注册与登录系统_第9张图片

3,展示我们的登录界面

Django学习笔记(9)—— 开发用户注册与登录系统_第10张图片

4,展示登录出现验证码错误页面:

Django学习笔记(9)—— 开发用户注册与登录系统_第11张图片

5,展示我们的注册页面

 Django学习笔记(9)—— 开发用户注册与登录系统_第12张图片

 6,下面注册一个harden,注册成功后  可以在admin后台看到注册的用户

Django学习笔记(9)—— 开发用户注册与登录系统_第13张图片

7,在后台查看注册的harden信息如下:

Django学习笔记(9)—— 开发用户注册与登录系统_第14张图片

 

8,在数据库查看其注册的数据如下:

Django学习笔记(9)—— 开发用户注册与登录系统_第15张图片

   我们可以看到密码长度跟你哈希算法的不同,已经变得很长了,所以前面model中设置password字段时候,不要将max_length 设置为16这么小的数字。

9,登录harden的信息,登录成功界面显示如下:

Django学习笔记(9)—— 开发用户注册与登录系统_第16张图片

 

八,代码

  这里只粘贴关键的重要的代码。

 Mysite_project/Mysite_project/urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('user19/', include('user19.urls')),
    # 增加验证码的路径
    path('captcha', include('captcha.urls'))
]

  

 Mysite_project/static/css/login.css

body{
    background-color: #eee;
}
.form-login{
    max-width: 330px;
    padding: 15px;
    margin: 0 auto;
}
.form-login .form-control{
    position: relative;
    height: auto;
    -webkit-box-sizing: border-box;
       -moz-box-sizing: border-box;
            box-sizing: border-box;
    padding: 10px;
    font-size: 16px;
}

.form-login .form-control:focus{
    z-index: 2;
}
.form-login input[type='text']{
    margin-bottom: -1px;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
}

.form-login input[type='password']{
    margin-bottom: 10px;
    border-top-right-radius: 0;
    border-top-left-radius: 0;
}

  

 Mysite_project/template/user19/login.html




    
    login




{#通过下面语句 继承了base.html 模板的内容#}
{% extends 'base.html' %}
{% load staticfiles %}
{% block title %}登录{% endblock %}
{% block css %}
    
{% endblock %}


{% block content %}
{% endblock %}

  

 Mysite_project/template/user19/index.html




    
    Title


{# Mysite/template/user19/index.html#}

    {% extends 'base.html' %}
    {% block title %}主页{% endblock %}
    {% block content %}
        {% if request.session.is_login %}
        

hello,{{ request.session.user_name }} ! Welcome to back!

{% else %}

你尚未登录,只能访问公开内容

{% endif %} {% endblock %}

  

 Mysite_project/template/user19/register.html




    
    Title



{% extends 'base.html' %}

{% block title %}注册{% endblock %}
{% block content %}
    
{% if messgae %}
{{ message }}
{% endif %} {% csrf_token %}

欢迎注册

{{ register_form.username.label_tag }} {{ register_form.username }}
{{ register_form.password1.label_tag }} {{ register_form.password1 }}
{{ register_form.password2.label_tag }} {{ register_form.password2 }}
{{ register_form.email.label_tag }} {{ register_form.email }}
{{ register_form.sex.label_tag }} {{ register_form.sex }}
{{ register_form.cpatcha.errors }} {{ register_form.captcha.label_tag }} {{ register_form.captcha }}
{% endblock %}

  

 Mysite_project/template/base.html

{% load staticfiles %}



  
    
    
    
    
    {% block title %}base{% endblock %}

    
    

    
    
    
  {% block css %}{% endblock %}
  
  





{% block content %}{% endblock %}


    
    

     
    

  

  

 Mysite_project/user19/admin.py

from django.contrib import admin

# Register your models here.
from . import models

admin.site.register(models.User)

 

 Mysite_project/user19/models.py

from django.db import models
from django import forms

# Create your models here.
class User(models.Model):
    # 用户表
    gender = (
        ('male', '男'),
        ('female', '女'),
    )
    # 最长不超过128个字符,并且唯一,也就是不能有相同的姓名
    username = models.CharField(max_length=128, unique=True)
    password = models.CharField(max_length=256)
    email = models.EmailField(unique=True)
    sex = models.CharField(max_length=32, choices=gender, default='男')
    c_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        # 帮助人性化显示对象信息
        return self.username

    class Meta:
        # 元数据里定义用户按创建时间的反序排列,也就是最近的最先显示
        ordering = ['c_time']
        verbose_name = '用户'
        verbose_name_plural = '用户'

  

 Mysite_project/user19/myforms.py

from django import forms
from captcha.fields import CaptchaField

class UserForm(forms.Form):
    username = forms.CharField(label='用户名', max_length=128,
                               widget=forms.TextInput(attrs={'class': 'form-control'}))
    password = forms.CharField(label='密码', max_length=256,
                               widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    captcha = CaptchaField(label='验证码')


class RegisterForm(forms.Form):
    gender = (
        ('male', '男'),
        ('female', '女'),
    )

    username = forms.CharField(max_length=128, label='用户名',
                               widget=forms.TextInput(attrs={'class': 'form-control'}))
    password1 = forms.CharField(max_length=256, label='密码',
                                widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    password2 = forms.CharField(max_length=256, label='确认密码',
                                widget=forms.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(label='邮箱地址', widget=forms.EmailInput(attrs={'class': 'form-control'}))
    sex = forms.ChoiceField(choices=gender, label='性别')
    captcha = CaptchaField(label="验证码")

  

 Mysite_project/user19/urls.py

from django.urls import path, include
from user19 import views
from django.contrib import admin

app_name = 'user19'

urlpatterns = [
    path('admin', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('register/', views.register),
    path('logout/', views.logout),


]

  

 Mysite_project/user19/views.py

from django.shortcuts import render, HttpResponse, redirect

# Create your views here.
from user19 import models
from .myforms import UserForm, RegisterForm

def login(request):
    # if request.session.get('is_login', None):
    #     return redirect('/user19/index')

    if request.method == 'POST':
        login_form = UserForm(request.POST)
        message = '请检查填写的内容'
        if login_form.is_valid():
            username = login_form.cleaned_data['username']
            password = login_form.cleaned_data['password']
            try:
                user = models.User.objects.get(username=username)
                # 哈希值和数据库内的值进行比对
                if user.password == hash_code(password):
                # if user.password == password:
                    request.session['is_login'] = True
                    request.session['user_id'] = user.id
                    request.session['user_name'] = user.username
                    return redirect('/user19/index/')
                else:
                    message = 'password is incorrect'
            except:
                message = 'username is not exist'
        return render(request, 'user19/login.html', locals())
    login_form = UserForm()
    # return render(request, 'user19/login.html')
    return render(request, 'user19/login.html', locals())

def index(request):
    pass
    return render(request, 'user19/index.html')

def register(request):
    # if request.session.get('is_login', None):
    #     # 登录状态不允许注册,你可以修改这条原则
    #     return redirect('/user19/index/')

    if request.method == 'POST':
        register_form = RegisterForm(request.POST)
        message = '请检查填写的内容'
        # 获取数据
        if register_form.is_valid():
            username = register_form.cleaned_data['username']
            password1 = register_form.cleaned_data['password1']
            password2 = register_form.cleaned_data['password2']
            email = register_form.cleaned_data['email']
            sex = register_form.cleaned_data['sex']
            # 判断两次密码是否相同
            if password1 != password2:
                message = '两次输入的密码不同'
                return render(request, 'user19/register.html', locals())
            else:

                same_name_user = models.User.objects.filter(username=username)
                print(same_name_user)
                # 用户名唯一
                if same_name_user:

                    message = '用户名已经存在,请重新选择用户名!'
                    # return  HttpResponse("用户名已经存在")
                    return render(request, 'user19/register.html', locals())

                same_email_user = models.User.objects.filter(email=email)
                if same_email_user:
                    message = '该邮箱地址已经被注册,请使用别的邮箱'
                    # return HttpResponse("邮箱地址已经注册")
                    return render(request, 'user19/register.html', locals())
                # 当一切都OK的情况下,创建了新用户
                new_user = models.User.objects.create()
                new_user.username = username
                # 使用加密密码
                new_user.password = hash_code(password1)
                # new_user.password = password1
                new_user.email = email
                new_user.sex = sex
                new_user.save()
                return redirect('/user19/login/')
    # 如果请求不是POST,则渲染的是一个空的表单。
    register_form = RegisterForm()
    # 如果用户通过表单提交数据,但是数据验证、不合法,则渲染的是一个带有错误信息的表单
    return render(request, 'user19/register.html', locals())

def logout(request):
    if not request.session.get('is_login', None):
        # 如果没登录,也就没有登出一说
        return redirect('/user19/index/')
    request.session.flush()
    # 或者使用下面的方法
    # del request.session['is_login']
    # del request.session['user_id']
    # del request.session['user_name']
    return redirect('/user19/index/')


# 密码加密
import hashlib

# 加点盐
def hash_code(s, salt='Mysite_project'):
    h = hashlib.sha256()
    s += salt
    # update方法只接受bytes类型
    h.update(s.encode())
    return h.hexdigest()

  

 

 

 

参考文献:https://blog.csdn.net/laikaikai/article/details/80563387#commentBox

(备注:此次项目,主要参考上面文献,目的是巩固学习Django的知识点)

你可能感兴趣的:(Django学习笔记(9)—— 开发用户注册与登录系统)