注册登录首页展示实现
首页和登录页面的配置
访问我们事先准备好的资源文件中的html文件
在html中找到首页的html,拷贝到我们的template目录下
新建static目录,用来存放css,js,图片等静态资源
配置处理静态文件的url,主项目下的urls.py中
rlpatterns = [
url(r'^xadmin/', xadmin.site.urls),
# TemplateView.as_view()会将template转换为view
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index')
]
在settings.py中设置静态文件的目录
# 说明静态文件放在哪个目录
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
修改index页面中样式还有一些静态资源的引用地址
全部换位/static/目录下
然后点击运行,刷新页面就可以看到我们的首页显示了.
拷贝登录页面到template
替换css/js等静态资源文件.
url配置跳转登录界面
# 登录页面跳转url
url('^login/$', TemplateView.as_view(template_name="login.html"), name="login")
在index页面,ctrl+f找到登录.将a标签中的地址替换为login的url
此时我们的首页已经可以成功显示了,通过首页点击登录可以成功跳转到登录界面
用户登录
配置url之前我们先书写好对应处理的view
Django的view实际上是一个函数,接收request请求对象,处理后返回response对象.
users/views.py
from django.shortcuts import render
def login(request):
# POST请求
if request.method == "POST":
pass
# GET请求
elif request.method == 'GET':
return render(request, 'login.html', {})
urls.py
from users.views import user_login
# 登录页面跳转url login不要直接调用。而只是指向这个函数对象。
url('^login/$',login, name="login")
断点调试
在两行返回语句的位置打上断点:
点击debug,进入首页后点击登录.可以看到下面所示
通过值浏览器窗口可以看到这是一个<WSGIRequest: GET '/login/'>
对象
path:
指向地址
f8继续运行,跳转到登录页面.
用户登录,通过form进行验证
templates/login.html
修改action='/login/' method = 'post'
input中的name值将会被传递到后台.以键值对的形式.
只保留post这里的断点.用户输入用户名密码.查看debug情况.
然后访问,会发生如下的错误
这是因为html页面内必须加上csrf_token
才能传递给后台
服务器会随机的给前端发一串符号,你必须使用{% csrf_token %}将字符串带回来,才允许你使用post
此时我们查看前端页面:
可以看到html登录下面有一个隐藏着的值:csrf token,不会显示
此时点击登录跳转到pass位置
可以看到request中的POST是一个queryset对象.我们可以把它当成一个字典来用.
来取到前端的数据,然后通过Django自带的auth方法进行验证.
from django.contrib.auth import authenticate, login
# 登录提交表单为post
if request.method == "POST":
# 取不到时为空,username,password为前端页面name值
user_name = request.POST.get("username", "")
pass_word = request.POST.get("password", "")
# 成功返回user对象,失败返回null
user = authenticate(username= user_name, password= pass_word)
# 如果不是null说明验证成功
if user is not None:
# login_in 两参数:request, user
# 实际是对request写了一部分东西进去,然后在render的时候:
# request是要render回去的。这些信息也就随着返回浏览器。完成登录
login(request, user)
# 跳转到首页 user request会被带回到首页
return render(request, "index.html")
# 没有成功说明里面的值是None,并再次跳转回主页面
else:
return render(request, "login.html", {})
authenticate调用只需要传入用户名和密码。成功会返回user对象,失败返回null
注意:
html中首页的展示通过用户是否登录来进行判断
设置成功如果登录显示个人中心那段,未登录显示登录注册
改造为使用邮箱用户名均可.
方法就是使用自定义的authenticate方法,通过创建一个类CustomBackend 继承自ModelBackend
rom django.contrib.auth.backends import ModelBackend
from .models import UserProfile
# 并集运算
from django.db.models import Q
# 实现用户名邮箱均可登录
# 继承ModelBackend类,因为它有方法authenticate,可点进源码查看
class CustomBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
# 不希望用户存在两个,get只能有一个。两个是get失败的一种原因 Q为使用并集查询
user = UserProfile.objects.get(Q(username=username)|Q(email=username))
# django的后台中密码加密:所以不能password==password
# UserProfile继承的AbstractUser中有def check_password(self, raw_password):
if user.check_password(password):
return user
except Exception as e:
return None
settings.py中
# 设置邮箱和用户名均可登录
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
用户提示:return页面时提供它的错误信息
return render(request, 'login.html', {"msg": '用户名或密码错误'})
html中如何取到这个值
<div class="error btns login-form-tips" id="jsLoginTips">{{ msg }}</div>
将以前的fbv(基于函数的视图)改成cbv(基于类的视图)
# 登录视图类
class LoginView(View):
def get(self, request):
# render就是渲染html返回用户
return render(request, 'login.html', {})
def post(self, request):
# 取不到的时候为空,username,password为前端页面标签元素的name属性值.
username = request.POST.get('username', '')
password = request.POST.get('password', '')
# 使用auth模块的authenticate方法进行验证,成功返回user,失败返回null
user = authenticate(username=username, password=password)
# 如果不是null
if user:
# auth模块的login方法,两个参数request,user
# 实际上是对request写了一部分东西进去,然后在render的时候:
# request是要render回去的.这些信息也就随着返回给浏览器,完成登录
login(request, user)
# 跳转到首页 user,request会被带回到首页
return render(request, 'index.html')
else:
我们去看源码View里面的请求方法有
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
对各个方法的解释
urls.py中换用类实现
url(r'^login/$', LoginView.as_view(), name='index'),
form字段验证
验证最大长度,是否为空等一系列,去数据库查询之前的一些对比
user.py下新建forms文件
def post(self, request):
# 类实例化需要一个字典参数dict:request.POST就是一个QueryDict所以直接传入
# POST中的usernamepassword,会对应到form中
login_form = LoginForm(request.POST)
#is_valid判断我们字段是否有错执行我们原有逻辑,验证失败跳回login页面
if login_form.is_valid():
# 取不到时为空,username,password为前端页面name值
user_name = request.POST.get("username", "")
pass_word = request.POST.get("password", "")
# 成功返回user对象,失败返回null
user = authenticate(username=user_name, password=pass_word)
# 如果不是null说明验证成功
if user is not None:
# login_in 两参数:request, user
# 实际是对request写了一部分东西进去,然后在render的时候:
# request是要render回去的。这些信息也就随着返回浏览器。完成登录
login(request, user)
# 跳转到首页 user request会被带回到首页
return render(request, "index.html")
# 验证不成功跳回登录页面
# 没有成功说明里面的值是None,并再次跳转回主页面
else:
return render(request, "login.html", {"msg": "用户名或密码错误! "})
错误提示的时候,表单验证失败之后,就没必要提示用户名或密码错误了
并且将form传回前端:
# 登录视图类
class LoginView(View):
def get(self, request):
# render就是渲染html返回用户
return render(request, 'login.html', {})
def post(self, request):
# 类实例化需要一个字典参数dict:request.POST就是一个QueryDict所以直接传入
# POST中的usernamepassword,会对应到form中
login_form = LoginForm(request.POST)
# is_valid判断我们字段是否有执行我们的原有的逻辑,验证失败跳回到login页面
if login_form.is_valid():
# 取不到的时候为空,username,password为前端页面标签元素的name属性值.
username = request.POST.get('username', '')
password = request.POST.get('password', '')
# 使用auth模块的authenticate方法进行验证,成功返回user,失败返回null
user = authenticate(username=username, password=password)
# 如果不是null
if user:
# auth模块的login方法,两个参数request,user
# 实际上是对request写了一部分东西进去,然后在render的时候:
# request是要render回去的.这些信息也就随着返回给浏览器,完成登录
login(request, user)
# 跳转到首页 user,request会被带回到首页
return render(request, 'index.html')
else:
return render(request, 'login.html', {"msg": '用户名或密码错误'})
else:
return render(request, 'login.html', {'login_form': login_form})
前端中取值
image.png给它们加上errorput会显示红色外框
将forms错误信息显示出来
<div class="error btns login-form-tips" id="jsLoginTips">
{% for key, error in login_form.errors.items %}
{{ error }}
{% endfor %}
{{ msg }}</div>
- 写了一个类继承Django的View,然后写了get,post方法(get/post的if是Django替我们完成的 )
- url中调用了LoginView的as_view方法需要加上括号,进行调用
- Django的form进行表单验证并把error值传递到前台
- is_vilid方法,验证表单
session和cookie的自动登录机制
cookie的存储
cookie是浏览器支持的一种本地存储方式.以dict,键值对方式存储.
{"sessionkey": "123"}
浏览器会自动对于它进行解析.
http请求是一种无状态的请求
用户向服务器发起的两次请求之间是没有状态的.也就是说服务器并不知道这是同一个用户发的.
做到记住用户:
浏览器a在向服务器发起请求,服务器会自动给浏览器a回复一个session_id,浏览器a把id
保存到cookie当中,在下一次请求时带上这个cookie里的id值向浏览器请求
服务器就知道你是哪个浏览器发送过来的了.
有状态请求示例:
服务器a发回来的id会放到服务器a的域之下.不能跨域访问cookie
使用浏览器随便打开一个网页,然后f12打开.
可以看到存储在浏览器的cookie
解决cookie放在本地不安全的问题(session)
用户在第一次请求之后,浏览器回复的id既可以是用户的user id
也可以是任意的字符串,我们称之为session id
根据用户名和密码,服务器会采用自己的规则生成session_id.这个session_id保存在
本地的cookie里,浏览器请求的时候会携带.
- 输入用户名&密码
- 调用login(),后端程序会根据用户名和密码生成session_id,保存在数据库,然后发送给浏览器
- 用户登录之后,需要通过这个session_id取出这些基本信息
Django默认的session表中记录了用户登录时,后端我们Django为用户生成的sessionid
session_key 发送到浏览器取名字为session id
通过session id用户访问任何一个页面都会携带,服务器会认识
Setting.py中
上面的sessions的app会拦截我们的每一次的request请求,在request中找到session id,然后去数据库中进行查询.然后通过session key去找到session data,此时直接为我们取出了user
在服务器返回浏览器的response中也会直接加上session id
cookie是浏览器本地存储机制.存在域名之下,存储不安全.
服务器在返回id时通过规则生成一个字符串,并设置了过期时间.
存储在服务器端(数据库)
参考文章
用户注册
拷贝注册页面进入templates目录
书写我们要对应处理的view(RegisterView)
users/views.py
# 注册视图类
class Register(View):
def get(self, request):
return render(request, 'register.html')
配置对应的url
url(r'^register/$', RegisterView.as_view(), name='register'),
修改index页面中的注册url
href = ''{% url "register" %}'>注册</a>
static静态文件加载参考
Django中static(静态)文件详解以及{% static %}标签的使用
关键步骤load staticfile
<html>的头部位置
{% load staticfiles %}
使用验证码库实现验证码
https://github.com/mbi/django-simple-captcha
安装配置使用第三方的验证码库
workon mxonline3
pip install django-simple-captcha
workon mxonline2
pip install django-simple-captcha==0.4.6
- 将captcha注册到你的INSTALL_APPS下
- 然后在urls.py中增加一个路由的入口
rom django.conf.urls import url, include
urlpatterns += [
url(r'^captcha/', include('captcha.urls')),
]
makemigrations
migrate
可以看到生成的表
将验证码展示到页面
users/forms.py:
定义我们的register form:
# 引入验证码field
from captcha.fields import CaptchaField
# 验证码form & 注册表单form
class RegisterForm(forms.Form):
# 此处email与前端name需保持一致。
email = forms.EmailField(required=True)
# 密码不能小于5位
password = forms.CharField(required=True, min_length=5)
# 应用验证码
captcha = CaptchaField()
在我们的RegisterView中实例化并传送到前端:
# form表单验证 & 验证码
from .forms import LoginForm, RegisterForm
# 注册功能的view
class RegisterView(View):
# get方法直接返回页面
def get(self, request):
# 添加验证码
register_form = RegisterForm()
return render(request, "register.html", {'register_form':register_form})
找到验证码部分
<img src="[/captcha/image/57fa09852c04cead81c277a36b93a74ce2d629d9/
(http://127.0.0.1:8000/captcha/image/57fa09852c04cead81c277a36b93a74ce2d629d9/)" alt="captcha" class="captcha" />
<input id="id_captcha_0" name="captcha_0" type="hidden" value="57fa09852c04cead81c277a36b93a74ce2d629d9" />
<input autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false"
id="id_captcha_1" name="captcha_1" type="text" />
我们写的代码只有label,但是前端可以看到input框等,也就是RegisterForm会为我们生成输入框+验证码
隐藏的字符串的框会被带到后台,由Django为我们进行验证.验证该验证码是否保存过.
可以看到我们数据库中将这个haskey进行了保存.这个key与验证码内容对应
后台会把验证码的值与haskey进行联合查询
编写Register view的后台逻辑
添加post方法
def post(self, request):
# 实例化form
register_form = RegisterForm(request.POST)
if register_form.is_valid():
pass
前端修改form提交的方式以及提交到哪一个url,还有csrf_token
前端form提交加上对应的crsf token
刷新验证码是前端帮我们完成的
//刷新验证码
function refresh_captcha(event){
$.get("/captcha/refresh/?"+Math.random(), function(result){
$('#'+event.data.form_id+' .captcha').attr("src",result.image_url);
$('#id_captcha_0').attr("value",result.key);
});
return false;
}
获取前端页面值并封装成一个user_profile对象,保存到数据库
from django.contrib.auth.hashers import make_password
if register_form.is_valid():
user_name = request.POST.get("email", "")
pass_word = request.POST.get("password", "")
# 实例化一个user_profile对象,将前台值存入
user_profile = UserProfile()
user_profile.username = user_name
user_profile.email = user_name
# 加密password进行保存
user_profile.password = make_password(pass_word)
user_profile.save()
pass
发送邮件实现
setting 中配置:
# 发送Email的服务器,用来在用户注册激活邮箱的时候,给用户发送激活链接.
EMAIL_HOST = "smtp.sina.com"
EMAIL_PORT = 25 # 端口号
EMAIL_HOST_USER = "fioman123@sina.com" # 用户名
EMAIL_HOST_PASSWORD = "112233cq"
EMAIL_USE_TLS = False
EMAIL_FROM = "fioman123@sina.com"
apps:utils/email_send.py:
# encoding:utf-8
from random import Random
from django.core.mail import send_mail # 导入Django自带的Email发送模块
from users.models import EmailVerifyRecord
from MxOnline.settings import EMAIL_FROM
__author__ = 'Fioman'
__date__ = '2018/11/23 10:36'
def random_str(random_length=8):
str = ''
# 生成字符串的可选字符串
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(random_length):
str += chars[random.randint(0, length)]
return str
# type 为发送邮箱干嘛的,这里用于激活
def send_register_email(email,send_ype="register"):
# 发送之前保存到数据库,以便到时候查询链接是否存在
# 实例化一个EmailVerifyRecord对象
email_record = EmailVerifyRecord()
# 生成随机的code放入到链接
code = random_str(16)
email_record.code = code
email_record.email =email
email_record.send_type = send_ype
# 保存到数据库
email_record.save()
# 定义邮件的内容
email_title = ""
email_body = ""
if send_ype == "register":
email_title = "智学教育网 注册激活链接"
email_body = "请点击下面的链接激活你的账号: http://127.0.0.1:8000/active/{0}".format(code)
# 使用Django内置的函数完成邮件发送.四个参数:主题,邮件内容,从哪里发,接受者list
send_status = send_mail(email_title,email_body,EMAIL_FROM,[email])
# 如果发送成功:
if send_status:
pass
为sina邮箱设置smtp开启服务
post 中加上发送邮件
users/views.py:
修改默认的激活状态为false
post 方法中
# 默认激活状态为false
user_profile_is_active = False
书写处理激活的view
# 激活用户的view
class ActiveUserView(View):
def get(self, request, active_code):
# 查询邮箱验证记录是否存在
all_record = EmailVerifyRecord.objects.filter(code = active_code)
# 激活form负责给激活跳转进来的人加验证码
active_form = ActiveForm(request.GET)
# 如果不为空也就是有用户
if all_record:
for record in all_record:
# 获取到对应的邮箱
email = record.email
# 查找到邮箱对应的user
user = UserProfile.objects.get(email=email)
user.is_active = True
user.save()
# 激活成功跳转到登录页面
return render(request, "login.html", )
# 自己瞎输的验证码
else:
return render(request, "register.html", {"msg": "您的激活链接无效","active_form": active_form})
配置用户激活的url并且通过url提取到变量
# 激活用户url
url(r'^active/(?P<active_code>.*)/$',ActiveUserView.as_view(), name= "user_active")
这里通过?p将后面.*代表全部提取的正则,符合的内容传入参数active_code中/为结尾
注册功能制作完毕,流程:注册,发送邮件,激活,登录.
密码找回功能的实现
1. 书写处理忘记密码的view,并且配置对应的url
# 忘记密码的view,点击忘记密码的时候,会调用这个视图
class ForgetView(View):
def get(self,request):
return render(request,"forgetpwd.html",{})
配置url
url(r'^forget/$', ForgetView.as_view(), name='forget_pwd'),
2.定义form,用来生成验证码
users/forms.py:
# 忘记密码的表单,用来生成验证码
class ForgetFrom(forms.Form):
email = forms.CharField(required=True)
captcha = CaptchaField(error_messages={'invalid': '验证码错误'})
3.发送邮件的逻辑,当发送的类型是'forget'的时候,邮件发送的内容
# type 为发送邮箱干嘛的,这里用于激活
def send_register_email(email, send_type="register"):
# 发送之前保存到数据库,以便到时候查询链接是否存在
# 实例化一个EmailVerifyRecord对象
email_record = EmailVerifyRecord()
# 生成随机的code放入到链接
code = random_str(16)
email_record.code = code
email_record.email =email
email_record.send_type = send_type
# 保存到数据库
email_record.save()
# 定义邮件的内容
email_title = ""
email_body = ""
if send_type == "register":
email_title = "智学教育网 注册激活链接"
email_body = "请点击下面的链接激活你的账号: http://127.0.0.1:8000/active/{0}".format(code)
# 使用Django内置的函数完成邮件发送.四个参数:主题,邮件内容,从哪里发,接受者list
send_status = send_mail(email_title,email_body,EMAIL_FROM,[email])
# 如果发送成功:
if send_status:
pass
elif send_type == 'forget':
email_title = "智学教育网 密码重置连接"
email_body = "请点击下面的链接激活你的账号: http://127.0.0.1:8000/reset/{0}".format(code)
# 使用Django内置的函数完成邮件发送.四个参数:主题,邮件内容,从哪里发,接受者list
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
# 如果发送成功:
if send_status:
pass
4.点击这个链接的时候,验证显示密码修改的页面
urls.py
url(r'^reset/(?P<active_code>.*)/$', ResetView.as_view(), name='reset_pwd'),
views.py
# 重置密码的视图处理类
class ResetView(View):
def get(self, request, active_code):
# 查询邮箱验证记录是否存在
all_record = EmailVerifyRecord.objects.filter(code=active_code)
# 进行表单验证
active_form = ActiveForm(request.GET)
if all_record:
for record in all_record:
# 获取对应的邮箱
email = record.email
# 将email传回来
return render(request, 'password_reset.html', {'email': email})
else:
return render(request, 'forgetpwd.html', {"msg": '你重置的密码连接无效,请重新请求', "active_form": active_form})
因为重置密码的页面需要传入参数,但是提交的时候不需要参数,所有这个get和提交不能在同一个url配置里面.
创建改变密码的forms
# 重置密码form实现
class ModifyPwdForm(forms.Form):
# 密码不能小于6位
password1 = forms.CharField(required=True)
password2 = forms.CharField(required=True)
书写改变密码的view.这个view主要处理判断密码是否可用以及将新密码保存到数据库
处理重置密码提交表单的View的视图类
class ModifyPwdView(View):
def post(self, request):
modifypwd_form = ModifyPwdForm(request.POST)
if modifypwd_form.is_valid():
pw1 = request.POST.get("password1", "")
pw2 = request.POST.get("password2", "")
email = request.POST.get("email", "")
# 如果两次的密码不一样,返回错误信息
if pw1 != pw2:
return render(request, 'password_reset.html', {"email": email, "msg": '密码不一致'})
# 如果密码一致
user = UserProfile.objects.get(email=email)
# 加密成密文,数据库存储的是密文
user.password = make_password(pw1)
# save保存到数据库
user.save()
# 返回到登录页面提示,密码修改成功
return render(request, 'login.html', {'msg': '密码修改成功,请登录'})
# 验证失败说明密码位数不够
else:
email = request.POST.get("email", "")
return render(request, 'password_reset.html', {"email": email, "modifypwd_form": modifypwd_form})
配置modify的url
url(r'^modify_pwd/$', ModifyPwdView.as_view(), name='modify_pwd'),
更多扩展:
重置密码链接是否被点击过,过期时间等