目录
一、登陆功能完善
验证码功能实现
ps:
1.使用pillow模块我们需要用一个ttf文件来设置字体的样式
2.因为大写的i和小写的l看起来比较相似,因此代码中我们把这两个字母删掉了。
from PIL import Image, ImageFont, ImageDraw
"""
Image 产生图片
ImageFont 字体样式
ImageDraw 画笔对象
"""
from io import BytesIO, StringIO
"""
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):
img_obj = Image.new('RGB', (350, 35), get_random())
# 将图片对象交给画笔对象
draw = ImageDraw.Draw(img_obj)
# 生成字体对象
font = ImageFont.truetype(font='static/HanYiYanKaiW-2.ttf', size=30)
# 生成随机字符串
code = ''
# 这里的code是用来存储生成的字符串结果的
for i in range(5):
ran_num = str(random.randint(0, 9))
ran_upper = chr(random.randint(65, 90))
# 生成大写的随机字母
# 去除I和L
while ran_upper == 'L' or ran_upper == 'I':
ran_upper = chr(random.randint(65, 90))
ran_lower = chr(random.randint(97, 122))
# 生成小写的随机字母
# 去除i和l
while ran_lower == 'i' or ran_lower == 'l':
ran_lower = chr(random.randint(97, 122))
res = random.choice([ran_num, ran_upper, ran_lower])
# 将生成的随机字符画到图片中
# 这里用变量i控制字符的横向位置
draw.text(xy=(10 + i * 60, 0), text=res, font=font, fill=get_color())
code += res
# 把生成的随机字符串存到code中记录,并把这个字符写到验证码图片中
# 这里我们用session模块保存验证码用来跟用户输入的数据进行对比,也可以在views中定义一个全局变量进行对比
request.session['code'] = code
# 昨天学习pillow模块的时候我们讲到,他产生的图片需要保存后才能使用,因此这里我们先把他保存成io对象再返回给前端的页面
io_obj = BytesIO()
img_obj.save(fp=io_obj, format='png')
return HttpResponse(io_obj.getvalue())
# 这是用与生成字体颜色的函数
def get_color():
x, y = 0, 255
return (random.randint(x, y), random.randint(x, y), random.randint(x, y))
单机验证码实现验证码刷新(局部刷新)
在实现了验证码后我们参考实际网页的登陆界面,发现验证码可以通过单击刷新,这里我们可以使用事件来实现这个功能。
// 1.验证码动态刷新
$('#d1').click(function () {
let oldSrc = $(this).attr('src');
{#这里的this就是指代id为d1的img标签,后面的attr就是查找这个标签的src属性的值#}
$(this).attr('src', oldSrc + '?')
{#这里用到一个小知识点,当我们在产生验证码的路由后加上一个问号就会重新触发路由对应的视图函数产生新的验证码,当我们每次点击图片想要刷新验证码的时候就会通过在路由后加上问号产生新的验证码#}
})
点击登陆提交数据进行校验
登陆页面编写到这里已经基本完成,只需要对用户提交的数据进行校验,这里我们使用ajax进行提交数据(因为验证码的存在,使用form表单不方便)
我们可以整理一下思路,目前我们需要实现的功能就是点击登陆按钮提交数据然后获取后端判断后的数据返回结果,而我们需要使用ajax进行数据提交,因此需要用事件来触发ajax。
- 先在html中写上ajax请求
// 2.登录按钮发送ajax请求
$('#loginBtn').click(function () {
// 可以再次使用form标签序列化功能 也可以自己挨个获取
$.ajax({
url:'',
type:'post',
{#这里我们自行拼接数据,组成字典的形式,方便后端用对象的方式获取数据#}
data:{'username':$('#name').val(), 'password':$('#password').val(), 'code':$('#code').val(), 'csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function (args) {
{#当后端接收了发送的数据,返回结果的时候,我们通过自行设置的code属性来判断是否登陆成功,并依据code的值设置结果#}
}
})
})
- 接下来去视图层写后端的处理数据的代码
ps:因为用到了auth模块,所以需要导入
from django.contrib import auth
def login_func(request):
back_dict = {'code': 10000, 'msg': ''}
# 类似之前的操作,先自行定义ajax返回的信息的格式,然后再根据情况填充数据
if request.method == 'POST':
# 先获取用户名跟密码以及验证码
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
# 接着先判断验证码是否正确(这里我们简单处理验证码,把字符全部转成大写后再进行判断)
if code.upper() == request.session.get('code').upper():
# 这里就是用auth模块查找是否有这个用户存在,并判断密码是否正确,不得不说auth是真的方便
user_obj = auth.authenticate(request, username=username, password=password)
if user_obj:
# 如果我们的auth模块查找到了对应的用户信息就说明信息是全部正确的已经登陆成功了
# 保存用户登录状态
auth.login(request, user_obj) # 执行之后就可以使用request.user获取登录用户对象
back_dict['msg'] = '登陆成功'
back_dict['url'] = '/home/'
else:
# 如果我们的auth模块没有查到对应的用户对象,说明用户名或密码不对
back_dict['code'] = 10001
back_dict['msg'] = '用户名或密码'
else:
# 这里就是验证码错了的情况,返回错误信息即可
back_dict['code'] = 10002
back_dict['msg'] = '验证码错误'
# 当接收post请求需要返回数据的时候需要返回back_dict给前端返回结果,用JsonResponse返回结果,可以在前端用点的方式获取数据
return JsonResponse(back_dict)
# 在判断用户名跟密码是否正确
return render(request, 'loginPage.html')
后端代码写好后,我们回到html文件中的ajax的接收返回数据的代码进行编写。(还需要编写一个csrf_token,否则csrf策略那过不去)
完整的前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
{% load static %}
<script src="{% static 'jquery-3.6.1.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="col-md-8 col-md-offset-2">
<h2 class="text-center">用户登录</h2>
{% csrf_token %}
<div class="form-group">
<label for="name">用户名</label>
<input type="text" id="name" class="form-control" name="username">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" class="form-control" name="password">
</div>
<div class="form-group">
<label for="code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="code" class="form-control" name="code">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" width="350" height="35" id="d1">
</div>
</div>
</div>
<input type="button" class="btn btn-success btn-block" value="登录" id="loginBtn">
</div>
</div>
<script>
// 1.验证码动态刷新
$('#d1').click(function () {
let oldSrc = $(this).attr('src');
{#这里的this就是指代id为d1的img标签,后面的attr就是查找这个标签的src属性的值#}
$(this).attr('src', oldSrc + '?')
{#这里用到一个小知识点,当我们在产生验证码的路由后加上一个问号就会重新触发路由对应的视图函数产生新的验证码,当我们每次点击图片想要刷新验证码的时候就会通过在路由后加上问号产生新的验证码#}
})
// 2.登录按钮发送ajax请求
$('#loginBtn').click(function () {
// 可以再次使用form标签序列化功能 也可以自己挨个获取
$.ajax({
url:'',
type:'post',
{#这里我们自行拼接数据,组成字典的形式,方便后端用对象的方式获取数据#}
data:{'username':$('#name').val(), 'password':$('#password').val(), 'code':$('#code').val(), 'csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function (args) {
{#当后端接收了发送的数据,返回结果的时候,我们通过自行设置的code属性来判断是否登陆成功,并依据code的值设置结果#}
if (args.code === 10000) {
window.location.href = args.url
}else {
{#这里也可以使用sweetalert插件进行美化#}{#swal(args.msg,'error')#}
alert(args.msg)
}
}
})
})
</script>
</body>
</html>
二、主页搭建
首先还是添加主页对应的路由
urlpatterns = [
path('admin/', admin.site.urls),
# 用户注册功能
path('register/', views.register_func, name='register_view'),
# 用户登录功能
path('login/', views.login_func, name='login_view'),
# 图片验证码相关功能
path('get_code/', views.get_code_func),
# 网址首页
path('home/', views.home_func, name='home_view'),
]
创建主页的html文件
- 首先我们需要去bookstrap官网找一个导航条,并进行一些更改。
- 然后我们参考学习auth模块时的作业,在导航条出实现当用户未登陆时显示登陆注册,登录后显示登陆的用户和一些操作的功能标签(用导航条的下拉框实现)。
- 接着我们设置登陆后可以进行操作的标签,先实现修改密码和注销登陆的功能,其中修改密码我们使用模态框来展示。
html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<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>
{% block css %}
{% endblock %}
</head>
<body>
<!--导航条开始-->
<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>
<a class="navbar-brand" href="#">
{% block title %}
BBS
{% endblock %}
</a>
</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="#">博客 <span class="sr-only">(current)</span></a></li>
<li><a href="#">文章</a></li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="搜索">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<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="#" data-toggle="modal" data-target="#myModal">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">注销登录</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'register_view' %}">注册</a></li>
<li><a href="{% url 'login_view' %}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!--导航条结束-->
<!--模态框开始-->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
</button>
<h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="">用户名</label>
<input type="text" value="{{ request.user.username }}" disabled class="form-control">
</div>
<div class="form-group">
<label for="">原密码</label>
<input type="text" id="old_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="text" id="new_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="text" id="confirm_pwd" class="form-control">
</div>
</div>
<div class="modal-footer">
<span id="error" style="color: red"></span>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-warning" id="setBtn">修改</button>
</div>
</div>
</div>
</div>
<!--模态框结束-->
<!--内容区域开始-->
<div class="container-fluid">
<div class="row">
{#这里也是用母版语法让子网页可以自定义#}
{% block content %}
{#主页的主要内容分成三大部分,左右两边用row分开展示一些广告或是标签,中间部分展示文章(文章标题文章作者和头像以及部分内容展示)#}
<div class="col-md-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">重金求子</h3>
</div>
<div class="panel-body">
事后必有重谢:wuyong123
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">百万大奖</h3>
</div>
<div class="panel-body">
恭喜你幸运儿:zhanghong321
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告招租</h3>
</div>
<div class="panel-body">
旺铺吃一辈子:suiyuanli222
</div>
</div>
</div>
<div class="col-md-8">
{% for article_obj in page_queryset %}
<div class="media">
<h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
<img class="media-object" src="/media/{{ article_obj.site.userinfo.avatar }}/" alt="..." width="80">
</a>
</div>
<div class="media-body" style="padding:10px">
{{ article_obj.desc }}
</div>
<br>
<div>
<span><a href="/{{ article_obj.site.userinfo.username }}/"> {{ article_obj.site.userinfo.username }} </a></span>
<span> {{ article_obj.create_time|date:'Y-m-d H:i:s' }} </span>
<span class="glyphicon glyphicon-thumbs-up"> {{ article_obj.up_num }} </span>
<span class="glyphicon glyphicon-thumbs-down"> {{ article_obj.down_num }} </span>
<span class="glyphicon glyphicon-comment"> {{ article_obj.comment_num }} </span>
</div>
</div>
<hr>
{% endfor %}
<div class="text-center">{{ page_obj.page_html|safe }}</div>
</div>
<div class="col-md-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">重金求子</h3>
</div>
<div class="panel-body">
事后必有重谢:wuyong123
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">百万大奖</h3>
</div>
<div class="panel-body">
恭喜你幸运儿:zhanghong321
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告招租</h3>
</div>
<div class="panel-body">
旺铺吃一辈子:suiyuanli222
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!--内容区域结束-->
<script>
$('#setBtn').click(function () {
$.ajax({
url: '/set_pwd/',
type: 'post',
data: {
'old_pwd': $('#old_pwd').val(),
'new_pwd': $('#new_pwd').val(),
'confirm_pwd': $('#confirm_pwd').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}',
},
success: function (args) {
if (args.code === 10000){
window.location.href = args.url
}else{
$('#error').text(args.msg)
}
}
})
})
</script>
{% block js %}
{% endblock %}
</body>
</html>
网页中我们用到了修改密码和注销登陆状态的功能,这两个功能需要在路由和视图层中进行注册和编写才能使用
# 修改密码功能
path('set_pwd/', views.set_pwd_func),
# 注销登录功能
path('logout/', views.logout),
views.py代码
def home_func(request):
# 查询所有用户编写的文章
article_queryset = models.Article.objects.all()
'''文章过多的情况下应该考虑添加分页器,分页器代码在下面'''
page_obj = mypage.Pagination(current_page=request.GET.get('page'), all_count=article_queryset.count())
page_queryset = article_queryset[page_obj.start:page_obj.end]
return render(request, 'homePage.html', locals())
因为修改密码和注销登陆状态,需要在登陆后才能执行,因此我们需要使用auth模块提供的装饰器进行区分
而使用auth模块的装饰器需要在配置文件中指定跳转的页面,代码如下:
LOGIN_URL = '/login/'
@login_required
def set_pwd_func(request):
back_dict = {'code': 10000, 'msg': ''}
if request.method == 'POST':
old_pwd = request.POST.get('old_pwd')
new_pwd = request.POST.get('new_pwd')
confirm_pwd = request.POST.get('confirm_pwd')
# 先校验原密码是否正确
if request.user.check_password(old_pwd):
# 再校验两次密码是否一致 并且不能为空
if new_pwd == confirm_pwd and new_pwd:
request.user.set_password(new_pwd)
request.user.save()
back_dict['msg'] = '密码修改成功'
back_dict['url'] = '/login/'
else:
back_dict['code'] = 10001
back_dict['msg'] = '两次密码不一致或者为空'
else:
back_dict['code'] = 10002
back_dict['msg'] = '原密码错误'
return JsonResponse(back_dict)
@login_required
def logout(request):
auth.logout(request)
return redirect('home_view')
分页器
在设置主页文章展示的时候,考虑到文章会很多,需要用分页器分批展示。
首先创建文件导入分页器代码,这里我们设置每页展示10篇文章
mypage.py
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
主页内容部分
网页代码中的内容代码部分,我们需要先添加数据才能进行展示。这里我们用django的admin后台管理来添加数据,这里可以回顾一下知识点,因为我们需要创建一个管理员用户才能登陆进去添加数据,我们通过注册页面创建的是普通用户。
创建管理员的关键字为createsuperuser
接着在浏览器中访问admin页面
http://127.0.0.1:8000/admin/
输入刚刚注册的管理员账号后进入如图界面:
这里我们可以看到,虽然进入了后台管理界面,但是什么都没有,我们无法操作也无法添加数据,这时候就需要到应用下的admin.py文件中编写代码添加配置,代码如下:
from django.contrib import admin
'因为我们需要注册models.py文件中的表,所以需要导入'
from app01 import models
# Register your models here.
'''只要注册了 admin就会自动生产针对该注册表的增删改查至少四个功能'''
admin.site.register(models.UserInfo)
admin.site.register(models.Site)
admin.site.register(models.Article)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
结果如图:
ps:如果不喜欢注册后的英文名,我们可以回顾面向对象中的双下str方法,我们可以在模型表中给他们添加双下str方法,让admin管理界面中,读取表名称的时候显示中文,如果想让注册后的名称显示成中文,需要给表增加配置。
# 修改admin后台管理的表名
class Meta:
verbose_name_plural = '用户表'
# 修改admin管理界面读取表的时候的名称,我们在绑定外键的时候会用到
def __str__(self):
return f'用户对象:{self.username}'
结果如果:
官网提供
官网提供了针对日期字段的切割处理
id content create_time month
1 111 2020-11-11 2020-11
2 222 2020-11-12 2020-11
3 333 2020-11-13 2020-11
4 444 2020-11-14 2020-11
5 555 2020-11-15 2020-11
"""
django官网提供的一个orm语法
from django.db.models.functions import TruncMonth
-官方提供
from django.db.models.functions import TruncMonth
Sales.objects
.annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
.values('month') # Group By month
.annotate(c=Count('id')) # Select the count of the grouping
.values('month', 'c') # (might be redundant, haven't tested) select month and count
时区问题报错
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
"""
作业
1.用户登录成功之后导航条制作用户头像