前言
登录注册逻辑大多情况都雷同,每次重写一遍效率底下,为提高效率在这里放一份,有意者自取
实现的功能:
- 图片验证码校验
- 邮箱验证码校验
- 错误提交后显示错误信息并保留已填写数据
- 刷新图片验证码保留已填写数据
- 二次确认密码校验
- 注册用户名重复验证
- 密码加密存储
- 密码校验
效果图:

正文
models.py
python
class Admin(models.Model):
""" 用户 """
email_validator = RegexValidator(
regex=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
message='请输入有效的邮箱地址'
)
email = models.EmailField(validators=[email_validator])
username = models.CharField(verbose_name="用户名", max_length=128)
password = models.CharField(verbose_name="密码", max_length=128)
def __str__(self):
return self.username
BootStrap.py
python
from django import forms
class BootStrap:
exclude_fields = []
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
for name,field in self.fields.items():
if name in self.exclude_fields:
continue
if field.widget.attrs:
field.widget.attrs['class'] = 'form-control'
field.widget.attrs['placeholder'] = '请输入' + field.label
else:
field.widget.attrs = {'class':'form-control','placeholder':'请输入' + field.label}
class BootStrapForm(BootStrap,forms.Form):
pass
class BootStrapModelForm(BootStrap,forms.ModelForm):
pass
form.py
python
from app01.utils import BootStrap
class AdminModel(BootStrap.BootStrapModelForm):
code = forms.CharField(label="验证码")
class Meta:
model = models.Admin
fields = ['username','password']
widgets = {
"password":forms.PasswordInput(render_value=True)
}
class AdminModelConfirm(BootStrap.BootStrapModelForm):
code = forms.CharField(label="验证码")
confirm_password = forms.CharField(label="确认密码",widget=forms.PasswordInput(render_value=True),)
class Meta:
model = models.Admin
fields = "__all__"
widgets = {
"password":forms.PasswordInput(render_value=True)
}
def clean_password(self):
pwd = make_password(self.cleaned_data['password'])
return pwd
def clean_confirm_password(self):
if not check_password(self.cleaned_data['confirm_password'],self.cleaned_data["password"]):
raise ValidationError("两次输入密码不一致")
return make_password(self.cleaned_data['confirm_password'])
def clean_username(self):
user_name = self.cleaned_data['username']
if models.Admin.objects.filter(username=user_name).exists():
raise ValidationError("该用户名已存在")
return user_name
code.py(生成图形验证码)
python
import random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
def check_code(width=120, height=30, char_length=5, font_file='Monaco.ttf', font_size=28):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
def rndChar():
"""
生成随机字母
:return:
"""
# return str(random.randint(0, 9))
return chr(random.randint(65, 90))
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
# 写文字
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
# 画干扰线
for i in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
settings.py(邮箱配置)
python
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.qq.com' # 或其他邮件服务商
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'xxxxx@qq.com' # 发件人邮箱
EMAIL_HOST_PASSWORD = 'xxxxxxx' # 邮箱授权码,不是密码
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
相关视图函数
python
from django.shortcuts import render, HttpResponse, redirect
from app01.utils.form import AdminModel, AdminModelConfirm
from app01.utils.code import check_code
from app01 import models
from io import BytesIO
import random
import string
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.hashers import check_password
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
def login(request):
""" 处理登录逻辑 """
if request.method == "GET":
form = AdminModel()
return render(request, 'login.html', {"form": form})
form = AdminModel(request.POST)
if form.is_valid():
user_code = form.cleaned_data.pop('code')
code = request.session.get('code_string', '')
if code.upper() != user_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {"form": form})
username = form.cleaned_data['username']
password = form.cleaned_data['password']
admin_object = models.Admin.objects.filter(username=username).first()
if admin_object and check_password(password, admin_object.password):
request.session.pop('code_string')
request.session['info'] = {"id": admin_object.id, "username": admin_object.username}
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/order/list/")
form.add_error("username", "用户名或密码错误")
return render(request, 'login.html', {"form": form})
return render(request, 'login.html', {"form": form})
def image_code(request):
""" 生成图形验证码 """
img, code_string = check_code()
request.session['code_string'] = code_string
request.session.set_expiry(60)
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())
def register(request):
""" 处理注册逻辑 """
if request.method == "GET":
form = AdminModelConfirm()
return render(request, 'register.html', {"form": form})
form = AdminModelConfirm(request.POST)
if form.is_valid():
user_code = form.cleaned_data.pop('code')
code = request.session.get('email_code', '')
if code != user_code:
form.add_error("code", "验证码错误或过期")
return render(request, 'register.html', {"form": form})
form.save()
return redirect("/login/")
return render(request, 'register.html', {"form": form})
def email_code(request):
""" 发送邮箱验证码 """
email = request.GET.get('to_email')
captcha = "".join(random.sample(string.digits, k=4))
content = {
"email_code": captcha
}
html_content = render_to_string('email.html', content)
text_content = strip_tags(html_content)
email_object = EmailMultiAlternatives(
subject="您的验证码",
body=text_content,
from_email=f"xxx.com <{settings.DEFAULT_FROM_EMAIL}>",
to=[email],
)
email_object.attach_alternative(html_content, 'text/html')
result = email_object.send()
if result:
request.session["email_code"] = captcha
request.session.set_expiry(120)
return JsonResponse({"status": True})
return JsonResponse({"status": True})
urls.py(相关路由配置)
python
from django.urls import path
from app01.views import login
urlpatterns = [
path("login/",login.login),
path("image/code/",login.image_code),
path("register/",login.register),
path("email/code/",login.email_code),
]
下面的html中会导入bootstrap和jQuery,可以自行网上寻找对应版本,不想自己找的话我这里放一份,里面都有
链接: https://pan.baidu.com/s/12dDtwFhbkuSSJfzAnLXDhQ?pwd=0916 提取码: 0916
login.html
html
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<link rel="stylesheet" href="{% static '/plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<script src="{% static '/plugins/jQuery/jquery-3.7.1.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
</head>
<body>
<div class="account">
<h2>用户登录</h2>
<form method="post" novalidate id="loginForm">
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
{{ form.code }}
<span style="color: red;">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
<img id="image_code" src="/image/code/" style="width: 125px;">
<a href="javascript:void(0);" onclick="refresh()" style="position: absolute">看不清?点击刷新</a>
</div>
</div>
</div>
<input type="submit" value="登 录" class="btn btn-primary">
<a href="/register/" style="margin-left: 20px">没有账户?点击注册</a>
</form>
</div>
<script type="text/javascript">
function refresh(){
sessionStorage.setItem('username',$("#id_username").val())
sessionStorage.setItem('password',$("#id_password").val())
location.reload()
}
$(function(){
var username = sessionStorage['username']
var password = sessionStorage['password']
if (username){
$("#id_username").val(username)
sessionStorage.removeItem('username')
}
if (password){
$("#id_password").val(password)
sessionStorage.removeItem('password')
}
})
</script>
</body>
</html>
register.html
html
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<link rel="stylesheet" href="{% static '/plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<script src="{% static '/plugins/jQuery/jquery-3.7.1.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
.btn-countdown {
background-color: #337ab7;
color: white;
transition: all 0.3s;
}
.btn-countdown:hover:not(:disabled) {
background-color: #286090;
}
.btn-countdown:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="account">
<h2>用户注册</h2>
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>邮箱号</label>
{{ form.email }}
<span style="color: red;">{{ form.email.errors.0 }}</span>
</div>
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.username.errors.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.password.errors.0 }}</span>
</div>
<div class="form-group">
<label>确认密码</label>
{{ form.confirm_password }}
<span style="color: red;">{{ form.confirm_password.errors.0 }}</span>
</div>
<div class="form-group">
<label for="id_email">邮箱验证码</label>
<div class="row">
<div class="col-xs-7">
<input class="form-control" name="code" placeholder="请输入验证码">
<span style="color: red;">{{ form.code.errors.0 }}</span>
</div>
<div class="col-xs-5">
<button type="button" class="btn btn-default btn-countdown" id="id_emailCode">发送验证码</button>
</div>
</div>
</div>
<input type="submit" value="注 册" class="btn btn-primary">
</form>
</div>
<script type="text/javascript">
$(document).ready(function () {
function startCountdown(seconds) {
let countdown = seconds;
const $button = $("#id_emailCode");
const originalText = $button.text();
$button.prop("disabled", true);
const countdownInterval = setInterval(function () {
$button.text(`重新发送(${countdown}秒)`);
countdown--;
if (countdown < 0) {
clearInterval(countdownInterval);
$button.text(originalText);
$button.prop("disabled", false);
}
}, 1000);
}
$("#id_emailCode").click(function () {
$.ajax({
url: "/email/code/",
type: "GET",
data: {
to_email: $("#id_email").val()
},
dataType: "json",
success: function (req) {
if (req.status) {
startCountdown(120);
}else{
alert("发送失败,请检查或稍后重试")
}
}
});
});
});
</script>
</body>
</html>