BBS项目(三)
注册forms类编写局部钩子和全局钩子
代码语言:javascript复制'''forms校验'''
from django import forms
from django.forms import widgets
from blog import models
from django.core.exceptions import ValidationError
'''注册校验,写入样式'''
class RegisterForm(forms.Form):
# 名称校验
username = forms.CharField(
max_length=18, min_length=3,label='用户名',
error_messages={'required': '该字段必填', 'max_length': '名字过长,不能超过18个字符',
'min_length': '名字过短,不能少于三个字符'},
widget=widgets.TextInput(attrs={'class': 'form-control'})
)
# 密码校验
password = forms.CharField(
max_length=18, min_length=3,label='密码',
error_messages={'required': '该字段必填', 'max_length': '密码过长,不能超过18个字符',
'min_length': '密码过短,不能少于三个字符'},
widget=widgets.PasswordInput(attrs={'class': 'form-control'})
)
re_password = forms.CharField(
max_length=18, min_length=3,label='确认密码',
error_messages={'required': '该字段必填', 'max_length': '密码过长,不能超过18个字符',
'min_length': '密码过短,不能少于三个字符'},
widget=widgets.PasswordInput(attrs={'class': 'form-control'})
)
# 邮箱
email = forms.EmailField(
error_messages={'required': '该字段必填', 'invalid': '邮箱格式不正确'},label='邮箱',
widget=widgets.EmailInput(attrs={'class': 'form-control'})
)
# 局部钩子,校验用户是否存在
def clean_username(self):
username = self.cleaned_data.get('username')
# 拿到数据判断用户在不在表中
user = models.UserInfo.objects.filter(username=username).first()
if user:
# 用户存在抛异常
raise ValidationError('该用户以存在')
else:
return username
# 全局钩子,校验密码是否一致
def clean(self):
password = self.cleaned_data.get('password')
re_password = self.cleaned_data.get('re_password')
if not password == re_password:
raise ValidationError('两次密码不一致')
注册功能前端
代码语言:javascript复制# 发送ajax请求,使用的Formdata
#form标签.serializeArray()
# 整体代码
$('#id_submit').click(function () {
let formdata = new FormData()
formdata.append('myfile', $('#myfile')[0].files[0])
//方案一
/*
formdata.append('username',$('#id_username').val())
formdata.append('password',$('#password').val())
formdata.append('re_password',$('#id_re_password').val())
formdata.append('email',$('#id_email').val())
*/
//方案二
let form_data = $('#my_form').serializeArray()
//console.log(form_data)
$.each(form_data, function (index, element) {
//console.log(index)
//console.log(element)
formdata.append(element.name, element.value)
})
//console.log(formdata.get('username'))
$.ajax({
url: '/register/',
method: 'post',
contentType: false,
processData: false,
data: formdata,
success: function (data) {
console.log(data)
if (data.status == 100) {
location.href = data.next_url
//location.href='/login/'
} else {
$.each(data.msg, function (key, value) {
//console.log('#id_' key)
if (key == '__all__') {
$('#id_error').html(value[0])
} else {
//取到input标签的下一个
//$('#id_' key).next().html(value[0])
//链式调用
//$('#id_' key).parent().addClass('has-error')
//链式调用
$('#id_' key).next().html(value[0]).parent().addClass('has-error')
}
})
//加了一个定时器,3s以后干某些事
setTimeout(function () {
//清除红色框
$('.form-group').removeClass('has-error')
//清空所有错误信息
$('.error').html('')
}, 3000)
}
}
})
})
注册功能后端
代码语言:javascript复制def register(request):
if request.method == 'GET':
register_form = RegisterForm()
return render(request, 'register.html', context={'form': register_form})
elif request.method == 'POST':
response = {'status': 100, 'msg': None}
register_form = RegisterForm(request.POST)
if register_form.is_valid():
# 数据校验通过
# 可能传头像,可能没传头像
clean_data=register_form.cleaned_data
print(clean_data)
my_file=request.FILES.get('myfile')
if my_file: # 传了头像
# FileField字段类型直接接受一个文件对象,
# 它会把文件存到upload_to='avatar/',然后把路径存到数据库中
# 相当于with open 打开文件,把文件存到avatar路径下,把路径赋值给avatar这个字段
clean_data['avatar']=my_file
clean_data.pop('re_password')
models.UserInfo.objects.create_user(**clean_data)
response['msg']='恭喜你,注册成功'
response['next_url']='/login/'
else:
response['status']=101
response['msg'] = register_form.errors
return JsonResponse(response)
注册功能前端错误渲染
代码语言:javascript复制success: function (data) {
console.log(data)
if (data.status == 100) {
location.href = data.next_url
} else {
$.each(data.msg, function (key, value) {
if (key == '__all__') {
$('#id_error').html(value[0])
} else {
$('#id_' key).next().html(value[0]).parent().addClass('has-error')
}
})
setTimeout(function () {
//清除红色框
$('.form-group').removeClass('has-error')
//清空所有错误信息
$('.error').html('')
}, 3000)
}
}
登录页面搭建
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">登录功能</h3>
</div>
<div class="panel-body">
<form id="my_form">
{% csrf_token %}
<div class="form-group">
<label for="">用户名</label>
<input type="text" id="id_username" class="form-control">
<span class="danger pull-right error"></span>
</div>
<div class="form-group">
<label for="">密码</label>
<input type="text" id="id_password" class="form-control">
<span class="danger pull-right error"></span>
</div>
<div class="form-group">
<label for="">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="id_code" class="form-control">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" height="35px" width="300px">
</div>
</div>
</div>
<div class="text-center">
<input type="button" value="登录" class="btn btn-warning" id="id_submit"><span
class="danger error"
id="id_error"
style="margin-left: 10px"></span>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
验证码
- 生成验证码的模块:https://www.jb51.net/article/153863.html
- 集成第三方,极验滑动验证,腾讯验证码(sdk)
- 手写验证码
'''手写验证码模板'''
def get_random():
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
def get_code(request):
# 最终方案
# img = Image.new('RGB', (300, 30), get_random())
img = Image.new('RGB', (250, 30), (250, 250, 250))
# 第一个参数,是文字格式的文件,ttf格式,第二个参数是文字大小
img_font = ImageFont.truetype('./static/font/ss.TTF', 20)
# 拿到一个画板对象,把图片放到画板上,指定写的字的字体是什么
img_draw = ImageDraw.Draw(img)
# 在画板上写文字
# 随机生成5位 小写字母,大写字母,和数字
code = ''
for i in range(5):
low_char = chr(random.randint(97, 122))
up_char = chr(random.randint(65, 90))
number_char = str(random.randint(0, 9))
res = random.choice([low_char, up_char, number_char])
code = res
img_draw.text((20 i * 40, 0), res, fill=get_random(), font=img_font)
print(code)
request.session['code'] = code
# 画点和线
# 画线和点圈
width = 250
height = 30
for i in range(5):
x1 = random.randint(0, width)
x2 = random.randint(0, width)
y1 = random.randint(0, height)
y2 = random.randint(0, height)
# 在图片上画线
img_draw.line((x1, y1, x2, y2), fill=get_random())
for i in range(20):
# 画点
img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random())
x = random.randint(0, width)
y = random.randint(0, height)
# 画弧形
img_draw.arc((x, y, x 4, y 4), 0, 90, fill=get_random())
bytes_io = BytesIO()
img.save(bytes_io, 'png') # 写到内存中,需要传format,图片格式
return HttpResponse(bytes_io.getvalue()) # 把内容读出来
点击刷新验证码
代码语言:javascript复制$('#id_img').click(function () {
let img_url = $('#id_img')[0].src
$('#id_img')[0].src = img_url '?'
})
登录功能前后端
代码语言:javascript复制前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<script src="/static/element/jQuery3.4.js"></script>
<script src="/static/element/bootstrap.min.js"></script>
<link rel="stylesheet" href="/static/element/bootstrap.min.css">
<style>
.danger {
color: tomato;
}
</style>
</head>
<body>
<div class="container">
<div class="row" style="margin-top: 15px">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">登录</h3>
</div>
<div class="panel-body">
<form action="" id='my_form' method="post">
{% csrf_token %}
{# 用户名、密码、验证码 #}
<div class="form-group">
{# 后端blog_forms.py获取label #}
{# item.auto_id获取当前id自动聚焦 #}
<label for="{{ item.auto_id }}">用户名</label>
<input type="text" id="id_username" class="form-control">
<span class="danger pull-right error"></span>
</div>
<div class="form-group">
{# 后端blog_forms.py获取label #}
{# item.auto_id获取当前id自动聚焦 #}
<label for="{{ item.auto_id }}">密码</label>
<input type="password" id="id_password" class="form-control">
<span class="danger pull-right error"></span>
</div>
<div class="form-group">
<label for="{{ item.auto_id }}">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="id_code" class="form-control">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="验证码图片" height="35px" width="300px" id="id_imgcode">
</div>
</div>
</div>
{# 提交,使用form表单,类型要写成button,如果是submit的话会触发表单提交 #}
<div class="text-center">
<input type="button" class="btn btn-info" id="id_submit" value="登录">
<a href="/admin/" class="btn btn-warning table-hover">退出</a>
<span class="danger error" id="id_error" style="margin-left: 20px"></span>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
{# 点击变图,发送不同请求 #}
$('#id_imgcode').click(function () {
var img_url = $('#id_imgcode')[0].src
$('#id_imgcode')[0].src = img_url '?' Math.floor(Math.random() * 100)
})
{#提交数据#}
$('#id_submit').click(function () {
$.ajax({
url: '/login/',
method: 'post',
data: {
username: $('#id_username').val(),
password: $('#id_password').val(),
code: $('#id_code').val(),
csrfmiddlewaretoken: '{{csrf_token}}',
},
success: function (data) {
if (data.status == 100) {
{#location.href='/index/'#}
location.href = data.url
} else {
$('#id_error').html(data.msg)
{#alert($('#id_error').html(data.msg)[0].innerText)#}
setTimeout(function (){
$('.error').html('')
},3000)
}
},
})
})
</script>
</html>
代码语言:javascript复制后端
# 验证码
def get_code(request):
# width = 300
# height = 30
# image = Image.new('RGB', (width, height), (255, 255, 255))
#
# with open('code.png','wb')as code_f:
# image.save(code_f)
# with open('./code.png','rb')as f:
# res = f.read()
# return HttpResponse(res)
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
# 随机字母:
def rndChar():
return chr(random.randint(65, 90))
# 随机颜色1:
def rndColor():
return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))
# 随机颜色2:
def rndColor2():
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))
width = 300
height = 50
image = Image.new('RGB', (width, height), (255, 255, 255))
# 创建Font对象:
# 创建ttf格式文件
font = ImageFont.truetype(r'E:BBSstaticcodearial.ttf', 36)
# 创建Draw对象:
draw = ImageDraw.Draw(image)
# 填充每个像素:
for x in range(width):
for y in range(height):
draw.point((x, y), fill=rndColor())
# 输出文字:
code = ''
for t in range(5):
'''如果想使用数字和大小写字母拼接的验证码可以用chr(),用ASCII,然后随机获取'''
res = rndChar()
code = res
draw.text((60 * t 10, 10), res, font=font, fill=rndColor2())
print(code)
'''将验证码写入code,需要注意的是,如果同一个浏览器打开两个页面,那么原来的session_data就会被覆盖掉,如果打开另外一个浏览器就会重新生成新的session_data记录'''
request.session['code'] = code
# 模糊:
# image = image.filter(ImageFilter.BLUR)
# 存入硬盘,读出
# image.save('code.jpg', 'jpeg')
# with open('./code.jpg', 'rb') as f:
# res = f.read()
# 存入内存,读出
bytes_io = BytesIO()
image.save(bytes_io, 'png')
return HttpResponse(bytes_io.getvalue())
# 登录
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
response = {'status': 100, 'msg': None}
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
if not code:
response['status'] = 101
response['msg'] = '验证码不能为空'
if code.lower() == request.session.get('code').lower():
user = authenticate(username=username, password=password)
if user:
auth.login(request,user)
response['msg'] = '登录成功'
response['url'] = '/index/'
else:
response['status'] = 102
response['msg'] = '用户名或密码错误'
else:
response['status'] = 101
response['msg'] = '验证码错误'
return JsonResponse(response)
首页页面搭建
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Blog-index</title>
<script src="/static/element/jQuery3.4.js"></script>
<script src="/static/element/bootstrap.min.js"></script>
<link rel="stylesheet" href="/static/element/bootstrap.min.css">
<style>
footer#footer {
padding-top: 32px;
padding-bottom: 32px;
display: flex;
flex-direction: column;
align-items: center;
color: #888;
background-color: #f3f3f3;
font-size: 13px;
font-weight: 400;
text-align: center;
}
footer {
display: block;
}
body {
font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
font-weight: normal;
background-color: #f9f9f9;
}
</style>
</head>
<body>
<div class="container-fluid">
{# 头部 #}
<div class="head">
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<li class="navbar-branding">
<a href="/index/" class="navbar-brand" title="开发者的网上家园" role="banner">
博客园
</a>
</li>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="/index/">首页 <span class="sr-only">(current)</span></a></li>
<li><a href="https://news.cnblogs.com/">新闻</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">发现 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="https://zzk.cnblogs.com/">找找看</a></li>
<li><a href="#">收藏</a></li>
<li><a href="https://www.lagou.com/">招聘</a></li>
<li role="separator" class="divider"></li>
<li><a href="/login/">个人园子</a></li>
</ul>
</li>
</ul>
{% if request.user.is_authenticated %}
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/">个人主页</a></li>
<li><a href="#">设置</a></li>
<li><a href="admin">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">退出</a></li>
</ul>
</li>
</ul>
{% else %}
<ul class="nav navbar-nav navbar-right">
<li><a href="/login/">登录</a></li>
<li><a href="/register/">注册</a></li>
</ul>
{% endif %}
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
{# 主体 #}
<div class="body row">
{# 左侧 #}
<div class="col-md-2">
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">Panel title</h3>
</div>
<div class="panel-body">
Panel content
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">Panel title</h3>
</div>
<div class="panel-body">
Panel content
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">Panel title</h3>
</div>
<div class="panel-body">
Panel content
</div>
</div>
</div>
{# 中间 #}
<div class="col-md-7">
<div class="lunbotu">
<div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
<!-- Indicators -->
<ol class="carousel-indicators">
<li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
<li data-target="#carousel-example-generic" data-slide-to="1"></li>
<li data-target="#carousel-example-generic" data-slide-to="2"></li>
</ol>
<!-- Wrapper for slides -->
<div class="carousel-inner" role="listbox">
{% for banner in banner_list %}
{% if forloop.first %}
<div class="item active">
<img src="{{ banner.url }}" alt="首页图">
{# <div class="carousel-caption">#}
{# {{ banner.name }}#}
{# </div>#}
</div>
{% else %}
<div class="item ">
<img src="{{ banner.url }}" alt="首页图">
</div>
{% endif %}
{% endfor %}
</div>
<!-- Controls -->
<a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
{# 文章 #}
<div class="article">
{% for article in article_list %}
{{ article.title }}
{% endfor %}
</div>
</div>
{# 右侧 #}
<div class="col-md-3">
<ul class="list-group">
<li class="list-group-item">Cras justo odio</li>
<li class="list-group-item">Dapibus ac facilisis in</li>
<li class="list-group-item">Morbi leo risus</li>
<li class="list-group-item">Porta ac consectetur ac</li>
<li class="list-group-item">Vestibulum at eros</li>
</ul>
</div>
</div>
{# 页脚 #}
<div>
<br><br><br><br><br><br><br><br><br><br>
<br><br><br><br><br><br><br><br><br><br>
</div>
<div class="footer text-center" style="background-color: rgb(243,243,243)">
<footer id="footer" class="footer">
<div id="friend_link" class="link-list friend-link">
友情链接:
<a href="//www.aliyun.com" target="_blank">阿里云</a>
<a href="//cloud.tencent.com" target="_blank">腾讯云</a>
<a href="//www.huaweicloud.com" target="_blank">华为云</a>
<a href="//cloud.baidu.com" target="_blank">百度云</a>
<a href="//www.jdcloud.com" target="_blank">京东云</a>
<a href="http://www.ucancode.com/" target="_blank">工控组态源码</a>
<a href="//www.shanhaibi.com/" target="_blank">山海鲸可视化</a>
<a href="http://www.gcpowertools.com.cn" target="_blank">葡萄城控件</a><a href="//www.chinaz.com/"
target="_blank">站长之家</a><a
href="http://dev.yesky.com" target="_blank">天极网</a><a href="//wetest.qq.com/?from=links_cnblogs"
target="_blank">腾讯WeTest</a>
</div>
<div class="footer-splitter"></div>
<div id="footer_bottom">
<div class="poweredby">Powered by .NET 6 on Kubernetes</div>
<div class="about"><a href="//about.cnblogs.com/">关于博客园</a><a
href="//about.cnblogs.com/contact">联系我们</a><a href="//about.cnblogs.com/ad">广告服务</a><a
href="//about.cnblogs.com/brandzone">专区合作</a><span>©2004-2022</span>
</div>
<div class="report-contact">举报电话:0571-88079867,举报邮箱:contact@cnblogs.com <a href="http://www.shjbzx.cn"
target="_blank"><img
src="/images/jblogo.png?v=20200730" alt=""></a></div>
</div>
</footer>
</div>
</div>
</body>
</html>