Django3.0 实现注册和登录功能
2020-6-1
Django3.0在2.0的基础上完善了websock的功能,因为想在我现在做的项目上加上及时通讯的功能,使用了Django3.0版本,本教程讲解如何实现网页登录功能。
源码:请访问Github
1、 创建APP
对于创建项目和虚拟环境我就不赘述了,直接从APP开始。为了代码管理更方便我把所有的APP放到APPS文件夹内,此时的目录结构如图
统一有关用户操作的功能都放到user的app中。
在setting.py中注册user APP
为了方便管理我在本项目中使用了二级域名。所以在mysite的url中还需要配置二级域名的url。所以需要在user APP中新增一个名为urls的python文件。并且在mysite的urls中添加进去。
此时基础工作已经完成。
2、 需求分析&数据库设计
为了满足我们的需求,对页面进行分析。设计数据库。
注册功能中,通过邮箱注册,服务器发送注册链接到注册邮箱,用户点击链接完成注册。
所以需要的字段有:
用户名
用户密码
验证链接码等。django自带的库中有user相关的库,进行重写:
models.py
import datetime
from django.db import models
from django.contrib.auth.models import AbstractUser
# 用户信息
class UserInformationModel(AbstractUser):
nike_name = models.CharField(max_length=50, verbose_name=u"昵称", default='')
birthday = models.DateField(verbose_name=u'生日', null=True, blank=True)
gender = models.CharField(max_length=6, choices=(("male", u"男"), ("female", u"女")), default="female")
image = models.ImageField(upload_to="image/%Y/%m", default="image/default.png", max_length=200, null=True)
describe = models.CharField(max_length=500, default=' ', verbose_name=u'个性签名')
class Meta:
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __unicode__(self):
return self.username
# 验证码
class EmailVerificationModel(models.Model):
code = models.CharField(max_length=20, verbose_name=u'验证码')
email = models.EmailField(max_length=200, verbose_name=u'邮箱')
send_type = models.CharField(max_length=10, choices=(("register", u'注册'), ("forget", u'密码找回')))
send_time = models.DateTimeField(default=datetime.now)
class Meta:
verbose_name = u'邮箱验证码'
verbose_name_plural = verbose_name
需要将以上的字段保存到数据库中,所以在setting中配置数据库信息。
DATABASES = {
'default': {
# 数据库类型
'ENGINE': 'django.db.backends.mysql',
# 数据库名称
'NAME': 'blog',
#登录用户
'USER': 'root',
#登录密码
'PASSWORD': '151968',
# 主机
'HOST': 'localhost',
#端口
'PORT': '3306',
}
}
在数据库同步之前,因为是继承AbstractUser类,需要在setting.py文件中添加:
# 设置用户组
AUTH_USER_MODEL = "user.UserInformationModel"
否则会报错。
数据库同步:
python manage.py makemigrations
python manage.py migrate
在数据库中发现下表表示同步成功
3、 功能实现
使用Django自带的Form进行简单的前端验证,以减少垃圾注册,减少服务器负担。
在user文件夹中新建一个form.py
form.py:
from django import forms
from captcha.fields import CaptchaField
#用户登录
class UserLoginForm(forms.Form):
Username = forms.CharField(required=True)
Password = forms.CharField(required=True, min_length=6)
#用户注册
class UserRegisterForm(forms.Form):
# 用户名验证 字段需要与前端input的name属性保持一致
Username = forms.CharField(required=True)
# 密码验证 字段需要与前端input的name属性保持一致
Password = forms.CharField(required=True, min_length=6)
# 重复密码验证 字段需要与前端input的name属性保持一致
rPassword = forms.CharField(required=True)
# 验证码
captcha = CaptchaField(error_messages={"Invalid": u'验证码错误'})
验证码使用第三方库captcha,pip安装后注册到setting.py中.
3.1 邮箱验证码功能实现
在user app中添加一个python package文件夹,添加Email_Send.py
此时目录结构为:
在Email_Send.py中编写:
from apps.user.models import EmailVerificationModel
from random import Random
from django.core.mail import send_mail
from mysite.settings import EMAIL_FROM
# 获取随机字符串
def get_random_string(code_length):
string = ""
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
length = len(chars) - 1
random = Random()
for i in range(code_length):
string += chars[random.randint(0, length)]
return string
def Email_Send_Register(email,sendtype="register"):
email_content = EmailVerificationModel()
email_code = get_random_string(16)
email_content.code = email_code
email_content.email = email
email_content.send_type = sendtype
email_content.save()
email_title = '用户注册'
email_body = '点击此链接完成注册.http://127.0.0.1:8000/user/sign-up/activate/{0}'.format(email_code)
send_key = send_mail(email_title, email_body, EMAIL_FROM, [email])
def Email_Send_Forget(email,sendtype="forget"):
email_content = EmailVerificationModel()
email_code = get_random_string(16)
email_content.code = email_code
email_content.email = email
email_content.send_type = sendtype
email_content.save()
email_title = '密码找回'
email_body = '点击此链接密码找回.http://127.0.0.1:8000/user/forget/verification/{0}'.format(email_code)
send_key = send_mail(email_title, email_body, EMAIL_FROM, [email])
同时并且在setting中添加邮箱配置:
# 邮件发送,使用第三方代理服务器发送
EMAIL_HOST = 'smtp.qq.com'
EMAIL_PORT = 25
# 邮件发送主机用户名
EMAIL_HOST_USER = 'XXXX@qq.com'
# SMTP代理发送密码
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
# 代理服务器发送
# EMAIL_USE_SSL = True
# 邮件发送者地址
EMAIL_FROM = 'XXXX@qq.com'
因为注册页面需要使用到验证码,所以需要对验证码进行配置:
# captcha验证码
CAPTCHA_OUTPUT_FORMAT = '%(image)s %(text_field)s %(hidden_field)s '
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_null',)
CAPTCHA_LENGTH = 6
# 超时(minutes)
CAPTCHA_TIMEOUT = 1
静态文件配置:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
配置之后再同步一下数据库:
python manage.py makemigrations
python manage.py migrate
此时就可以编写视图函数了。
3.2、 View.py编写
views.py:
from django.shortcuts import render
# emil
from apps.user.until.Email_Send import *
# 加密
from django.contrib.auth.hashers import make_password
from django.contrib.auth.hashers import check_password
# 查询
from django.db.models import Q
from django.contrib.auth.backends import ModelBackend
# 登录
from django.contrib.auth import authenticate
from django.contrib.auth import login
from django.contrib.auth import logout
# 跳转
from django.http import HttpResponseRedirect
from django.http import HttpResponse
from django.shortcuts import reverse
# view
from django.views.generic.base import View
# from
from .form import *
# model
from .models import UserInformationModel
# 重写类方法根据邮箱和用户名查找
class UserVerificationView(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user_information = UserInformationModel.objects.get(Q(email=username) | Q(username=username))
if check_password(password, user_information.password):
return user_information
except Exception as err:
return None
# 登录
class UserLoginView(View):
def get(self, request):
user_login_form = UserLoginForm()
return render(request, "sign-in.html", locals())
def post(self, request):
user_login_form = UserLoginForm(request.POST)
if user_login_form.is_valid():
username = request.POST.get("Username", "")
password = request.POST.get("Password", "")
try:
user_information = authenticate(username=username, password=password)
if user_information is not None:
if user_information.is_active:
login(request, user_information)
# 定向跳转到博客主页
return HttpResponseRedirect(reverse("blog-index"))
else:
return render(request, "sign-in.html", {
"message": "该用户尚未激活!",
})
else:
return render(request, "sign-in.html", {
"message": "用户名或密码错误!",
})
except Exception as error:
return render(request, "sign-in.html", {
"message": "未知错误,请与网站管理员联系!",
})
# 注册
class UserRegisterView(View):
def get(self, request):
UserRegister = UserRegisterForm()
return render(request, "sign-up.html", locals())
def post(self, request):
UserRegister = UserRegisterForm(request.POST)
if UserRegister.is_valid():
username = request.POST.get("Username", "")
password = request.POST.get("Password", "")
rpassword = request.POST.get("rPassword", "")
if password != rpassword:
return render(request, "sign-up.html", {
"message": "两次密码输入不一致!",
"UserRegister": UserRegister,
})
if UserInformationModel.objects.filter(email=username):
return render(request, "sign-up.html", {
"message": "该邮箱已被注册!",
"UserRegister": UserRegister,
})
UserInformation = UserInformationModel()
UserInformation.email = username
UserInformation.username = username
UserInformation.is_active = False
UserInformation.password = make_password(password)
UserInformation.save()
Email_Send_Register(email=username, sendtype="register")
return render(request, "user-sign-up-turn.html")
else:
UserRegister = UserRegisterForm()
return render(request, "sign-up.html", locals())
# 用户验证
class UserActivateView(View):
def get(self, request, activate_code):
all_records = EmailVerificationModel.objects.filter(code=activate_code)
if all_records:
for record in all_records:
email = record.email
user = UserInformationModel.objects.get(email=email)
user.is_active = True
user.save()
else:
return render(request, 'user-sign-up-activate-fail.html')
return render(request, 'user-sign-up-activate-succeed.html')
user-sign-in.html:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>用 户 登 录</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="application/x-javascript"> addEventListener("load", function () {
setTimeout(hideURLbar, 0);
}, false);
function hideURLbar() {
window.scrollTo(0, 1);
} </script>
<link href="{% static 'login®ister/css/snow.css' %}" rel="stylesheet" type="text/css" media="all"/>
<link href="{% static 'login®ister/css/style.css' %}" rel="stylesheet" type="text/css" media="all"/>
<link rel="stylesheet" href="{% static 'font-awesome/css/font-awesome.css' %}">
<!- Layui ->
<link href="{% static 'layui/css/layui.css' %}" rel="stylesheet">
<!- ico ->
<link href="{% static "login®ister/css/font-awesome.min.css" %}" rel="stylesheet" type="text/css" media="all">
<!- js ->
<script type="text/javascript" src="{% static 'jQuery/jquery-3.3.1.js' %}"></script>
<script type="text/javascript" src="{% static 'jQuery/jquery-3.3.1.min.js' %}"></script>
<script type="text/javascript" src="{% static 'layui/layui.js' %}"></script>
<script type="text/javascript" src="{% static 'layui/layui.all.js' %}"></script>
<!-- web font -->
<link href="//fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet">
<link href="//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<!-- //web font -->
</head>
<body>
<div class="snow-container">
<div class="snow foreground"></div>
<div class="snow foreground layered"></div>
<div class="snow middleground"></div>
<div class="snow middleground layered"></div>
<div class="snow background"></div>
<div class="snow background layered"></div>
</div>
<div class="top-buttons-agileinfo">
<a href="#">登录</a>
<a href="{% url 'register' %}" class="active">注册</a>
</div>
<h1 class="pad-bottom-1"> Sign In </h1>
<div class="main-agileits">
<!--form-stars-here-->
<div class="form-w3-agile">
<h2 class="sub-agileits-w3layouts">Sign In</h2>
<form action="{% url 'login' %}" method="post">
<div class="input-group pos-relative">
<span class="input-group-addon user-box"><i class="fa fa-envelope-o fa-fw"></i></span>
<input id="input-control" type="text" name="Username" placeholder="用户名或邮箱" required=""/>
</div>
<div class="input-group pos-relative">
<span class="input-group-addon user-box" style=""><i class="fa fa-lock fa-fw"></i></span>
<input id="input-control" type="password" name="Password" placeholder="密码" required=""/>
</div>
<p class="p-bottom-w3ls left pad-bottom-3 pad-right-3">
<a class="color-fff" href="{% url 'register' %}">点击注册</a>
</p>
<p class="p-bottom-w3ls left pad-bottom-3">
</p>
<div class="submit-w3l">
<input type="submit" value="登录">
</div>
{% csrf_token %}
</form>
</div>
</div>
<!--//form-ends-here-->
<!-- copyright -->
<div class="copyright w3-agile">
<p> © 2020 rainbowsea.xyz</p>
</div>
<!-- //copyright -->
<script type="text/javascript">
//监听输入
$('input[name=Username]').bind('input propertychange', function () {
console.log($('input[name=Username]').val())
});
$('input[name=Password]').bind('input propertychange', function () {
let password = $('input[name=Password]').val()
if (password.length < 8) {
layer.tips('您的密码长度不足8位,当前长度为:' + password.length, $('input[name=Password]'));
} else {
layer.close(layer.tips('您的密码长度不足8位,当前长度为:' + password.length, $('input[name=Password]')));
}
});
$("input[type=submit]").click(function () {
let password = $('input[name=Password]').val()
if (password.length < 8) {
alert("密码长度不够!")
return false;
}
})
var message = "{{ message }}"
if (message){
layer.msg(message)
}
</script>
</body>
</html>
user-sign-up.html:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>用 户 注 册 </title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="application/x-javascript"> addEventListener("load", function () {
setTimeout(hideURLbar, 0);
}, false);
function hideURLbar() {
window.scrollTo(0, 1);
} </script>
<link href="{% static 'login®ister/css/snow.css' %}" rel="stylesheet" type="text/css" media="all"/>
<link href="{% static 'login®ister/css/style.css' %}" rel="stylesheet" type="text/css" media="all"/>
<!- Layui ->
<link href="{% static 'layui/css/layui.css' %}" rel="stylesheet">
<!- ico ->
<link href="{% static "font-awesome/css/font-awesome.min.css" %}" rel="stylesheet" type="text/css" media="all">
<!- js ->
<script type="text/javascript" src="{% static 'jQuery/jquery-3.3.1.js' %}"></script>
<script type="text/javascript" src="{% static 'jQuery/jquery-3.3.1.min.js' %}"></script>
<script type="text/javascript" src="{% static 'layui/layui.js' %}"></script>
<script type="text/javascript" src="{% static 'layui/layui.all.js' %}"></script>
<!-- google font -->
<link href="//fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet">
<link href="//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<!-- //web font -->
</head>
<body>
<div class="snow-container">
<div class="snow foreground"></div>
<div class="snow foreground layered"></div>
<div class="snow middleground"></div>
<div class="snow middleground layered"></div>
<div class="snow background"></div>
<div class="snow background layered"></div>
</div>
<div class="top-buttons-agileinfo">
<a href="{% url 'login' %}" class="active">登录</a><a href="#">注册</a>
</div>
<h1 class="pad-bottom-1"> Sign Up Form </h1>
<div class="main-agileits">
<!--form-stars-here-->
<div class="form-w3-agile">
<h2 class="sub-agileits-w3layouts">登录</h2>
<form action="{% url 'register' %}" method="post">
{% block input %}
<div class="input-group pos-relative">
<span class="input-group-addon ico-box-register"><i class="fa fa-envelope-o fa-fw"></i></span>
<input id="input-control" type="email" name="Username" placeholder="邮箱" required=""/>
</div>
<div class="input-group pos-relative">
<span class="input-group-addon ico-box-register" style=""><i class="fa fa-lock fa-fw"></i></span>
<input id="input-control" type="password" name="Password" placeholder="密码" required=""/>
</div>
<div class="input-group pos-relative">
<span class="input-group-addon ico-box-register" style=""><i class="fa fa-lock fa-fw"></i></span>
<input id="input-control" type="password" name="rPassword" placeholder="重复密码" required=""/>
</div>
<!-验证码 ->
{{ UserRegister.captcha }}
<!- 提交 ->
<div class="submit-w3l">
<input type="submit" value="Sign up">
</div>
{% csrf_token %}
{% endblock %}
</form>
</div>
</div>
<!--//form-ends-here-->
<!- copyright ->
<div class="copyright w3-agile">
<p> © 2020 rainbowsea.xyz</p>
</div>
<script>
var msg = '{{ UserRegister.errors.captcha }}'
if (msg) {
layer.msg(msg)
}
/* 刷新验证码 */
$('.captcha').click(function () {
$.getJSON("/captcha/refresh/", function (result) {
$('.captcha').attr('src', result['image_url']);
$('#id_captcha_0').val(result['key'])
});
});
$("#id_captcha_1").attr("placeholder", "请输入验证码")
/*
行为验证
*/
$('input[name=Password]').bind('input propertychange', function () {
let password = $('input[name=Password]').val()
if (password.length < 8) {
layer.tips('您的密码长度不足8位,且至少应是数字、字母、等两种字符以上的组合', $('input[name=Password]'));
} else {
layer.close(layer.tips('您的密码长度不足8位,且至少应是数字、字母、等两种字符以上的组合', $('input[name=Password]')));
}
});
$('input[name=rPassword]').bind('input propertychange', function () {
let password1 = $('input[name=Password]').val()
let password2 = $('input[name=rPassword]').val()
if (password1 != password2) {
layer.tips("您两次输入的密码不一致", $('input[name=rPassword]'));
} else {
layer.close(layer.tips("您两次输入的密码不一致", $('input[name=rPassword]')));
}
});
$('input[name=captcha_1]').bind('input propertychange', function () {
if ($('input[name=captcha_1]').val().length < 6) {
layer.tips("验证码长度小于6,当前长度为" + $('input[name=captcha_1]').val().length, $('input[name=captcha_1]'), {
tips: 3
})
}
else {
layer.close(layer.tips("验证码长度小于6,当前长度为" + $('input[name=captcha_1]').val().length, $('input[name=captcha_1]'), {
tips: 3
}))
}
});
$('input[type=submit]').click(function () {
let password1 = $('input[name=Password]').val()
let password2 = $('input[name=rPassword]').val()
let regNumber = /\d+/; //验证0-9的任意数字最少出现1次。
let regString = /[a-zA-Z]+/; //验证大小写26个字母任意字母最少出现1次。
let code = $('input[name=captcha_1]').val()
if (password1 != password2) {
layer.msg('两次密码输入不一致');
return false;
}
if (password1.length < 8) {
layer.msg('密码长度小于8');
return false
}
if (code.length < 6) {
layer.msg('验证码错误');
return false;
}
if (regNumber.test(password1) && regString.test(password1)) {
return true
} else {
layer.msg('密码至少应是数字和字母的两种组合');
return false;
}
});
var message = "{{ message }}"
if (message){
layer.msg('{{message}}');
}
</script>
</body>
</html>
4、注册路由
先配置user下面的路由
user/urls.py:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : urls.py
@Time : 2020/6/1 14:23
@Author : c-cc
@Software : PyCharm
'''
from django.urls import path
from .views import *
urlpatterns = [
path('sign-in/', UserLoginView.as_view(), name="login"),
path('sign-up/', UserRegisterView.as_view(), name="register"),
# 活跃验证
path('sign-up/activate/<str:activate_code>/', UserActivateView.as_view(), name="activate"),
# 退出登录
path('logout/', UserLogoutView.as_view(), name="logout"),
]
mysite/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('user/', include('apps.user.urls')),
# capthca
path('captcha/', include('captcha.urls')),
]
自此完成Django的登录和注册功能。我自己初学的时候也是遇到了很多的坑,现在写一比较详细的教程来帮助一下新手玩家不踩我踩过的坑。还有很多功能还没完成,如果有时间我也会继续更新。另外此项目我也部署到了我的个人服务器上附上地址::http://rainbowsea.xyz/,当然主页是我网上找的,自己写还是很恼火的,除此以外的大多都是我自己操作的,也、欢迎各位大佬莅临参观。如果发现了BUG,别告诉我,最近忙毕业设计…
转载请注明来源。