一、路由
python
复制代码
from django.contrib import admin
from django.urls import path, re_path
from app01 import views
from django.views.static import serve
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
# 注册
path('register/', views.register),
# 登录
path('login/', views.login),
# 验证码
path('get_code/', views.get_code),
# 首页路由
path('home/', views.home),
# 退出系统
path('logout/', views.logout),
# 修改密码
path('set_password/', views.set_password),
# 放开media文件夹
re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
# 文章详情页
re_path('(?P<username>\w+)/(?P<article_id>\d+)', views.article_detail),
# 点赞点彩
path('up_and_down/', views.up_and_down),
# 评论功能
path('comment/', views.comment),
# re_path('(?P<username>\w+)/category/(\d+)', views.site),
# re_path('(?P<username>\w+)/tag/(\d+)', views.site),
# re_path('(?P<username>\w+)/archive/(\w+)', views.site),
# 优化以上三个路由
re_path('(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)', views.site),
re_path('(?P<username>\w+)', views.site),
]
二、文章详情页
1.前端
html
复制代码
{% extends 'home.html' %}
{% block css %}
<style>
.s1 {
margin-right: 10px;
color: #999;
}
.content {
font-size: 18px;
color: #444;
}
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 128px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/upup.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/downdown.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;
}
.clearfix:focus {
content: '';
display: block;
clear: both;
}
</style>
{% endblock %}
{% block content %}
<div class="col-md-3">
<div class="panel panel-info">
<div class="panel-heading">文章分类</div>
<div class="panel-body">
{% for category in category_list %}
<!-- 如果后台用的是values -->
<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }} ({{ category.1 }})</a></p>
<!-- 如果后台用的是values -->
{# <p><a href="/{{ username }}/category/{{ category.pk }}">{{ category.name }} ({{ category.count_article_num }})</a></p>#}
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">文章标签</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }} ({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">日期归档</div>
<div class="panel-body">
{% for date in date_list %}
{# <p><a href="">{{ date.0 }} ({{ date.1 }})</a></p> #}
<p><a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}">{{ date.month|date:'Y年m月' }}
({{ date.count_article_nums }})</a></p>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-9">
<h3 style="color: #399ab2;">{{ article_detail.title }}</h3>
<div class="content">
{{ article_detail.content|safe }}
</div>
</div>
{% endblock %}
{% block js %}
<script>
// 11647089,'Digg'分别表示文章id和标志flag
// 思路1:
{% comment %}function votePost(id, flag) {
is_up = flag === 'Digg' ? 0 : 1;
}{% endcomment %}
// 思路2:
$(".active").click(function () {
let is_up = $(this).hasClass('diggit');
// 文章id
var article_id = '{{ article_detail.pk }}';
var _this = $(this);
// 发起Ajax请求
$.ajax({
url: '/up_and_down/',
type: 'post',
data: {is_up: is_up, article_id: article_id, csrfmiddlewaretoken: '{{ csrf_token }}'},
success: function () {
// 将标签放入文本,要么是text,要么是html
if (res === 200) {
$("#digg_tips").text(res.msg);
// 如果是点赞就让点赞数加1,如果是点踩就让点踩数加1
// 注意:在text()中加数据就是赋值,空就是获取值
let old_num = _this.children().text();
// 此时的 old_num 是string类型,需要转类型
{#_this.children().text(parseInt(old_num)+1);#}
//或者使用 Number
_this.children().text(Number(old_num) + 1);
} else {
$("#digg_tips").html(res.msg);
}
},
});
});
</script>
{% endblock %}
2.后端
python
复制代码
# 文章详情页
def article_detail(request, username, article_id):
print(article_id) # 1
user_obj = models.UserInfo.objects.filter(username=username).first()
print(user_obj)
if not user_obj:
'''
图片防盗链:通过 Referer参数判断,
通过这个参数就可以知道你当前的地址是从哪个网页调过来的,然后做验证
'''
return render(request, '404.html')
# 查询用户自己的所有文章(过滤当前站点的文章)
blog = user_obj.blog
article_detail = models.Article.objects.filter(pk=article_id).first()
category_list = models.Category.objects.filter(blog=blog).annotate(
count_article_num=Count('article__pk')).values_list('name', 'count_article_num', 'pk')
tag_list = models.Tag.objects.filter(blog=blog).annotate(
count_article_num=Count('article__pk')).values_list('name', 'count_article_num', 'pk')
date_list = models.Article.objects.annotate(
month=TruncMonth('create_time')).values('month').filter(blog=blog).annotate(
count_article_nums=Count('pk')).values('month', 'count_article_nums')
# 查询所有的评论列表
comment_list = models.Comment.objects.filter(article_id=article_id).all()
return render(request, 'article_detail.html', locals())
三、点赞点菜
1.前端
html
复制代码
{% extends 'home.html' %}
{% block css %}
<style>
.s1 {
margin-right: 10px;
color: #999;
}
.content {
font-size: 18px;
color: #444;
}
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 128px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/upup.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/downdown.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;
}
.clearfix:focus {
content: '';
display: block;
clear: both;
}
</style>
{% endblock %}
{% block content %}
<div class="col-md-3">
<div class="panel panel-info">
<div class="panel-heading">文章分类</div>
<div class="panel-body">
{% for category in category_list %}
<!-- 如果后台用的是values -->
<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }} ({{ category.1 }})</a></p>
<!-- 如果后台用的是values -->
{# <p><a href="/{{ username }}/category/{{ category.pk }}">{{ category.name }} ({{ category.count_article_num }})</a></p>#}
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">文章标签</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }} ({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">日期归档</div>
<div class="panel-body">
{% for date in date_list %}
{# <p><a href="">{{ date.0 }} ({{ date.1 }})</a></p> #}
<p><a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}">{{ date.month|date:'Y年m月' }}
({{ date.count_article_nums }})</a></p>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-9">
<h3 style="color: #399ab2;">{{ article_detail.title }}</h3>
<div class="content">
{{ article_detail.content|safe }}
</div>
<!-- 点赞点彩样式开始 -->
<div class="clearfix">
<div id="div_digg">
<div class="diggit active" onclick="votePost(11647089,'Digg')">
<span class="diggnum" id="digg_count">{{ article_detail.up_num }}</span>
</div>
<div class="buryit active" onclick="votePost(11647089,'Bury')">
<span class="burynum" id="bury_count">{{ article_detail.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
<!-- 点赞点彩样式结束 -->
</div>
{% endblock %}
{% block js %}
<script>
// 11647089,'Digg'分别表示文章id和标志flag
// 思路1:
{% comment %}function votePost(id, flag) {
is_up = flag === 'Digg' ? 0 : 1;
}{% endcomment %}
// 思路2:
$(".active").click(function () {
let is_up = $(this).hasClass('diggit');
// 文章id
var article_id = '{{ article_detail.pk }}';
var _this = $(this);
// 发起Ajax请求
$.ajax({
url: '/up_and_down/',
type: 'post',
data: {is_up: is_up, article_id: article_id, csrfmiddlewaretoken: '{{ csrf_token }}'},
success: function () {
// 将标签放入文本,要么是text,要么是html
if (res === 200) {
$("#digg_tips").text(res.msg);
// 如果是点赞就让点赞数加1,如果是点踩就让点踩数加1
// 注意:在text()中加数据就是赋值,空就是获取值
let old_num = _this.children().text();
// 此时的 old_num 是string类型,需要转类型
{#_this.children().text(parseInt(old_num)+1);#}
//或者使用 Number
_this.children().text(Number(old_num) + 1);
} else {
$("#digg_tips").html(res.msg);
}
},
});
});
</script>
{% endblock %}
2.后端
python
复制代码
# 点赞点彩
def up_and_down(request):
'''
分析点赞点彩的实现逻辑:
1.必须判断用户是否登陆了。如果没有则在前端页面显示登录
2.若是第一次登录:
2.1 点赞数加 1
2.2 在页面上显示点赞成功
3.如果已经点击过,就提示不让他再点了
4.如果是第一次点击,应该在处理哪些逻辑
4.1 肯定需要在点赞点彩表中增加一条记录
4.2 还需要更新文章中的up_num或者down_num字段
5. 取消点赞或者点彩功能-----》收藏
:param request:
:return:
'''
if request.method == 'POST':
back_dict = {'code': 200, 'msg': '支持成功'}
# 1.接收参数
is_up = request.POST.get('is_UP') # str
is_up = json.loads(is_up)
article_id = request.POST.get('article_id')
# 2.判断用户是否登录
if not request.session.get('username'):
back_dict['code'] = 1400
back_dict['msg'] = '请先<a href="/login/" style="color: red;">登录</a>'
return JsonResponse(back_dict)
# 3.验证参数
# 4.判断是否已经点赞过了
res = models.UpAndDown.objects.filter(article_id=article_id, user_id=request.session.get('id')).first()
if res:
back_dict['code'] = 1401
back_dict['msg'] = '你已经支持过了'
return JsonResponse(back_dict)
# 5.处理业务逻辑
# 操作up_and_down, article
if is_up:
models.Article.objects.create(pk=article_id).update(up_num=F('up_num') + 1)
else:
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
back_dict['msg'] = '支持成功'
# 查询出来要么点赞,要么点踩
models.UpAndDown.objects.create(is_up=is_up, article_id=article_id, user_id=request.session.get('id'))
return JsonResponse(back_dict)
四、评论功能
1.前端
html
复制代码
{% extends 'home.html' %}
{% block css %}
<style>
.s1 {
margin-right: 10px;
color: #999;
}
.content {
font-size: 18px;
color: #444;
}
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 128px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/img/upup.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/downdown.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;
}
.clearfix:focus {
content: '';
display: block;
clear: both;
}
</style>
{% endblock %}
{% block content %}
<div class="col-md-3">
<div class="panel panel-info">
<div class="panel-heading">文章分类</div>
<div class="panel-body">
{% for category in category_list %}
<!-- 如果后台用的是values -->
<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }} ({{ category.1 }})</a></p>
<!-- 如果后台用的是values -->
{# <p><a href="/{{ username }}/category/{{ category.pk }}">{{ category.name }} ({{ category.count_article_num }})</a></p>#}
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">文章标签</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }} ({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">日期归档</div>
<div class="panel-body">
{% for date in date_list %}
{# <p><a href="">{{ date.0 }} ({{ date.1 }})</a></p> #}
<p><a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}">{{ date.month|date:'Y年m月' }}
({{ date.count_article_nums }})</a></p>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-9">
<h3 style="color: #399ab2;">{{ article_detail.title }}</h3>
<div class="content">
{{ article_detail.content|safe }}
</div>
<!-- 点赞点彩样式开始 -->
<div class="clearfix">
<div id="div_digg">
<div class="diggit active" onclick="votePost(11647089,'Digg')">
<span class="diggnum" id="digg_count">{{ article_detail.up_num }}</span>
</div>
<div class="buryit active" onclick="votePost(11647089,'Bury')">
<span class="burynum" id="bury_count">{{ article_detail.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
<!-- 点赞点彩样式结束 -->
<!-- 评论列表的展示 -->
<div class="comment_list">
<h3><span class="glyphicon glyphicon-comment"></span>评论列表</h3>
<ul class="list-group">
{% for comment in comment_list %}
<li class="list-group-item">
<span style="margin-right: 10px;"># {{ forloop.counter }}楼</span>
<span style="margin-right: 10px;">{{ comment.comment_time }}</span>
<!-- 子评论 -->
{# <span style="margin-right: 10px;">{{ comment.parent.user.username }}</span>#}
<!-- 根评论 -->
<span style="margin-right: 10px;">{{ comment.user.username }}</span>
<!-- 若是页面有滚动轮,则 a 标签中的 href 不能用 #,否则页面会调到开头,应该用javascript:; 这是因为锚点的缘故 -->
<span style="margin-right: 10px;" class="pull-right">
<a href="javascript:;" comment_username="{{ comment.user.username }}"
comment_id="{{ comment.pk }}" class="repay">回复</a>
</span>
<div class="content" style="margin-left: 14px;">
{% if comment.parent %}
{{ comment.content }}
{% else %}
<!-- @用户名 评论的内容 -->
<p>@ {{ comment.parent.user.username }}</p>
<p>{{ comment.content }}</p>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
</div>
<!-- 评论列表的展示结束 -->
<!-- 评论功能开始 -->
<div class="comment">
<p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
<p>
<textarea name="" id="content" cols="30" rows="10"></textarea>
</p>
<p>
<button class="btn btn-success comment_content">提交评论</button>
</p>
</div>
<!-- 评论功能结束 -->
</div>
{% endblock %}
{% block js %}
<script>
// 11647089,'Digg'分别表示文章id和标志flag
// 思路1:
{% comment %}function votePost(id, flag) {
is_up = flag === 'Digg' ? 0 : 1;
}{% endcomment %}
// 思路2:
$(".active").click(function () {
let is_up = $(this).hasClass('diggit');
// 文章id
var article_id = '{{ article_detail.pk }}';
var _this = $(this);
// 发起Ajax请求
$.ajax({
url: '/up_and_down/',
type: 'post',
data: {is_up: is_up, article_id: article_id, csrfmiddlewaretoken: '{{ csrf_token }}'},
success: function () {
// 将标签放入文本,要么是text,要么是html
if (res === 200) {
$("#digg_tips").text(res.msg);
// 如果是点赞就让点赞数加1,如果是点踩就让点踩数加1
// 注意:在text()中加数据就是赋值,空就是获取值
let old_num = _this.children().text();
// 此时的 old_num 是string类型,需要转类型
{#_this.children().text(parseInt(old_num)+1);#}
//或者使用 Number
_this.children().text(Number(old_num) + 1);
} else {
$("#digg_tips").html(res.msg);
}
},
});
});
// 定义一个全局变量 parent_id
// 若为空则是根评论,有值就是子评论
var parent_id = null;
<!-- 评论功能开始 -->
$(".comment_content").click(function () {
// 1.获取参数
var article_id = '{{ article_detail.pk }}';
// 2.获取评论的内容
var content = $('#content').val();
// 3.判断是根评论还是子评论
if (parent_id) {
// 有值就是子评论
// indexOf 若匹配到了则返回它在字符串中的位置
let sub_number = content.indexOf('\n' + '');
// 拿到截取之后的内容
content = content.slice(sub_number);
}
$.ajax({
url: '/comment/',
type: 'post',
data: {
article_id: article_id,
content: content,
parent_id: parent_id,
csrfmiddlewaretoken: '{{ csrf_token }}'
},
success: function (res) {
// 做评论内容的临时渲染
// 注意:反引号是 ES6语法中的模板语法
var username = '{{ request.session.username }}';
var html = `<li class="list-group-item">
<span style="margin-right: 10px;"><span class="glyphicon glyphicon-comment"></span>${username}</span>
<div class="content" style="margin-left: 14px;">
${content}
</div>
</li>`
$(".list-group").append(html)
//清空内容
$('#content').val('')
},
});
});
<!-- 评论功能结束 -->
<!-- 子评论功能开始 -->
$(".repay").click(function () {
// 获取自定义属性comment_username
let comment_username = $(this).attr('comment_username');
// 获取子评论的 parent_id
parent_id = $(this).attr("comment_id");
// 根据评论框 textarea的id属性,设置回复格式焦点,并且换行
$("#content").val('@' + comment_username + '\n').focus();
});
<!-- 子评论功能结束 -->
</script>
{% endblock %}
2.后端
python
复制代码
# 评论功能
def comment(request):
'''
分析评论的逻辑:
1.登录之后才能评论
2.评论的内容要入库
1.操作文章表,
2.评论表
:param request:
:return:
'''
back_dict = {'code': 200, 'msg': '支持成功'}
# 1.接收参数
article_id = request.POST.get('article_id')
content = request.POST.get('content')
parent_id = request.POST.get('parent_id')
# 2.判断用户是否登录
if not request.session.get('username'):
back_dict['code'] = 1404
back_dict['msg'] = '请先登录之后再评论'
return JsonResponse(back_dict)
# 加事务
from django.db import transaction
try:
with transaction.atomic():
# 操作文章表
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
# 操作评论表
models.Comment.objects.create(content=content, article_id=article_id,
parent_id=parent_id, user_id=request.session.get('id'))
except:
# 加入日志
...
transaction.rollback()
return JsonResponse(back_dict)