一,项目题目: 开发用户注册与登录系统
该项目主要练习使用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:设计数据模型
- 数据库模式设计
- 设置数据库为MySQL
- 数据库迁移
4.2:admin后台
- 在admin中注册模型
- 创建超级管理员
4.3:url路由和视图
- 路由设计
- 架构初步视图
- 创建HTML页面文件
4.4:前端页面设计
- 原生HTML页面
- 引入Bootstrap
- 创建base.html模板
- 创建页面导航条
- 使用Bootsrap静态文件
- 设计登录页面
4.5:登录视图
- 登录视图
- 数据验证
- 添加提示信息
4.6:Django表单
- 创建表单模型
- 修改视图
- 修改login界面
- 手动渲染表单
4.7:图片验证码
- 安装captcha
- 添加url路由
- 修改myforms.py
- 修改login.html
4.8:session会话
- 使用session
- 完善页面
4.9:注册视图
- 创建myforms
- 完善register.html
- 注册视图
- 密码加密
五,注意事项
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:
这里采用的是二级路由的方法(并没有直接在根路由下直接编写路由条目):
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目录,在里面创建三个文件夹,如下:
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
然后我们可以增加几个测试用户
6.4:前端页面设计
6.4.1 原生HTML页面
login.html 文件中的内容,写入下面的代码:
登录 欢迎登录!
页面如下:
6.4.2 引入Bootstrap
根目录下新建一个static目录,并将解压后的bootstrap-3.3.7目录,整体拷贝到static目录中,而且由于Bootstrap依赖于JQuery,所以我们需要引入JQuery,并且在static目录下,新建一个CSS和JS目录,作为以后的样式文件和JS文件的存放地,最后如下图所示:
然后打开项目的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 提供了一个基本的表单样式,代码如下:
我们结合Bootstrap和前面自己写的form表单,修改自己的login.html,最后修改的代码如下:
{% extends 'login/base.html' %} {% load staticfiles %} {% block title %}登录{% endblock %} {% block css %} {% endblock %} {% block content %}{% endblock %}
代码说明:
- 通过{% extends 'base.html'} 继承了 'base.html' 模板的内容
- 通过{% block title%}登录 {%endblock%}设置了专门的title
- 通过block css 引入了针对性的 login.css样式文件
- 添加了一个重置按钮。
在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%} 标签:
通过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 %}{% endblock %}{% 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 }}
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,展示整个项目的结构
2,后台展示我们管理的界面
3,展示我们的登录界面
4,展示登录出现验证码错误页面:
5,展示我们的注册页面
6,下面注册一个harden,注册成功后 可以在admin后台看到注册的用户
7,在后台查看注册的harden信息如下:
8,在数据库查看其注册的数据如下:
我们可以看到密码长度跟你哈希算法的不同,已经变得很长了,所以前面model中设置password字段时候,不要将max_length 设置为16这么小的数字。
9,登录harden的信息,登录成功界面显示如下:
八,代码
这里只粘贴关键的重要的代码。
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 %}{% if message %} {{ message }}{% endif %} {% csrf_token %}Welcome to login!
{{ login_form.username.label_tag }} {{ login_form.username }}{{ login_form.password.label_tag }} {{ login_form.password }}{{ login_form.captcha.errors }} {{ login_form.captcha.label_tag }} {{ login_form.captcha }}
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 %}{% endblock %}{% 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 }}
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的知识点)