django--BBS项目,后端业务逻辑整理
经典的生活价值观
别让人生,输给了心情。心情不是人生的全部,却能左右人生的全部。心情好,什么都好,心情不好,一切都乱了。我们常常不是输给了别人,而是坏心情贬低了我们的形象,降低了我们的能力,扰乱了我们的思维,从而输给了自己。
控制好心情,生活才会处处祥和。好的心态塑造好心情,好心情塑造最出色的你。
静静的过自己的生活,心若不动,风又奈何。你若不伤,岁月无恙。
BBS 项目开发逻辑梳理
第一步:先进行数据库设计
数据库设计规则是:
1.先创建基表:用户表、站点表、文章表、标签表、分类表、文章2标签第三张关系表、点赞点踩表、评论表
2.书写表中的基本含有的字段
3.添加外键(一对一,一对多,多对多)
4.第三张关系表
注意事项:创建外键关系的时候,to='表名',不要忘记引号,null=true,并不是所有的外键都加的
第二步settings配置
一定要进行settings的相关配置:
1.数据库配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bbszikao',
'USER':'root',
'PASSWORD': 'root',
'HOST':'127.0.0.1',
'PORT':3306,
'CHARSET':'utf8'
}
}
2.静态文件资源配置
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static')
]
3.models.py文件中,用户表继承AbstractUser类,需要对其进行settings配置
from django.contrib.auth.models import AbstractUser
配置auth模块的访问:
AUTH_USER_MODEL='app01.Userinfo'
4.静态图片资源settings配置+urls路由访问配置,暴露给用户查看,用户注册可以访问到默认头像
#settings文件配置:
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
#urls文件访问头像路由配置
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
第三步功能开发
注册功能
后端开发逻辑:
分为:register函数+register.html+myform.py文件
后端开发逻辑:
# myform.py 文件
1.建立myform.py文件,利用forms表单,提交注册的数据信息
创建class MyRegForm(forms.Form):类
用户名,密码,确认密码,邮箱
from django import forms
from app01 import models
class MyRegForm(forms.Form):
username = forms.CharField(max_length=8,min_length=3,label='用户名',
error_messages={
'max_length': '用户名最长8位',
'min_length': '用户名最短3位',
'required': "用户名不能为空"
},
#标签设置
widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
建立局部钩子函数校验用户名是否存在
# 局部钩子 校验用户名是否已存在
def clean_username(self):
username = self.cleaned_data.get('username')
res = models.Userinfo.objects.filter(username=username)
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
# models.py 文件
2.把myform文件中的MyRegForm表单对象拿到,
然后把form_obj对象发送给前端register.html页面
form_obj=myform.MyRegForm()
return render(request,'register.html',locals())
后端开发逻辑如下:
把myform文件中的MyRegForm表单对象拿到
判断前端发送过来的请求方式是不是post请求
定义back_dic 字典
对用户在前端提交的post数据进行校验,生成form_obj对象
如果数据合法:
获取所有键值对
pop掉确认密码键值对
获取前端发送的文件请求(avatar),创建文件对象
判断用户是否上传文件:
上传文件了,把avatar添加到clean_data字典对象中
用户表创建用户(create_user)
back_dic字典添加msg信息,注册成功
back_dic字典添加url,login路径
数据不合法:
字典添加code=2000
字典添加msg=form_obj.errors
返回json数据到前端,[需要导入JsonResponse模块(from django.http import JsonResponse)]
然后把form_obj对象发送给前端register.html页面
前端开发逻辑:
上传头像文件功能,注册按钮功能
前端开发逻辑:register.html文件,前端只整理需要整理的
上传头像文件功能
<script>
//上传头像文件相关的处理
$('#mdd').on('change',function () {
//利用内置对象filereader完成文件的读取操作
let MyFileReader=new FileReader();
//获取用户上传的文件对象
let fileobj=$(this)[0].files[0];
//让文件阅读器读取文件,IO操作,异步
MyFileReader.readAsDataURL(fileobj);
//将读取之后的内容替换到img标签src属性中
MyFileReader.onload=function () {
$('#img').attr('src',MyFileReader.result)
}
});
// 注册按钮
$('#submit').click(function () {
// 将用户输入的数据全部发送给后端 普通的键值对 文件
let MyFormData = new FormData();
// 不停的朝里面添加键值对
{#MyFormData.append('','')#}
{#console.log($('#myform').serializeArray())#}
// 普通键值对添加完毕 (利用form标签内部有一个自动序列化普通键值对方法)
$.each($('#myform').serializeArray(),function (index,obj) {
MyFormData.append(obj.name,obj.value)
});
// 手动添加文件数据
MyFormData.append('avatar',$('#mdd')[0].files[0]);
// 发送ajax请求
$.ajax({
url:'',
type:'post',
data:MyFormData,
// 发送文件一定要指定两个参数
processData:false, // 不要让浏览器处理你的数据
contentType:false, // 不要使用任何的编码 django能够识别对象自身
success:function (data) {
if (data.code == 1000){
// 跳转到登录页面
window.location.href = data.url
}else{
$.each(data.msg,function (index,obj) {
{#console.log(index,obj)#} // index就是报错字段 obj就是错误信息 数组的形式
// 获取报错字段 手动拼接出该字段所对应的input框的id值
let targetId = '#id_' + index;
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
});
// input框获取焦点事件,---这个是鼠标放到input框上面后,错误信息消失
$('input').focus(function () {
$(this).next().text('').parent().removeClass('has-error')
})
</script>
图片验证码功能
后端开发逻辑
1.所需要的模块
import random
from PIL import Image,ImageDraw,ImageFont
from io import BytesIO,StringIO
'''
内存管理器模块:
BytesIO 保存数据,并且在获取的时候,是以二进制的方式给你
StringIO 保存数据,并且在获取的时候,是以字符串的方式给你
Image 生成图片
ImageDraw 在图片上写字
ImageFont 控制字的字体样式
'''
#io_obj=BytesIO() #你就将该对象看成是文件句柄即可
'''
什么是文件句柄???
在文件I/O中,要从一个文件读取数据,应用程序首先
要调用操作系统函数并传送文件名,并选一个到该文件
的路径来打开文件。该函数取回一个顺序号,即文件句柄
(file handle),该文件句柄对于打开的文件是唯一的
识别依据。要从文件中读取一块数据,应用程序需要调用
函数ReadFile,并将文件句柄在内存中的地址和要拷贝的
字节数传送给操作系统。当完成任务后,再通过调用系统函数
来关闭该文件。”
'''
2.图片验证码的开发分为两步:
随机取色+图片验证码
2.1随机取色
def get_random():
return random.randint(0,255),random.randint(0,255),random.randint(0,255)
2.2图片验证码函数
图片的宽高和随机取色---生成画板对象 Image.new
将生成好的图片对象交给ImageDraw---画笔对象 ImageDraw.Draw
字体样式 ---何种字体 ImageFont.truetype
#随机验证码 ---何种要求(大小写英文加数字,5位)
定义code=''
循环5次:
大写字母 upper_str
小写字母 lower_str
str(随机数) random_int
随机选取一个,random.choice([大写,小写,str(随机数)])
往图片上写一个验证码 img_draw.text
存储写的字 code+=tmp
将code存到session中,供全局函数访问 request.session['code']=code
生成I/O文件句柄 io_obj=BytesIO()
图片对象调用save方法保存io_obj文件对象,png的格式进行保存 img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
--------------------------------------------------------------------------------
3.图片验证码代码如下:
def get_code(request):
# 图片的宽高和随机取色 ----画板
img_obj=Image.new('RGB',(360,35),get_random())
#将生成好的图片对象交给ImageDraw ---画笔
img_draw=ImageDraw.Draw(img_obj)
#字体样式 ---何种字体
img_font=ImageFont.truetype('static/font/111.ttf',30)
#随机验证码 ---何种要求(大小写英文加数字,5位)
code=''
for i in range(5):
upper_str=chr(random.randint(65,90))
lower_str=chr(random.randint(97,122))
random_int=str(random.randint(0,9))
#随机选取一个
tmp=random.choice([upper_str,lower_str,random_int])
#往图片上写一个验证码
img_draw.text((i*60+60,0),tmp,get_random(),img_font)
#存储写的字
code+=tmp
print(code)
#这个验证码后面其他视图函数可能要用到,
#找个地方存一下,并且这个地方全局的视图函数都能访问
request.session['code']=code
io_obj=BytesIO() #你就将该对象看成是文件句柄即可
'''
在文件I/O中,要从一个文件读取数据,应用程序首先
要调用操作系统函数并传送文件名,并选一个到该文件
的路径来打开文件。该函数取回一个顺序号,即文件句柄
(file handle),该文件句柄对于打开的文件是唯一的
识别依据。要从文件中读取一块数据,应用程序需要调用
函数ReadFile,并将文件句柄在内存中的地址和要拷贝的
字节数传送给操作系统。当完成任务后,再通过调用系统函数
来关闭该文件。”
'''
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
图片验证码实时刷新功能
前端开发逻辑
图片验证码随机变化的逻辑其实很简单,就是:
首先为验证码绑定点击事件,
其次拿到img中的src属性,
最后为src设置新的值,使得图片验证码不断的更换
<div class="col-md-6">
<img src="/get_code/" alt="" width="360" height="35" id="id_img">
</div>
<script>
//拿到img中的src,
//然后为src设置新的值,使得图片验证码不断的更换
$('#id_img').click(function () {
var oldPath = $(this).attr('src');
$(this).attr('src',oldPath+='?')
});
</script>
登录功能
后端开发逻辑
urls.py文件中:
# 登录功能
url(r'^login/',views.login,name='login'),
views.py文件中:
开发逻辑如下:
如果请求方式是post请求:
定义back_dic
获取post请求的用户名
获取post请求的密码
获取post请求的验证码
#先校验验证码是否正确,忽略大小写
#再校验用户名和密码是否正确
如果用户输入的验证码和后端保存的验证码相等:
再用auth模块校验用户名和密码是否相等,生成用户对象
如果相等:
利用auth模块保存用户对象的登录状态
#就可以在任意位置通过request.user获取到当前登录对象,并且request.user.is_authenticated()判断当前用户是否登录
back_dic字典添加msg登录成功
back_dic字典添加url,home主页路径
如果用户名和密码不相等:
back_dic字典添加code=2000
back_dic字典添加msg用户名或密码错误
如果验证码不相等:
back_dic字典添加code=3000
back_dic字典添加msg,验证码错误
通过JsonResponse返回字典
返回到登录页面
# 保存用户登录状态,这个不太会,对该知识点模糊要多注意了,
# 通过auth模块的login方法将用户保存起来,就可以在任意位置通过request.user,
# 获取到当前登录对象,并且可以通过request.user.is_authenticated()判断当前用户是否登录
auth.login(request, user_obj)
auth模块很重要需要多复习复习
前端开发逻辑
为登录按钮绑定点击事件,
利用ajax请求,把数据发送到后端
ajax的固定格式为:
$.ajax({
url:'',
type:'post',
data:{k,v键值对,'csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function(data){
//此处的data就是后端返回过来的back_dic字典对象
如果code=1000:
跳转到对应页面的url连接
如果不等于1000:
渲染错误数据信息
信息错误后自动刷新验证码
}
})
提交post请求跳过csrf中间件拦截的两种方式:
{% csrf_token %} ------- 在form表单中书写
'csrfmiddlewaretoken':'{{ csrf_token }}' ------- 在ajax中的data字典中书写
<div>
<input type="button" class="btn btn-primary" value="登录" id="id_submit">
<span style="color: red;" id="id_error"></span> //在此处渲染页面的错误信息
</div>
<script>
$('#id_submit').click(function () {
$.ajax({
url:'',
type:'post',
data:{
//$('#标签名').val()是获取当前标签的值
'username':$('#id_username').val(),
'password':$('#id_password').val(),
'code':$('#id_code').val(),
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (data) {
if(data.code == 1000){
// 跳转链接
window.location.href = data.url
}else{
//渲染错误数据信息
$('#id_error').text(data.msg);
//数据填写错误,提交后验证码再次刷新
var oldPath = $('#id_img').attr('src');
$('#id_img').attr('src',oldPath+='?')
}
}
})
</script>
首页搭建功能
后端开发逻辑
#home主页
第一步:查询所有文章,生成文章queryset对象,
第二步:把数据全部提交到home页面
def home(request):
#查询所有文章,生成文章queryset对象,
#把数据全部提交到home页面
article_queryset=models.Article.objects.all()
return render(request,'home.html',locals())
前端开发逻辑
主页搭建共分为四块,
第一块,导航条
第二块,左侧面板 2
第三块,中间面板 8
第四块,右侧面板 2
退出登录功能
后端开发逻辑
导入auth模块,导入登录认证装饰器:
from django.contrib import auth
from django.shortcuts import reverse ----这个是用来反向解析的模块
from django.contrib.auth.decorators import login_required
给退出登录函数添加@login_required装饰器,装饰器不要加括号
利用auth模块的退出登录函数 ---auth.logout(request)
返回通过重定向反转解析到home主页 ---redirect(reverse('home'))
代码如下:
@login_required
def logout(request):
auth.logout(request)
return redirect(reverse('home')) ---退出登录之后跳转到home主页
前端开发逻辑
用户登录情况下:
展示用户名:
拿到用户的用户名,------超链接;通过auth模块的is_authenticated判断用户是否已经登录
修改密码
修改头像
后台管理
退出登录
未登录情况下:
展示登录 --超链接,反向解析login,{% url 'login' %}
展示注册 --超链接,反向解析register,{% url 'register' %}
部分逻辑代码如下:
{% if request.user.is_authenticated %}
<li><a href='#'> {{ request.user.username }} </a></li>
<li><a data-target="#myModal" data-toggle="modal">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退出登录</a></li>
{% else %}
<li><a href="{% url 'login' %}">登录</a></li>
<li><a href="{% url 'register' %}">注册</a></li>
{% endif %}
修改密码功能
后端开发逻辑
urls.py文件中开设修改密码的路由
url(r'^set_password/',views.set_password,name='set_pwd')
views.py文件
from django.shortcuts import reverse ----这个是用来反向解析的模块
from django.contrib import auth
from django.contrib.auth.decorators import login_required
首先给修改密码函数添加装饰器@login_required
如果前端请求方式是post请求:
获取前端发送过来的原密码old_password
获取前端发送过来的新密码new_password
获取前端发送过来的确认密码confirm_password
#利用auth模块先判断前端发送过来的原密码是否正确,
#check_password是auth模块自带的校验密码是否相等的功能
request.user.check_password(old_password)
如果原密码正确:
如果新密码和确认密码相等:
利用auth模块中的set_password设置新的密码
然后利用auth模块中的save方法进行保存
返回重定向解析到login登录页面
如果新密码和确认密码不相等:
返回文本,两次密码不一致
如果原密码不正确:
返回文本,原密码错误
注意事项:
因为没有导入reverse模块,并且把reverse写成了reversed导致的bug
在退出登录和修改密码两处需要用到的反向解析的地方代码都是写成reversed导致的项目bug,如下:
报错信息如下:
NoReverseMatch at /set_password
Reverse for '<reversed object at 0x0000000004EA0E48>' not found. '<reversed object at 0x0000000004EA0E48>' is not a valid view function or pattern name.
翻译如下:
NoReverseMatch在/ set_password
没有找到'< Reverse object at 0x0000000004EA0E48>'。''不是有效的视图函数或模式名。
前端开发逻辑
利用form表单,发送post请求
反向解析到set_pwd路径下,{% url 'set_pwd' %}
利用{% csrf_token %} 跳过csrf中间件
下面就是form表单中的5个div,分别是:
用户名,使用disable属性,默认展示,用户名不支持修改
原密码
新密码
确认密码
取消,修改
admin数据录入功能
后端开发逻辑
1.创建超级用户,进入后台管理---Tools---Run manage Task -----createsuperuser
2.去应用下的admin.py文件中注册你想要管理的模型类
导入models文件
注册管理的模型类:
admin.site.register(models.Userinfo)
admin.site.register(models.Blog)
admin.site.register(models.Tag)
admin.site.register(models.Category)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
3.为表添加中文别名:
在models.py文件中,对应的8张表中分别添加
class Meta:
verbose_name_plural='用户表'
#verbose_name='用户表' 会自动在末尾加s后缀
class Meta:
verbose_name_plural='文章表'
#verbose_name='用户表' 会自动在末尾加s后缀
后面依次如下!!!!
4.在模型表中为模型表添加双下str方法打印对象对应的名字,
双下str只能返回字符串。
def __str__(self):
return self.username
self点对应表中的字段名
5.admin数据录入:
顺序:
文章表
分类和个人站点
用户表 ----绑定对应的站点,在用户表中填写blank=True,
blank告诉admin后台管理该字段可以为空,不会影响到数据库,不用执行迁移命令
标签表
文章2标签表
首页文章展示功能
后端开发逻辑
home函数
查询当前网站所有的文章,展示到前端页面上
def home(request):
#查询当前网站所有的文章,展示到前端页面上
article_queryset = models.Article.objects.all()
return render(request,'home.html',locals())
前端开发逻辑
前端主要编写逻辑和主要逻辑代码如下:
通过for循环article_queryset把文章一篇篇的都读取出来,全部展示到前端页面
{% for article in article_queryset %}
文章的标题,利用跨表查询
<a href="#">
{{ article.title }}
</a>
文章头像,利用跨表查询
<img class='media-object' src='/media/{{article.blog.userinfo.avatar}}/' height='60'>
文章简介
<div class='media-body'> {{ article.desc }} </div>
文章情况包括:
用户名(软件老王)
<span><a href='#'>{{article.blog.userinfo.username}}</a></span>
发布于
<span>发布于 </span>
日期(年月日),需要用到日期过滤器
<span>{{ article.create_time|date:'Y-m-d' }}</span>
评论数(0)
<span>{{ article.comment_num }}</span>
点赞数(0)
<span>{{ article.up_num }}</span>
用户头像展示功能
后端开发逻辑
settings.py文件
media配置,能够将用户上传的所有的文件都统一保存在指定的文件夹下
MEDIA_ROOT=os.path.join(BASE_DIR,'media')
urls.py文件
手动开设后端资源,将media文件夹下面所有的资源暴露给外界访问------固定写法规则,背下来
导入serve模块,导入settings配置文件
from django.views.static import serve
from BBS import settings
#谨慎使用
url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
前端开发逻辑
文章头像,利用跨表查询
<img class='media-object' src='/media/{{article.blog.userinfo.avatar}}/' height='60'>
图片防盗链,404页面功能
后端开发逻辑
urls.py文件
配置个人站点的路由
#个人站点
url(r'^(?P<username>\w+)/$',views.site,name='username')
views.py文件
定义站点函数,接收有名分组username
通过username查询用户对象
如果用户对象不存在:
返回404页面
代码如下:
def site(request,username):
user_obj=models.Userinfo.objects.filter(username=username).first()
if not user_obj: #404页面
return render(request,'error.html')
图片防盗链
通过判断当前请求之前的所在地址
如果是本网站的链接正常访问,如果不是本网站的链接就直接禁止
如果查看呢?
通过请求头里面来查看,
refer ---表示你从哪里来的
user-agent ---标识你是否是一个浏览器
前端开发逻辑
直接在博客园404页面右键检查打开,复制代码,拷贝到BBS项目中的error.html页面
返回网站首页,通过反向解析设置到主页
{% url 'home' %}
------------------------------------------------下面的还没有进行录音工作---------------------------------------------------------
个人站点页面搭建
后端开发逻辑
urls.py文件
配置个人站点的路由
#个人站点
url(r'^(?P<username>\w+)/$',views.site,name='username')
views.py文件
定义站点函数,接收有名分组username
通过username查询用户对象
如果用户对象不存在:
返回404页面
通过用户对象查询到该用户的站点
查询该用户站点下的所有文章
返回数据到site页面
代码如下:
def site(request,username):
#通过username查询用户对象
user_obj=models.Userinfo.objects.filter(username=username).first()
#如果用户对象不存在:
# 返回404页面
if not user_obj:
return render(request,'error.html')
#通过用户对象查询到该用户的站点
blog=user_obj.blog
# 查询当前用户站点下的所有文章
article_list=models.Article.objects.filter(blog=blog)
#返回数据到site页面
return render(request,'site.html',locals())
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
只需一招让你分清QuerySet对象,和用户字典对象
article_list = models.Article.objects.filter(blog=blog)
user_obj = models.Userinfo.objects.filter(username=username).first()
上面的两个查询不太懂
#article_list是可迭代的QuerySet对象,支持for循环
#user_obj这个是用户字典对象,不支持for循环
所有支持for循环的数据类型必须是可迭代数据类型!!!
# 查询当前用户站点下的所有文章
# article_list = models.Article.objects.filter(blog=blog).first()
#所有支持for循环的数据类型必须是可迭代数据类型,
#字典不可迭代,所以for循环取值会报错:TypeError: 'Article' object is not iterable,
#所以article_list必须是可以迭代的对象,QuerySet对象可以迭代,是可迭代对象,
#那么就要把.first()给去掉
------------------这里是通过E盘自己练习的day53课件自己摸索总结出来的---------------------------
# 2.filter() 得到可迭代的QuerySet对象,支持for循环取容器内的元素,在django中推荐使用
# 筛选,相当于你原生sql语句里面的where关键字,返回的结果queryset对象
res=models.Books.objects.filter(pk=1,title='张三丰') #支持多个参数,and关系
print(res) #<QuerySet [<Books: 张三丰>]>
print('************************')
# 3.get() 在django中不推荐使用
# 筛选,获取的是数据对象本身,条件不存在的数据直接报错,并且查询条件必须是唯一的
res=models.Books.objects.get(title='西游记')
print(type(res)) #如果是数据库中不存在的数据进行查询的话,会报错,因为数据库中没有该数据
<QuerySet [<Books: 张三丰>]>
************************
<class 'app01.models.Books'>
//////////////////////////
(0.001) SELECT `app01_books`.`id`, `app01_books`.`title`, `app01_books`.`price`, `app01_books`.`kucun`, `app01_books`.`maichu` FROM `app01_books` WHERE `app01_books`.`price` = 766 LIMIT 21; args=(Decimal('766'),)
<QuerySet [<Books: 西游记>, <Books: 西游记2>]>
<class 'django.db.models.query.QuerySet'>
************************
西游记
1000
<class 'app01.models.Books'>
(0.001) SELECT `app01_books`.`id`, `app01_books`.`title`, `app01_books`.`price`, `app01_books`.`kucun`, `app01_books`.`maichu` FROM `app01_books` WHERE `app01_books`.`price` = 766 ORDER BY `app01_books`.`id` ASC LIMIT 1; args=(Decimal('766'),)
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
# 4.first()
# 功能一:取queryset 相同数据中的第一个数据对象,
重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点
特殊功能如下:
还有一个特殊的功能可以把QuerySet对象转换为字典对象,方便字典通过对象点属性取值----88颗星的重点
# id=3,title='西游记' id=5,title='西游记',first取值会优先取id=3的数据
res = models.Books.objects.filter(price='766')
print(res)
print(type(res))
print('************************')
res = models.Books.objects.filter(price='766').first()
print(res)
print(res.kucun)
print(type(res))
'''上面的打印结果如下:
< QuerySet[ < Books: 西游记 >, < Books: 西游记2 >] >
<class 'django.db.models.query.QuerySet'>
************************
西游记
1000
<class 'app01.models.Books'>
'''
前端开发逻辑
知道怎么回事即可!
site.html个人站点页面
页面布局3,9
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
{% load mytag %}
{% my_menu username %}
</div>
<div class="col-md-9">
{% block content %}
{% endblock %}
</div>
</div>
</div>
模板继承
{% extends 'base.html' %}
循环展示个人站点下的所有文章
{% for article in article_list %}
#posted @ 2019-09-28 17:42 武沛齐 阅读 (2714) 评论 (4) 编辑#
<br>
<div class="pull-right">
<span>posted </span>
<span>@ </span>
<span>{{ article.create_time|date:'Y-m-d' }} </span>
<span>{{ article.blog.userinfo.username }} </span>
<span>评论数({{ article.comment_num }}) </span>
<span>点赞数({{ article.up_num }})</span>
<span><a href="#">编辑</a></span>
</div>
个人站点侧边栏展示功能
后端开发逻辑
共分为三块文章分类,文章标签和日期归档
通过orm查询和django官方提供的日期处理方法来实现
'''
侧边栏筛选的功能到底是筛选什么?
筛选的是当前这个用户下面的所有的文章,再进行标签、分类、日期进行筛选
本质就是对已经查询出来的article_list再进行筛选操作。
'''
1.查询当前用户每一个分类和分类下的文章数
category_list=models.Category.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name','num')
print(category_list) #<QuerySet [('luzhaoshan的分类1', 1)]>
2.查询当前用户每一个标签和标签下的文章数
tag_list=models.Tag.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name','num')
print(tag_list) #<QuerySet [('luzhaoshan的标签1', 1)]>
3.按照年月分组
date_list=models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(num=Count('pk')).values_list('month','num')
print(date_list) #<QuerySet [(datetime.date(2019, 12, 1), 1)]>
备注:(2019,12,1)中的1是django中默认含有的
通过return返回到前端site.html页面,因为获取到的值都是列表套元组,
在前端可以通过列表取值的方式,取索引0是名字,取索引1是对应的值。
前端开发逻辑
后端通过return返回到前端site.html页面,因为获取到的值都是列表套元组,
在前端可以通过列表取值的方式,取索引0是名字,取索引1是对应的值。
分为三块,文章分类,文章标签,日期归档
#<QuerySet [('luzhaoshan的分类1', 1)]>
<h3 class="panel-title">文章分类</h3>
{% for category in category_list %}
<p><a href="#">{{ category.0 }}</a>({{ category.1 }})</p>
{% endfor %}
-----------------------------------------------------------------
#<QuerySet [('luzhaoshan的标签1', 1)]>
<h3 class="panel-title">文章标签</h3>
{% for tag in tag_list %}
<p><a href="#">{{ tag.0 }}</a>({{ tag.1 }})</p>
{% endfor %}
--------------------------------------------------------------------
#<QuerySet [(datetime.date(2019, 12, 1), 1)]>
备注:(2019,12,1)中的1是django中默认含有的
<h3 class="panel-title">日期归档</h3>
{% for date in date_list %}
<p><a href="#">{{ date.0|date:'Y年 - m月' }}</a>({{ date.1 }})</p>
{% endfor %}
个人站点侧边栏筛选功能
后端开发逻辑
urls.py文件
个人站点侧边栏筛选功能
url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site),
url(r'^(?P<username>\w+)/category/(?P<param>\d+)/',views.site),
url(r'^(P<username>\w+)/category/(?P<param>\d+)',views.site),
优化如下:
url(r'^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>)',views.site)
views.py文件
通过关键字参数(**kwargs)来接收有名分组
所以,site函数中使用:
def site(request,username,**kwargs):
user_obj = models.Userinfo.objects.filter(username=username).first()
if not user_obj: # 404页面
return render(request, 'error.html')
blog = user_obj.blog
# 有当前用户所有的文章
article_list = models.Article.objects.filter(blog=blog)
if kwargs: #如果关键字参数有值,则可以按照条件跳转对应的分类或者标签或者日期归档
'''
侧边栏筛选的功能到底是筛选什么?
筛选的是当前这个用户下面的所有的文章,再进行标签,分类,日期筛选,
本质其实就是对该站点进行一个再细分的细化。
'''
condition=kwargs.get('condition')
param=kwargs.get('param')
if condition=='category':
#按照分类筛选
article_list=article_list.filter(category_id=param)
#按照标签筛选 ,
注意事项:
注意双下划线是在本表中的字段通过双下跨表到另外一张表查询
tags是文章表中的外键字段,通过双下划线跨表到tag分类表中
——————————————————————————————————————————————————————————————————————————————————
双下划线在本表中【普通的字段】进行查询,叫母表查询,
双下划线通过本表中的【外键字段】查询是跨表查询,是跨到另外一张表中
——————————————————————————————————————————————————————————————————————————————————
tag_list=article_list.filter(tag__id=param) #双下划线条件查询,查询id=param的数据
#按照年月日期筛选
year,month=param.split('-')
#__year 查询年份 __month 查询月份 #双下划线查询
article_list=article_list.filter(create_time__year=year,create_time__month=month)
前端开发逻辑
<div class="panel-heading">
<h3 class="panel-title">文章分类</h3>
</div>
<div class="panel-body">
{% for category in category_list %}
<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}</a>({{ category.1 }})</p>
{% endfor %}
</div>
——————————————————————————————————————————————————————————————————————————————————
<div class="panel-heading">
<h3 class="panel-title">文章标签</h3>
</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}</a>({{ tag.1 }})</p>
{% endfor %}
</div>
——————————————————————————————————————————————————————————————————————————————————
<div class="panel-heading">
<h3 class="panel-title">日期归档</h3>
</div>
<div class="panel-body">
{% for date in date_list %}
<p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年 - m月' }}</a>({{ date.1 }})</p>
{% endfor %}
</div>
——————————————————————————————————————————————————————————————————————————————————
侧边栏制作
后端开发逻辑
### 侧边栏制作
开发逻辑:
1.根据文章id,查询出对应的文章,展示到前端即可
2.前端用到了模板的继承,把site.html抽出来做成模板
3.创建base.html页面继承site.html页面,然后对9的部分进行调整,
3.1划定区域通过
{% block content %}
aa-这里是页面内容
{% endblock %}
3.2然后原来的site.html页面,就继承base.html页面
{% extends 'base.html' %}
{% block content %}
然后把aa的内容删除掉,放在这个地方
{% endblock %}
3.3来到article_detail.html页面
继承base.html页面
{% extends 'base.html' %}
{% block content %}
这里面是把文章拿过来:
文章标题,
文章内容,
{% endblock %}
4.自定义标签过滤器
4.1创建templatetags文件,和文件下的mytag.py文件
写上:
from django import template
register=template.Library()
@register.inclusion_tag('left_menu.html')
然后创建left_menu.html页面,里面内容全删除掉,空页面
4.2继续创建left_menu函数
def left_menu(username):
然后把site个人站点下的下面的代码剪切到这里,目的就是为了方便多次调用
user_obj = models.Userinfo.objects.filter(username=username).first()
把用户对象拿过来
# 1.查询当前用户每一个分类和分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name', 'num',
'pk')
print(category_list) # <QuerySet [('luzhaoshan的分类1', 1)]>
# 2.查询当前用户每一个标签和标签下的文章数
tag_list = models.Tag.objects.filter(blog=blog).annotate(num=Count('article')).values_list('name', 'num', 'pk')
print(tag_list) # <QuerySet [('luzhaoshan的标签1', 1)]>
# 3.按照年月分组
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(num=Count('pk')).values_list('month', 'num')
print(date_list) # <QuerySet [(datetime.date(2019, 12, 1), 1)]> 备注:(2019,12,1)中的1是django中默认含有的
4.3通过return locals(),把当前所有的内容全部返回到left_menu.html页面
然后去base.html页面把3部分中的内容剪切拿过来,放到left_menu.html页面中
然后base.html页面3的部分中写上:
{% load mytag %}
{% left_menu username %}
### 点赞点踩样式拷贝
开发逻辑
拷贝图标到本地,防止图片防盗链,导致图片加载失败
拷贝点赞点踩的样式
### 点赞点踩的业务逻辑
1.校验用户是否登录,没有登录的情况下,不可点赞和点踩,提示请先登录
2.登录的用户,不能给自己的文章点赞和点踩
3.已经给该文章点过赞和点过踩的用户,不能再点
4.不能给自己点赞和点踩
文章详情页:
点赞点踩通过ajax请求发送给后端,都给点赞和点踩按钮绑定相同的action,
然后通过绑定点击事件实现点赞和点踩的数量增加。
$('.action').click(function(){
如何区分你点的是赞还是点的是踩?
$(this) 是当前被点击的这个对象
$(this).hasClass('diggit') 判断当前这个点击的对象有没有'diggit'这个属性
为了提高代码的阅读性,专门为点赞点踩开设一个url
通过ajax发送请求到后端
$.ajax({
url:'/up_down',
type:'post',
data:{
'csrfmiddlewaretoken':'{{csrf_token}}',
'is_up':isUp,
'article_id':"{{article_obj.pk}}"
},
success:function(data){
现在就可以在updown视图函数中进行相关的逻辑处理了
}
})
})
进入到updown函数中:
点赞点踩业务逻辑
updown函数中:
直接判断过来的请求是不是ajax请求,如果是ajax请求的话:
再判断请求的方法是不是POST请求,如果是POST请求的话:
back_dic={'code':1000,'msg':''}
is_up=request.POST.get('is_up') 是一个字符串格式的json数据
article_id=request.POST.get('article_id')
import json
is_up=json.loads(is_up) 是将json格式的字符串格式数据,转成python对应的格式boolean布尔类型
下面是点赞点踩业务逻辑:
1.校验用户是否登录,没有登录的情况下,不可点赞和点踩,提示请先登录
if request.user.is_authenticated():
2.当前这篇文章是不是当前登录用户自己写的
通过拿到当前站点所对应的的用户对象 是否等于 当前登录的用户对象
article_obj=models.Article.objects(pk=article_id).first()
如果两个对象不相等的话,代表该文章不是该登录用户写的:
if not article_obj.blog.userinfo == request.user:
3.那么,我们继续判断当前这篇文章用户是否已经点过
到点赞点踩表中去查询数据即可
筛选数据,筛选用户是当前用户对象,并且文章对象是当前文章对象的数据,
如果有值表示已经点过了,如果没有值表示还没有点过。
通过.exists()返回布尔类型,TRUE还是false
is_click=models.UpAndDown.objects.filter(user=request.user,article=article_obj).exists()
if not is_click: 如果没有点过,下面就要开始操作数据库,完成数据修改了
操作数据库完成数据修改
if is_up:
如果为true,为点赞数量加1,up_num=原来的数量基础上加1,通过拿字段我们使用F查询
导入from django.db import F
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') +1 )
back_dic['msg'] = '点赞成功'
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') +1 )
back_dic['msg]='点踩成功'
然后操作点赞点踩表插入数据库中,为每一个字段附上值
models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
下面是点赞点踩的业务逻辑完善
后端和前端交互是ajax,那么后端需要定义一个字典
back_dic={'code':1000,'msg':''}放在上面
下面是业务完善:
如果点过了,就不能再点了
else:
back_dic['code'] = 2000
back_dic['msg'] = '你已经点过了,不能再点了'
else:
back_dic['code'] = 3000
back_dic['msg'] = '你不能自己给自己点赞'
else:
back_dic['code'] = 4000
back_dic['msg'] = '请先<a href="/login/">登录</a>'
### 由于请先登录是一个超链接,那么我们在后端可以添加a标签,然后后端取消转义即可
### 后端取消转义导入模块 from django.utils.safestring import mark_safe
### 然后通过mark_safe包裹一下,那么最后优化如下所示:
back_dic['msg'] = mark_safe('请先<a href="/login/">登录</a>')
再通过return JsonResponse把字典数据返回给前端
return JsonResponse(back_dic)
5.操作数据库,完成数据修改
1.点赞点踩添加数据的时候,文章表里面对应的三个普通字段也得修改
然后下面就到了article_detail.html页面,用来处理后端发送过来的请求:
success: function (data) {
if (data.code == 1000) {
// 给span标签渲染信息
$('.info').text(data.msg);
// 点赞或点踩成功之后 应该将前端的数字也加一
let $span = $target.children();
let oldNum = $span.text();
$span.text(Number(oldNum) + 1) // 转整型相加 不然就是字符串拼接了
} else {
$('.info').text(data.msg)
}
评论功能之根评论
#### 评论功能之根评论
如果请求是ajax请求:
请求方式是POST:
定义back_dic字典,back_dic={'code':1000,'msg':'' }
获取文章article_id,
获取评论的内容,
获取parent_id,
开启事务,from django.db import transaction
with transaction.atomic():
models.Comment.objects.create(user=request.user,article_id=article_id,content=content,parent_id=parent_id)
继续操作Article表中的comment_num字段,数量加 1
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1,parent_id=parent_id)
back_dic['msg'] = '评论成功'
return JsonResponse(back_dic)
后面就是前端上的处理了,这里不做总结,详情可以去看前端代码
### 评论功能之子评论
前端点击回复按钮到底发生了几件事? 详情看前端代码
1.自动获取当前点击评论的评论人
2.拼接 @ + 人名 + \n
3.将拼接好的内容添加到评论框中,并且评论框自动聚焦