1 文章详情页
# url设计
/username/article/1
# 先验证url是否会被其他url顶替
# 文章详情页和个人站点基本一致 所以用模版继承
# 侧边栏的渲染需要传输数据才能渲染 并且该侧边栏在很多页面都需要使用
1.哪个地方用就拷贝需要的代码(不推荐 有点繁琐)
2.将侧边栏制作成inclusion_tag
# 自定义模板步骤:
1.在应用下创建一个名字必须叫templatetags文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内先固定写两行代码
from django import template
register = template.Library()
# 自定义过滤器
# 自定义标签
# 自定义inclusion_tag
# 自定义inclusion_tag:left_menu
from django import template
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = template.Library()
@register.inclusion_tag('left_menu.html')
def left_menu(username):
# 构造侧边栏需要的数据
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 左侧三个筛选 (侧边栏的筛选其实就是对article_list再进一步筛选)
# 1.查询个人站点的文章分类及分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values(
'name', 'count_num', 'pk')
# 2.查询个人站点的文章标签及标签下的文章
tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values(
'name', 'count_num', 'pk')
# 3.查询个人站点下的文章时间及时间下的文章数
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('id')).values('month', 'count_num').order_by('month')
return locals()
2 文章点赞点踩
2.1 逻辑梳理
# 引入:
1.浏览器上你看到的花里胡哨的页面,内部都是HTML(前端)代码
2.那现在我们的文章内容应该写什么??? >>> html代码
3.如何拷贝文章
博客园文章--copy outerhtml
4.拷贝点赞点踩
1.拷贝前端点赞点踩图标 只拷了html
2.css也要拷贝
由于有图片防盗链的问题 所以将图片直接下载到本地
# 课下思考:
前端如何区分用户是点了赞还是点了踩
1.给标签各自绑定一个事件
两个标签对应的代码其实基本一样,仅仅是是否点赞点踩这一个参数不一样而已
2.二合一
给两个标签绑定一个事件
# 给所有的action类绑定事件
$('.action').click(function () {
alert($(this).hasClass('diggit'))
})
# 由于点赞点踩内部有一定的业务逻辑,所以后端单独开设视图函数处理
# 前端页面
1.拷贝博客园点赞点踩前端样式
html代码 + css代码
2.如何判断用户到底点击了哪个图标
恰巧页面上只有两个图标,所以给两个图标标签添加一个公共的样式类
然后给这个样式类绑定点击事件
再利用this指代的就是当前被操作对象
利用hasClass判断是否有某个特定的类属性,
从而判断出到底是两个图标中的哪一个
3.书写ajax代码朝后端提交数据
4.后端逻辑书写完毕之后 前端针对点赞点踩动作实现需要动态展示提示信息
1.前端点赞点踩数字自增1 需要注意数据类型的问题
Number(old_num) + 1
2.用户没有登陆 需要展示没有登陆提示 并且登陆可以点击跳转
html()
|safe
mark_safe()
# 后端逻辑
1.先判断用户是否登陆
request.user.authenticated()
2.再判断当前文字是否是当前用户自己写的
通过文章主键值获取文章对象
之后利用orm查询获取文章对象对应的用户对象与request.user比对
3.判断当前用户是否已经给当前文章点了
利用article_obj文章对象和request.user用户对象去点赞点踩表中筛选数据如果有数据则点过没有则没点
4.操作数据库 需要注意要同时操作两张表
# 前端发送过来的是否点赞是一个字符串 需要你自己转成布尔值或者用字符串判断
is_up = json.loads(is_up)
F模块
2.2 代码实现
##### views.py
# 个人建议:
写代码先把所有正确的逻辑写完 再去考虑错误的逻辑 不要试图两者兼得
import json
from django.db.models import F
def up_or_down(request):
"""
1.校验用户是否登陆
2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
3.当前用户是否已经给当前文章点过了
4.操作数据库
"""
if request.is_ajax():
back_dic = {'code':1000,'msg':''}
# 1 先判断当前用户是否登陆
if request.user.is_authenticated():
article_id = request.POST.get('article_id')
is_up = request.POST.get('is_up')
is_up = json.loads(is_up) # 记得类型转换
# print(is_up, type(is_up)) # True <class 'bool'>
# 2 判断当前文章是否是当前用户自己写的 根据文章id查询文章对象 根据文章对象查作者 根request.user比对
article_obj = models.Article.objects.filter(pk=article_id).first()
if not article_obj.blog.userinfo == request.user:
# 3 校验当前用户是否已经点了 哪个地方记录了用户到底点没点
is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)
if not is_click:
# 4 操作数据库 记录数据 要同步操作普通字段
# 判断当前用户点了赞还是踩 从而决定给哪个字段加一
if is_up:
# 给点赞数加一
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)
else:
back_dic['code'] = 1001
back_dic['msg'] = '你已经点过了,不能再点了'
# 这里你可以做的更加的详细 提示用户到底点了赞还是点了踩
else:
back_dic['code'] = 1002
back_dic['msg'] = '你个臭不要脸的!'
else:
back_dic['code'] = 1003
back_dic['msg'] = '请先<a href="/login/">登陆</a>'
return JsonResponse(back_dic)
#### article_detail.html
<script>
// 给所有的action类绑定事件
$('.action').click(function () {
let isUp = $(this).hasClass('diggit'); # digg n.推荐 bury n.埋葬
let $div = $(this);
// 朝后端发送ajax请求
$.ajax({
url:'/up_or_down/',
type:'post',
data:{
'article_id':'{{ article_obj.pk }}',
'is_up':isUp,
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if(args.code == 1000){
$('#digg_tips').text(args.msg)
// 将前端的数字加一
// 先获取到之前的数字
let oldNum = $div.children().text(); // 文本 是字符类型
// 易错点
$div.children().text(Number(oldNum) + 1) // 字符串拼接了 1+1 = 11 11 + 1 = 111
}else{
$('#digg_tips').html(args.msg)
}
}
})
})
</script>
3 文章评论
# 顺序:
先根评论
再子评论
# 根评论
先把整体的评论功能跑通 再去填补
1.书写前端获取用户评论的标签
可能点赞点踩有浮动带来的影响
clearfix
2.点击评论按钮发送ajax请求
3.后端针对评论单独开设url处理
后端逻辑其实非常的简单非常的少
4.针对根评论涉及到前端的两种渲染方式
1.DOM操作临时渲染评论楼
需要用到模版字符串
// 临时渲染评论楼
let userName = '{{ request.user.username }}';
let temp = `
<li class="list-group-item">
<span>${userName}</span>
<span><a href="#" class="pull-right">回复</a></span>
<div>
${conTent}
</div>
</li>
`
// 将生成好的标签添加到ul标签内
$('.list-group').append(temp);
2.页面刷新 永久渲染(render)
后端直接获取当前文章对应的所有评论 传递给html页面即可
前端利用for循环参考博客园评论楼样式渲染评论
3.评论框里面的内容需要清空
# 子评论
从回复按钮入手
点击回复按钮发生了哪些事
1.评论框自动聚焦 .focus()
2.评论框里面自动添加对应评论的评论人姓名
@username\n
3.评论框内部自动换行
# 点击回复按钮之后 我们应该获取到根评论对应的用户名和主键值
针对主键值 多个函数都需要用 所以用全局变量的形式存储
# 针对子评论内容 需要切割出不是用户写的 "@username\n"
// 获取用户评论的内容
let conTent = $('#id_comment').val();
// 判断当前评论是否是子评论 如果是 需要将我们之前手动渲染的@username去除
if(parentId){
// 找到\n对应的索引 然后利用切片 但是前片顾头不顾尾 所以索引+1
let indexNum = conTent.indexOf('\n') + 1;
conTent = conTent.slice(indexNum) // 将indexNum之前的所有数据切除 只保留后面的部分
}
# 后端parent字段本来就可以为空,所以传不传值 都可以直接存储数据
# 前端针对子评论再渲染评论楼的时候 需要额外的判断
{% if comment.parent_id %}
<p>@{{ comment.parent.user.username }}</p>
{% endif %}
{{ comment.content }}
# 前端parentId字段每次提交之后需要手动清空
# 思考:
1.根评论和子评论点的是同一个按钮: parent_id
2.根评论和子评论的区别
子评论在其实跟评论的ajax代码中 只需要添加一个父评论id即可