BBS仿博客园代码总结1---注册、登录功能详解
项目流程
仿造博客园项目的核心:文章的增删改查
--------------------------------------------
表关系分析:(项目的重中之重环节,表分析好了,后面的业务代码就好写了)
先确定表的数量 再确定表的基础字段 最后确定表的外键字段
--------------------------------------------
表数量分析
1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.点赞点踩表
7.文章评论表
--------------------------------------------
--------------------------------------------
表字段分析
用户表
替换auth_user表并扩展额外的字段
电话号码、头像、注册时间
个人站点表
站点名称(jason\lili\kevin)
站点标题(努力奋斗去他妹的)
站点样式(css文件)
文章表
文章标题
文章简介
文章内容
发布时间
文章分类表
分类名称
文章标签表
标签名称
点赞点踩表:记录哪个用户给哪篇文章点了推荐(赞)还是反对(踩)
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
点赞点踩
文章评论表:记录哪个用户给哪篇文章评论了什么内容
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
评论内容
评论时间
外键字段(自关联)
--------------------------------------------
表的外键字段分析:
用户表
用户与个人站点是一对一外键关系
个人站点表
文章表
文章表与个人站点表是一对多外键关系
文章表与文章分类表是一对多外键关系
文章表与文章标签表是多对多外键关系
文章评论数
文章点赞数
文章点踩数
'''
数据库字段优化设计:我们想统计文章的评论数 点赞数
通过文章数据跨表查询到文章评论表中对应的数据统计即可
但是文章需要频繁的展示 每次都跨表查询的话效率极低
我们在文章表中再创建三个普通字段
之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三 个普通字段即可
'''
文章分类表
文章分类与个人站点是一对多外键关系
文章标签表
文章标签与个人站点是一对多外键关系
bbs仿博客园项目表关系分析
个人站点和文章分类与文章标签也有一对多的外键关系!!!
.
.
.
注册功能
用户注册
1.渲染前端标签
2.校验用户数据
3.展示错误提示
ps:forms组件、modelform组件
单独开设py文件编写 解耦合!!!
.
.
.
准备工作
--------------------------------------------
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
AUTH_USER_MODEL = 'app01.UserInfo'
--------------------------------------------
# 注册
path('register/', views.register_func),
--------------------------------------------
.
模型层所有的表,该bbs项目的关键中的关键!!!表建不好,什么业务都不好写!!!
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""用户表"""
phone = models.BigIntegerField(verbose_name='手机号', null=True, blank=True) # blank参数用于控制admin后台管理与数据库无关
# FileField文件对象字段类型,传文件对象,自动保存到提前配置好的路径下并存储该路径信息!!
avatar = models.FileField(upload_to='avatar/', default='avatar/1111.jpg', verbose_name='用户头像')
register_time = models.DateTimeField(verbose_name='注册事件', auto_now_add=True)
# 外键一对一字段(#千万别建成一对多)
site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True) # 表名开头别小写了
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '用户表'
def __str__(self):
return f'用户对象<*>{self.username}'
class Site(models.Model):
"""个人站点表"""
site_name = models.CharField(verbose_name='站点名称', max_length=32)
site_title = models.CharField(verbose_name='站点标题', max_length=32)
site_theme_css = models.TextField(verbose_name='站点样式', null=True)
site_theme_js = models.TextField(verbose_name='站点样式', null=True)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '个人站点表'
def __str__(self):
return f'个人站点对象<*>{self.site_name}'
class Article(models.Model):
"""文章表"""
title = models.CharField(verbose_name='文章标题', max_length=32)
desc = models.CharField(verbose_name='文章简介', max_length=255)
content = models.TextField(verbose_name='文章内容')
create_time = models.DateTimeField(verbose_name='创建事件', auto_now_add=True)
# 三个优化字段
comment_num = models.IntegerField(verbose_name='评论数', default=0)
up_num = models.IntegerField(verbose_name='点赞数', default=0)
down_num = models.IntegerField(verbose_name='点踩数', default=0)
# 外键一对多字段 一个站点对多个文章,一个分类对多个文章
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
category = models.ForeignKey(to='Category', on_delete=models.CASCADE, null=True)
# 外键多对多字段 一个文章可以有多个标签,一个标签可以有多个文章,外键建在查询频率高的表里面
tags = models.ManyToManyField(to='Tag',
through='Article2Tag',
through_fields=('article', 'tag')
)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '文章表'
def __str__(self):
return f'文章对象<*>{self.title}'
class Article2Tag(models.Model):
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
# 外键一对多字段
tag = models.ForeignKey(to='Tag', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '文章与标签多对多表'
class Category(models.Model):
"""文章分类表"""
name = models.CharField(verbose_name='分类名称', max_length=32)
# 外键一对多字段
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '文章分类表'
def __str__(self):
return f'文章分类对象<*>{self.name}'
class Tag(models.Model):
"""文章标签表"""
name = models.CharField(verbose_name='标签名称', max_length=32)
# 外键一对多字段
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '文章标签表'
def __str__(self):
return f'标签对象<*>{self.name}'
class UpAndDown(models.Model):
"""点赞点踩表"""
up_or_down = models.BooleanField(verbose_name='点赞点踩') # 传布尔值 存0或1
# 外键一对多字段
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '点赞点踩表'
class Comment(models.Model):
"""文章评论表"""
content = models.TextField(verbose_name='评论内容')
comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
# 外键一对多字段
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
# 自关联字段
parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '文章评论表'
------------------------------------------
表创好了后,mysql创个数据库,settings里面改一下
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '20230113bbs',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '222',
'CHARSET': 'utf8'
}
}
.
.
注册相关代码
注册代码的思路:
首先就是要利用django提供的forms类来进行input标签的渲染与数据的校验
所以要先写好注册的forms类 class RegisterForm(forms.Form):
别忘了局部钩子与全局钩子函数的编写
--------------------------------
当后端将前端发送过来的字典数据通过request.POST拿到后,交给我们自己定义的forms类进行校验,
只要当数据符合字段与正则的要求后,就放到cleaned_data这个字典里面去。
注意钩子函数的触发要满足的前提是,数据符合字段参数的校验与正则的校验后,才能走到触发钩子函数的这一步。
如果字段参数的校验与正则的校验没有问题后,触发钩子函数的运行,如果不符合钩子函数的逻辑校验,通过self.add_error('字段名','错误信息') 给对应字段的数据,添加错误信息
----------------------------------
forms类写好后,要先在后端生成个空对象并传到前端页面,渲染出标签
所以开始写前端页面
首先如果forms类渲染出的input标签很多,那么我们ajax在发送数据的时候要按id一个一个查找input框并获取value值,太繁琐了,所以利用form表单标签的序列化功能,可以一次性拿到所有form标签里面的所有input框里面的数据,但是不要form表单提交请求的功能,所以form标签删掉action="" 加个id属性
------------------------------------
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.auto_id }}">{{ form.label }}</label>
{{ form }}
<span style="color: red"></span>
</div>
{% endfor %}
------------------------------------
用户头像的标签就自己写,不用forms类渲染了
<div class="form-group">
<label for="myFile">用户头像
<img src="/static/img/default.jpg/" id="myImg" alt="" style="width: 100px">
</label>
<input type="file" id="myFile" style="display: none">
</div>
------------------------------------
最后就是重点的3个事件了
第一个就是文本域变化事件,当头像的input框里面选择过头像图片后,能够实时的展示到页面上的图片标签上
$('#myFile').change(function () {}
先产生一个文件阅读器对象,再将该input框的标签对象转文件对象,拿到用户选择的头像文件
将头像文件交给阅读器对象,等待阅读器对象读完后,再修改img标签的src的属性展示选择的图片
$('#myImg').attr('src', myFileReaderObj.result)
第二个就是点击注册按钮的点击事件,因为要发头像文件的数据给后端,
所以要先 let myFormDataObj = new formdata() 产生一个空对象
然后就是利用form表单serializeArray()一次性获取里面的input标签普通的数据,往空对象里面添加,头像文件也往空对象里面添加,最后发送ajax请求,注意发送文件数据别忘了
contentType : false 和processData :false 不编码也不做任何处理
回调函数success里面的操作下面图片区处有讲解
第三个就是获取焦点事件了,当用户输入的信息有误,并被我们渲染在页面上时,我们需要在用户再次点击输入框的时候,将错误提示信息与输入框的错误样式去掉
$('input').focus(function () {
// 事件函数中的this关键字指代的就是当前被操作的标签对象本身
$(this).next().text('').parent().removeClass('has-error')
})
------------------------------------
最后就是后端的逻辑判断了
先request.POST获取普通数据,然后交给forms类校验,
然后调用form_obj.is_valid()校验方法完成数据的校验,
用一个变量名接收一下form_obj.cleaned_data里面的符合校验规则的字典数据
然后再去掉字典里面的confirm_password的键对应的值,
再把request.FILES.get('avatar')获取到的头像文件,以键'avatar'添加到字典里面去
最后创建用户数据create_user比create更强,可以给密码加密写到数据库
**将字典打散变成关键字参数的形式写到数据库去
models.UserInfo.objects.create_user(**cleaned_data)
最后把一些小的逻辑判断补一下,注册就结束了
myforms.py 里面代码
from django import forms
from django.core.validators import RegexValidator
from app01 import models
class RegisterForm(forms.Form):
"""注册forms类"""
username = forms.CharField(max_length=9, min_length=3,
label='用户名',
error_messages={
'max_length': '最长不能超过9位',
'min_length': '最短不低于3位',
'required': '用户名不能位空'
},
widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(max_length=9, min_length=3,
label='密码',
error_messages={
'max_length': '最长不能超过9位',
'min_length': '最短不低于3位',
'required': '用户名不能位空'
},
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
confirm_password = forms.CharField(max_length=9, min_length=3,
label='确认密码',
error_messages={
'max_length': '最长不能超过9位',
'min_length': '最短不低于3位',
'required': '用户名不能位空'
},
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
phone = forms.CharField(label='手机号码',
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^1(3[0-9]|4[0-9]|5[0-9])\d{8}$', '号码不符合要求')
],
widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
email = forms.EmailField(label='邮箱',
error_messages={
'required': '邮箱不能为空',
'invalid': '邮箱格式不正确'
},
widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
)
# 局部钩子
def clean_username(self):
username = self.cleaned_data.get('username')
res = models.UserInfo.objects.filter(username=username).first()
if res:
self.add_error('username', '用户名已存在')
return username
# 全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
self.add_error('confirm_password', '两次密码不一致')
return self.cleaned_data
----------------------------------
.
.
视图层代码
def register_func(request):
# 前后端ajax交互 通常采用字段作为交互对象
back_dict = {'code': 10000, 'msg': ''}
# 1.先产生一个空的form_obj,该空对象是用来被locals()传到前端渲染出标签的
form_obj = myforms.RegisterForm()
# 整体是前端传回来的数据,进行逻辑判断与处理,并将字典信息传给ajax的异步回调函数
if request.method == 'POST':
form_obj = myforms.RegisterForm(request.POST) # 正常的5个,还有一个csrfmiddlewaretoken
if form_obj.is_valid():
clean_data = form_obj.cleaned_data # 存储符合校验的数据,只有正常的5个键值对数据了
# 将confirm_password键值对移除
clean_data.pop('confirm_password') # 只有正常的4个键值对数据了
# 获取用户上传的头像数据
avatar_obj = request.FILES.get('avatar') # 用户有可能没有上传
if avatar_obj:
clean_data['avatar'] = avatar_obj # 如果头像存在,将文件对象添加到clean_data里面去
# 创建用户数据
models.UserInfo.objects.create_user(**clean_data) # 上述处理字典的目的就是为了创建数据省事
back_dict['msg'] = '注册成功'
back_dict['url'] = '/app01/login/'
else:
back_dict['code'] = 10001
back_dict['msg'] = form_obj.errors
return JsonResponse(back_dict)
return render(request, 'registerPage.html', locals())
.
.
模板层代码
# 注册的html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/static/jQuery3.6.js"></script>
<link rel="stylesheet" href="/static/bootstrap-3.4.1-dist/css/bootstrap.min.css">
<script src='/static/bootstrap-3.4.1-dist/js/bootstrap.min.js'></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col_md-set-2">
<h2 class="text-center">用户注册</h2>
<form id="form">
<!--不使用form表单提交数据,但是用form标签 它有一个序列化的功能-->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<!--目的就是让多个获取用户的标签上下间距更大一些-->
<!--双括号form.auto_id的意思是:自动获取下面标签的id值,现在label标签和input框绑定了,点击字段名也将会聚焦到框里面去了-->
<label for="{{ form.auto_id }}">{{ form.label }}</label>
<!--拿到字段对应的中文名称-->
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<!--用户头像,自己编写相关标签获取头像-->
<div class="form-group">
<label for="myfile">头像
<img src="/static1/img/default.jpg" alt="" width="120" id="myimg" style="margin-left: 30px"> <!--把图片放到label里面,这样点图片就会触发input框了-->
</label>
<input type="file" id="myfile" style="display: none">
</div>
<input type="button" id="subBtn" class="btn btn-primary btn-block" value="注册">
</form>
</div>
</div>
</div>
<script>
// 用户头像的实时展示,给标签绑定一个域内容变化
$('#myfile').change(function () {
// 1. 产生一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2. 获取用户上传的头像文件
let fileObj = this.files[0] // 事件函数中的this关键字指代的就是当前被操作的标签对象本身
// 3. 将文件对象交给阅读器
myFileReaderObj.readAsDataURL(fileObj); // 异步操作
// 5. 需要等待文件阅读器对象加载完毕之后再修改src
myFileReaderObj.onload = function (){
// 4. 修改img标签的src属性展示图片
$('#myimg').attr('src',myFileReaderObj.result)
// attr() 相当于js代码 setAttribute() 给标签添加默认属性或自定义属性
}
})
// 给注册按钮绑定点击事件 发送ajax请求 携带了文件数据
$('#subBtn').click(function () {
// 1. 先用js代码产生一个内置对象,帮我们携带文件数据
let myFormDataObj = new FormData();
// 2. myFormDataObj.append('username',$('#id_username')) 单个单个添加普通数据效率太低
{#console.log($('#form').serializeArray()) // 可以一次性获取form标签内所有普通字段数据[{},{},{}]#}
{# 对结果for循环,然后交给后面的函数处理,行参index接收的是索引,行参dataObj接收到的是对象 #}
$.each($('#form').serializeArray(),function (index,dataObj) {
myFormDataObj.append(dataObj.name,dataObj.value)
})
// 3. 添加头像文件数据
myFormDataObj.append('avatar',$('#myfile')[0].files[0])
// 4. 发送ajax请求
$.ajax({
url:'',
type:'post',
data:myFormDataObj,
contentType:false,
processData:false,
success:function (args) {
if (args.code===10000){
window.location.href = args.url // 注册成功跳转登录路由
}else{
// args.msg是一个自定义对象,就是个大字典,里面字段名是键,错误提示是值
// 如何针对性的渲染错误提示 {'username':[错误提示]}
// 我们发现forms类渲染出来的input框标签的id为 id_对应的字段名
$.each(args.msg,function (k,msgArray) {
// 拼接标签的id值
let eleId = '#id_' + k
// 根据id查找标签 修改下面span标签的内容 并给父标签添加错误样式
$(eleId).next().text(msgArray[0]).parent().addClass('has-error')
})
}
}
})
})
// 给所有的input标签绑定获取焦点事件 移除错误样式
$('input').focus(function () {
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
---------------------------------------------
$('#myForm').serializeArray() 一次性获取form标签内的所有普通字段数据,拿到一个列表套字典的数据,每一个字典里面是一个索引为键,值又是一个一个字典的结构。最里面的字典里面才是name对应的是字段名,value对应的是用户写在对应的input框里面的数据
.
.
.
.
登录页面啥都不写,前端console.log(args.msg)的结果
每个字典里面键是字段名,值是被列表套着的错误的信息
那么我们如何将这些错误的信息渲染到对应的input标签下面的span标签里面去了?
我们发现forms组件渲染出来的input标签的id就是对的id_字段名
这样我们只需要将字段名前面拼接出#id_ 那么我们就可以利用拼接后的值来查找到对应的input标签,再.next()就找到对应的下面的span标签了
再点parent()就能找到input标签的父标签了
这个地方因为直接给input标签添加样式类没效果,所以给包裹它的父标签加了
.
.
.
.
.
.
.
.
.
.
.
.
登录功能
img标签的src属性
1.可以直接填写图片地址
2.还可以填写一个路由 会自动朝该路由发送get请求
如果结果是图片的二进制数据 那么自动渲染图片
pip install pillow -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
下载pillow模块时,如果提示不信任源复制最后代码 --trusted-host mirrors.aliyun.com
空一格,粘贴到原来的代码后面,把整行代码到Terminal里面粘贴一下,pip后面加个3.8 区分不同的解释器的pip文件
pip3.8 install --user --index-url http://mirrors.aliyun.com/pypi/simple/ pillow --trusted-host mirrors.aliyun.com
.
.
.
登录相关代码
登录的难点就在获取验证码那边,先用get_random()函数获取一个元组包含3个0-255的随机数用来控制生成的图片对象的背景颜色
将图片对象交给画笔对象。
再生成一个字体样式对象。
利用random模块与range以及chr()与str()方法产生随机大小写字母与数字的字符,并每次随机取一个
最后利用画笔对象将字符画到图片上面去
draw_obj.text(位置、字符、字符大小)
最后for循环完,画笔以及在图片上画完所有的字符后,利用内存管理器对象将图片对象保存到内存中,最后再从内存中取出该图片通过HttpResponse返回给前端页面
io_obj = BytesIO()
img_obj.save(io_obj, 'png')
return HttpResponse(io_obj.getvalue())
-------------------------------------
登录视图函数的逻辑判断
获取从前端ajax发送过来的post请求数据,拿到用户输入的用户名、密码、验证码。
先判断验证码是否正确,由于用户输入的验证码可能不一定区分大小写字母,所以统一将用户的验证码统一转成大写或小写字母,然后将存在session表里面的验证码也统一转换成大写或小写,进行比对。
再利用auth.authenticate()判断用户名与密码是否正确,如果正确一定不要忘了auth.login(request, user_obj) 确认登录成功,保存用户的登录状态,将用户对象记到session表里!!
最后将不符合的情况逻辑补全,并补全不同情况下的字典收据,通过JsonResponse传给前端ajax的异步回调函数处理!!!
-------------------------------------
前端页面代码的编写要点:
首先把用户登录要输入的input框标签都写好
再弄一个button的登录按钮
最后来两个点击事件,
一个是点击a标签的文字,更换随机验证码,注意要用return false取消掉a标签的跳转功能。
第二个就是点击提交按钮,用ajax将前端标签框里面的数据,发送给后端,并将后端传过来的数据用异步回调函数处理,因为登录要输入的数据不多,所以可以直接利用if与elif判断code码来给对应的input框下面的空span标签渲染内容,或者简单点直接利用alert(args.msg)将错误信息以弹出框的形式弹出来
path('login/', views.login_func, name='login_view'),
# 图片验证码相关功能
path('get_code/', views.get_code_func),
---------------------------------------------
# 视图层代码
def login_func(request):
back_dict = {'code': 10000, 'msg': ''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
print(username, password)
code = request.POST.get('code')
# 先判断验证码忽略大小写,再判断用户名与密码
if code.upper() == request.session.get('code').upper():
user_obj = auth.authenticate(request, username=username, password=password)
if user_obj:
# 登录成功后,保存用户的登录状态,将用户对象记到session表里,这句话执行成功,就可以使用request.user获取登录用户对象
auth.login(request, user_obj)
back_dict['msg'] = '登录成功'
back_dict['url'] = '/app01/home/'
else:
back_dict['msg'] = '用户名或密码不正确'
back_dict['code'] = 10001
else:
back_dict['code'] = 10002
back_dict['msg'] = '验证码不正确'
return JsonResponse(back_dict)
return render(request, 'loginPage.html')
---------------------------------------------
from PIL import Image, ImageFont, ImageDraw
from io import BytesIO, StringIO
"""
Image 产生图片
ImageFont 字体样式
ImageDraw 画笔对象
BytesIO 在内存中临时存储 读取的时侯以bytes格式为准
StringIO 在内存中临时存储 读取的时侯以字符串格式为准
"""
import random
def get_random():
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
def get_code_func(request):
# 1.推导步骤1: 直接读取图片文件返回
# with open(r'D:\pythonProject\djangoBBS\app01\avatar\3333.jpg','rb') as f:
# data = f.read()
# return HttpResponse(data)
----------------------------------
# 2.推导步骤2:随机产生图片动态返回 pillow模块
# img_obj = Image.new('RGB', (350, 35), 'blue') # 产生一个宽350,高35,红色的图片
# with open(r'XXX.png', 'wb') as f:
# img_obj.save(f, 'png')
# with open(r'xxx.png', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
----------------------------------
# 3. 推导步骤3:针对图片的保存与读取做优化,内存管理
# img_obj = Image.new('RGB', (350, 35), 'red')
# io_obj = BytesIO() 产生一个内存管理器对象
# img_obj.save(io_obj,'png') 把图片对象通过内存管理器对象以png格式存到内存里面
# return HttpResponse(io_obj.getvalue())
# 通过内存管理器对象从内存里面拿出刚刚存进去的对象
----------------------------------
# 4. 推导步骤4:图片颜色可以随机变换
# img_obj = Image.new('RGB', (350, 35), get_random())
# io_obj = BytesIO()
# img_obj.save(io_obj, 'png')
# return HttpResponse(io_obj.getvalue())
----------------------------------
# 5. 推导步骤5:编写验证码
img_obj = Image.new('RGB', (350, 35), get_random())
draw_obj = ImageDraw.Draw(img_obj) # 将图片对象交给画笔对象
font_obj = ImageFont.truetype('static/font/111.ttf', 35) # 确定字体样式(ttf文件与字体尺寸)
# 产生随机验证码
code = ''
for i in range(5):
random_upper = chr(random.randint(65, 90))
random_lower = chr(random.randint(97, 122))
random_int = str(random.randint(1, 9))
# 三选一
temp_choice = random.choice([random_upper, random_lower, random_int])
# 元组(i * 45 + 45, 0) 第一个参数代表在X轴的位置 第二个产生代表在y轴的位置 所以每次循环写在x轴的位置都不一样
draw_obj.text((i * 45 + 45, 0), temp_choice, font=font_obj) # text(水平与竖直位置、字符、字符大小)
code += temp_choice
# 后端保存验证码 便于后续的比对
request.session['code'] = code # 也可以在全局定义一个code变量,然后在函数里面先申明为global变量,这样在函数里就可改全局变量了
io_obj = BytesIO()
img_obj.save(io_obj, 'png')
return HttpResponse(io_obj.getvalue())
---------------------------------------------
.
.
.
登录的模板层代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/static/jQuery3.6.js"></script>
<link rel="stylesheet" href="/static/bootstrap-3.4.1-dist/css/bootstrap.min.css">
<script src='/static/bootstrap-3.4.1-dist/js/bootstrap.min.js'></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center">用户登录</h2>
<div class="form-group">
<label for="">用户名</label>
<input type="text" class="form-control" id="username">
</div>
<div class="form-group">
<label for="">密码</label>
<input type="text" class="form-control" id="password">
</div>
<!--验证码-->
<div class="form-group">
<label for="code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="code" style="height: 55px">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" width="350" height="35" id="d111">
<h5 class="text-center" id="v111"><a href="">看不清换一张</a></h5>
</div>
</div>
</div>
<input type="button" id="subBtn" class="btn btn-block btn-primary" value="用户登录">
</form>
</div>
</div>
</div>
<script>
// 换验证码 点击事件
$('#v111').click(function () {
let oldSrc = $('#d111').attr('src')
$('#d111').attr('src', oldSrc + '?')
return false // 取消a标签的跳转功能
})
// 登录按钮点击事件
$('#subBtn').click(function () {
$.ajax({
url: '',
type: 'post',
data: {
'username': $('#username').val(),
'password': $('#password').val(),
'code': $('#code').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
if (args.code === 10000) {
window.location.href = args.url
} else {
alert(args.msg)
}
}
})
})
</script>
</body>
</html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY