BBS项目day4
一 侧边栏展示
三个分组查询
# 按分类查询文章数
res_category =models.Category.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name', 'num', 'id')
# 按标签查询文章数
res_tag =models.Tag.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name','num', 'id')
# 按时间对文件进行归档
res_month =models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
根据年月分组的分析过程
id create_time month
1 2020-09-29 11:59:01.326770 2020-09 2
2 2020-09-29 11:59:37.299591 2020-09
3 2020-10-29 09:59:53.029608 2020-10 4
4 2020-10-29 12:00:11.200812 2020-10
5 2020-10-29 12:00:26.995485 2020-10
10 2020-10-30 09:45:14.887788 2020-10
8 2020-08-01 09:03:16.000000 2020-08 2
9 2020-08-01 09:24:24.000000 2020-08
from django.db.models.functions import TruncMonth # 需要导入该模块进行月切分
# filter:指定站点(用户)--> 划出年月的虚拟字段--> values:按年月分组--> 统计文章数 -->按月份倒序排列 -->输出月份与对应文章数
models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time'))
.values('month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
页面样式
<div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">我的标签</h3>
</div>
<div class="panel-body">
{% for tag in res_tag %}
<p><a href="/{{ name }}/tag/{{ tag.2 }}.html">{{ tag.0 }}({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔分类</h3>
</div>
<div class="panel-body">
{% for category in res_category %}
<p><a href="/{{ name }}/category/{{ category.2 }}.html">{{ category.0 }}({{ category.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔档案</h3>
</div>
<div class="panel-body">
{% for month in res_month %}
<p>
<!--用过滤器只过滤出年月-->
<a href="/{{ name }}/archive/{{ month.0|date:'Y/m' }}.html">{{ month.0|date:'Y年m月' }}({{ month.1 }})</a>
</p>
{% endfor %}
</div>
</div>
</div>
二 侧边栏筛选(包含文章部分)
使点击某一站点标签、分类或随笔归档时,返回筛选出的所有文章
路由
# 三个有名分组,分别用来查询:用户名、筛选类别、
re_path('^(?P<name>\w+)/(?P<query>category|tag|archive)/(?P<condition>.*).html$', views.site),
页面
<div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">我的标签</h3>
</div>
<div class="panel-body">
{% for tag in res_tag %}
<p><a href="/{{ name }}/tag/{{ tag.2 }}.html">{{ tag.0 }}({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔分类</h3>
</div>
<div class="panel-body">
{% for category in res_category %}
<p><a href="/{{ name }}/category/{{ category.2 }}.html">{{ category.0 }}({{ category.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔档案</h3>
</div>
<div class="panel-body">
{% for month in res_month %}
<p>
<a href="/{{ name }}/archive/{{ month.0|date:'Y/m' }}.html">{{ month.0|date:'Y年m月' }}({{ month.1 }})</a>
</p>
{% endfor %}
</div>
</div>
</div>
视图
from django.db.models import Count
def site(request, name, **kwargs):
# 分三种情况(根据标签过滤,根据分类过滤,根据时间过滤)
user = models.UserInfo.objects.filter(username=name).first()
if user:
article_list = user.blog.article_set.all() # 拿出用户的所有文章
# 根据不同情况对article_list进行过滤,article_list是queryset对象,可以继续filter
query = kwargs.get('query', None)
if query == 'category': # 说明走的是过滤的路由
condition = kwargs.get('condition')
article_list = article_list.filter(category_id=condition) # 从用户的所有文章中过滤出指定分类
elif query == 'tag':
condition = kwargs.get('condition')
article_list = article_list.filter(tag__id=condition) # 从用户所有文章中过滤出指定标签
elif query == 'archive':
year, month = kwargs.get('condition').split('/') # 2020/09
article_list = article_list.filter(create_time__year=year, create_time__month=month) # 从用户所有文章中过滤出指定文章创建年月
# 如果上面三个判断都不成立当然就返回最初拿出的用户所有文章咯,也就是访问类似"www.IP号/用户名"这样用户主页时触发
# 用来显示进入页面后,侧边栏的分类栏
res_category = models.Category.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list(
'name', 'num', 'id')
# 用来显示进入页面后,侧边栏的标签栏
res_tag = models.Tag.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name',
'num', 'id')
# 用来显示进入页面后,侧边栏的随笔归档
res_month = models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
return render(request, 'site.html', locals())
else:
return render(request, 'error.html')
三 侧边栏用inclusion_tag实现(不包含文章部分)
后端
不知各位还记不记得模板层学过的inclusion_tag
,用途是可以生成一片模板中的代码块,详情请见Django第八日
我反正是不记得了
- 在app下创建一个包,
templatetags
,包的名字必须叫这个 - 在其中新建一个py文件,名字随意,这里我就给他命名
my_tag.py
from django.db.models import Count
from django.template import library
from django.app名 import models
register = library.Library()
# 写一个函数,使用inclusion_tag装饰
@register.inclusion_tag('left.html') # left.html是要导入的模板,是页面的一小部分
def left(username): # 这个函数之后可以在页面中用模板语言调用
user = models.UserInfo.objects.filter(username=username).first()
res_category = models.Category.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list(
'name', 'num', 'id')
res_tag = models.Tag.objects.filter(blog=user.blog).annotate(num=Count('article__id')).values_list('name',
'num', 'id')
res_month = models.Article.objects.filter(blog=user.blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(c=Count('id')).order_by('-month').values_list('month', 'c')
return {'username':username,'res_category': res_category, 'res_tag': res_tag, 'res_month': res_month}
写要导入用的模板文件:left.html
<div class="这是侧边栏哦">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">我的标签</h3>
</div>
<div class="panel-body">
{% for tag in res_tag %}
<p><a href="/{{ name }}/tag/{{ tag.2 }}.html">{{ tag.0 }}({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔分类</h3>
</div>
<div class="panel-body">
{% for category in res_category %}
<p><a href="/{{ name }}/category/{{ category.2 }}.html">{{ category.0 }}({{ category.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">随笔档案</h3>
</div>
<div class="panel-body">
{% for month in res_month %}
<p>
<a href="/{{ name }}/archive/{{ month.0|date:'Y/m' }}.html">{{ month.0|date:'Y年m月' }}({{ month.1 }})</a>
</p>
{% endfor %}
</div>
</div>
</div>
收尾
- 可以将视图函数
site.py
里给侧边栏传参的三行代码删去了 - 将模板文件
site.html
中侧边栏标签里的三个小侧边栏删去,替换成如下代码{% load my_tag %} {% left username %}
四 使用上母版继承
因为文章详情页与文章筛选页只有文章部分在不断切换,筛选页中是一堆文章摘要,而详情页则是一整篇的文章,同时无论哪边标题、侧边栏都没有变化,所以可以将标题跟侧边栏放入母版
- 新建模板文件
base.html
,并在其中放入site.html
文件的所有数据 - 清空’site.html’文件,并在其中写入盒子
并将{% extend "base.html" %} {% block content %} {% endblock %}
base.html
里的文章div整个剪切到盒子中 - 将
base.html
中的文章div整个替换为
注意不要连分区div也给删了,于是乎文章筛选页<div class="col-md-10"> {% block content %} {% endblock %} </div>
site.html
完成了
- 下面开始写文章详情页,其实很简单,就是把整篇文章放入分区标签中:
{% extends 'base.html' %} {% block title %} {{ article.title }} {% endblock %} {% block css %} <link rel="stylesheet" href="/static/css/mycss.css"> {% endblock %} {% block content %} <div> <h3 class="text-center">{{ article.title }}</h3> <hr> <div> {{ article.content }} </div> </div> {% endblock %}
re_path('^(?P<name>\w+)/article/(?P<id>\d+).html$', views.article_detail),
def article_detail(request, name, id): user = models.UserInfo.objects.get(username=name) article = models.Article.objects.get(id=id) return render(request, 'article_detail.html', locals())
五 点赞点踩样式
html
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article.up_num }}</span>h
</div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;">
</div>
</div>
css:从博客园抄来的,图片也是从那儿保存过来的,大家自由发fu挥zhi
在/static/css
中新建mycss.css
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/up.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url(/static/img/down.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
.clear {
clear: both;
}
.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: #808080;
}
六 点赞点踩功能完成
js
<script>
$(".action").click(function () {
var is_up = $(this).hasClass('diggit')
var span = $(this).children('span')
$.ajax({
url: '/upanddown/',
method: 'post',
data: {
article_id: '{{ article.id }}',
is_up: is_up,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (data) {
console.log(data)
$('#digg_tips').html(data.msg)
if (data.code == 100) {
//点赞或者点踩的数字加一
var num = Number(span.html()) + 1
span.html(num)
}
}
})
})
</script>
后端
def upanddown(request):
res = {'code': 100, 'msg': ''}
if request.user.is_authenticated: # 如果登陆
# article表中数字加1,在点赞点踩表中记录一条
# 这个人对该文章只能点赞或者点踩一次
# 先查一下,如果有记录了,就不能再点了
article_id = request.POST.get('article_id') # 获取文章id
user_id = request.user.id # 获取当前用户id
is_up = request.POST.get('is_up') # is_up是一个字符串
print(type(is_up)) # 传过来的是字符串而不是布尔
# if is_up=='true': # 方式一:通过字符串校验
# is_up=True
# else:
# is_up = False
is_up=json.loads(is_up) # 方式二:通过json将字符串转化成布尔
print(type(is_up))
res_1 = models.UpAndDown.objects.filter(article_id=article_id, user_id=user_id).count() # 查询用户是否对该文章有操作记录
if res_1: # 有过
res['code'] = 101
res['msg'] = '已经点过了'
else: # 没有过
with transaction.atomic(): # 事务
models.UpAndDown.objects.create(article_id=article_id, user_id=user_id, is_up=is_up) # 新增字段(操作记录,is_up为false时代表点的是踩)
if is_up:
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
res['msg'] = '点赞成功' # 准备给ajax的消息
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
res['msg'] = '点踩成功' # 准备给ajax的消息
else: # 没有登陆
res['code'] = 109
res['msg'] = '请先<a href="/login/">登录</a>' # 准备给ajax的消息
return JsonResponse(res) # 将消息发送给ajax
新增点赞点踩后的文章页面(将上面代码整合的完全体)
{% extends 'base.html' %}
{% block title %}
{{ article.title }}
{% endblock %}
{% block css %}
<link rel="stylesheet" href="/static/css/mycss.css">
{% endblock %}
{% block content %}
<div>
<h3 class="text-center">{{ article.title }}</h3>
<hr>
<div>
{{ article.content }}
</div>
<!--
点赞点踩
-->
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article.up_num }}</span>
</div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;">
</div>
</div>
<!--
评论
-->
<div></div>
</div>
{% endblock %}
{% block script %}
<script>
// 点赞跟点踩按钮都有action类,所以无论点击哪个都会触发
$(".action").click(function () {
// 当该按钮同时含有diggit类的时候赋值True
var is_up = $(this).hasClass('diggit')
// 将该按钮下的span子标签取出
var span = $(this).children('span')
$.ajax({
url: '/upanddown/',
method: 'post',
data: {
article_id: '{{ article.id }}', // 但概念文章id
is_up: is_up, // 点赞时是True,点踩时是False
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (data) {
console.log(data)
// 接收过来的文件data.msg,分很多情况,提示点过了、提示尚未登陆、提示点赞成功、提示点踩成功
$('#digg_tips').html(data.msg) // 将提示信息渲染到页面上
if (data.code == 100) {
//点赞或者点踩的数字加一,因为var span赋值时就对应着当前点击的那个按钮,所以对应也会给那个按钮数字加一
var num = Number(span.html()) + 1 // 获取当前数字并加一
span.html(num) // 将修改后的数字渲染顶替之前的数字
}
}
})
})
</script>
{% endblock %}