BBS第四天之文章详情,点赞点踩,根子评论,后台展示,文章添加(富文本编辑器)
一.文章详情
路由url.py:
url(r'^(?P<username>\w+)/article/(?P<pk>\d+)$', views.article_detail),
点击个人站点文章title即可关联到文章详情路由找到其视图函数
views.py:
def article_detail(request,username,pk): user = models.UserInfo.objects.filter(username=username).first() if not user: return render(request, 'error.html') blog = user.blog category_ret=models.Category.objects.all().filter(blog=blog).annotate(cou=Count('article__nid')).values_list('title','cou','nid') tag_ret=models.Tag.objects.all().filter(blog=blog).annotate(cou=Count('article__nid')).values_list('title','cou','nid') year_ret=models.Article.objects.all().annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('nid')).values_list('month','c') article=models.Article.objects.filter(nid=pk).first() # commit_list=article.commit_set.all() 前台模板渲染也可获取评论列表 return render(request,'article_detail.html',locals())
ps: 通过有名分组拿到用户名和文章id,找到该文章对象渲染到article_detail.html (用到了母版base.html)
base.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ blog.title }}的个人站点</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<link rel="stylesheet" href="/static/css/{{ blog.theme }}">
<script src="/static/jquery-3.3.1.js"></script>
{% block mycss %}
{% endblock %}
</head>
<body>
<div class="head">
{{ blog.site_name }}
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="panel panel-danger">
<div class="panel-heading">我的标签</div>
<div class="panel-body">
{% for tag in tag_ret %}
<p><a href="/{{ user.username }}/tag/{{ tag.2 }}">{{ tag.0 }}({{ tag.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">随笔分类</h3>
</div>
<div class="panel-body">
{% for category in category_ret %}
<p>
<a href="/{{ user.username }}/category/{{ category.2 }}">{{ category.0 }}({{ category.1 }})</a>
</p>
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">随笔档案</h3>
</div>
<div class="panel-body">
{% for year in year_ret %}
<p>
<a href="/{{ user.username }}/archive/{{ year.0|date:"Y-m" }}">{{ year.0|date:"Y年m月" }}({{ year.1 }})</a>
</p>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-9">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</body>
</html>
article_detail.html:
{% extends 'base.html' %}
{% block mycss %}
<link rel="stylesheet" href="/static/css/commoncss.css">
{% endblock %}
{% block content %}
<div>
<h4>{{ article.title }}</h4>
<div>
{{ article.content|safe }}
</div>
<div class="clearfix">
<div id="div_digg">
<div class="diggit upanddown">
<span class="diggnum" id="digg_count">{{ article.up_num }}</span>
</div>
<div class="buryit upanddown">
<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>
{#评论列表#}
<p>评论列表</p>
<ul class="list-group commit_content">
{% for commit in article.commit_set.all %}
<li class="list-group-item">
<p>
<span>#{{ forloop.counter }} 楼</span>
<span>{{ commit.create_time|date:"Y-m-d H:i" }}</span>
<span>{{ commit.user.username }}</span>
<span class="pull-right reply" username="{{ commit.user.username }}"
commit_id="{{ commit.pk }}"><a>回复</a></span>
</p>
{% if commit.parent %}
<p class="well">@{{ commit.parent.user.username }}----{{ commit.parent.content }}</p>
{% endif %}
{{ commit.content }}
</li>
{% endfor %}
</ul>
</div>
<div>
{#发表评论#}
<p>发表评论</p>
<p>
昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
value="{{ request.user.username }}">
</p>
<p>评论内容</p>
<textarea name="" id="id_textarea" cols="80" rows="10">
</textarea>
<p>
<button class="btn btn-success" id="btn_submit">提交</button>
</p>
</div>
</div>
</div>
<script>
var parent_id = ''
//评论相关
$("#btn_submit").click(function () {
var content = $("#id_textarea").val()
if (parent_id) {
//有值,截取前面的@姓名
//indexof 截取 \n的索引位置
var index = content.indexOf('\n') + 1
content = content.slice(index)
}
//谁对那篇文章评论了什么内容
$.ajax({
url: '/commit/',
type: 'post',
data: {
article_id: '{{article.pk}}',
content: content,
csrfmiddlewaretoken: '{{ csrf_token }}',
parent_id: parent_id
},
success: function (data) {
console.log(data)
//清除输入框的数据
$("#id_textarea").val("")
if (data.code == 100) {
var username = data.username
var reply_content = data.reply_content
//往后追加内容
if (parent_id) {
var parent_name = data.parent_name
var s = `
<li class="list-group-item">
<p>
<span>${username}</span>
</p>
<p class="well">@${parent_name}</p>
${reply_content}
</li>
`
} else {
//追加根评论的内容
//es6的字符串替换
var s = `
<li class="list-group-item">
<p>
<span>${username}</span>
</p>
${reply_content}
</li>
`
}
$(".commit_content").append(s)
}
}
})
})
//回复相关
$(".reply").click(function () {
var username = '@' + $(this).attr('username') + '\n'
parent_id = $(this).attr('commit_id')
//光标聚焦到该控件上
$("#id_textarea").focus()
$("#id_textarea").val(username)
})
//点赞点踩相关
$(".upanddown").click(function () {
//当前点击控件有没有diggit 这个类
var is_up = $(this).hasClass('diggit')
//拿到当前点击控件子控件的span标签对象
var cu_span = $(this).children('span')
//alert(is_up)
//谁对哪篇文章点赞或点踩
$.ajax({
url: '/diggit/',
type: 'post',
data: {article_id: '{{article.pk}}', is_up: is_up, csrfmiddlewaretoken: '{{ csrf_token }}'},
success: function (data) {
console.log(data)
$("#digg_tips").html(data.msg)
if (data.code == 100) {
{#cu_span.text(cu_span.text()+1)#}
//在当前点击的div下的span标签上数字加以
cu_span.text(Number(cu_span.text()) + 1)
}
}
})
})
</script>
{% endblock %}
文章详情展示了以下内容: 文章title, 文章content (传html文件,用safe转义),点赞点踩,评论列表,评论内容:
a.点赞点踩:
<div class="clearfix"> <div id="div_digg"> <div class="diggit upanddown"> <span class="diggnum" id="digg_count">{{ article.up_num }}</span> </div> <div class="buryit upanddown"> <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>
js:
//点赞点踩相关 $(".upanddown").click(function () { //当前点击控件有没有diggit 这个类 var is_up = $(this).hasClass('diggit') //拿到当前点击控件子控件的span标签对象 var cu_span = $(this).children('span') //alert(is_up) //谁对哪篇文章点赞或点踩 $.ajax({ url: '/diggit/', type: 'post', data: {article_id: '{{article.pk}}', is_up: is_up, csrfmiddlewaretoken: '{{ csrf_token }}'}, success: function (data) { console.log(data) $("#digg_tips").html(data.msg) if (data.code == 100) { {#cu_span.text(cu_span.text()+1)#} //在当前点击的div下的span标签上数字加以 cu_span.text(Number(cu_span.text()) + 1) } } })
点赞点踩视图函数: 开启事务,将点赞点踩表创建记录的同时,把文章表的点赞或点踩数也加一
import json from django.db.models import F def diggit(request): response={'code':100,'msg':None} #当前登陆用户id if request.user.is_authenticated(): user_id=request.user.nid is_up=request.POST.get('is_up') print(type(is_up)) print(is_up) is_up=json.loads(is_up) print(type(is_up)) print(is_up) article_id=request.POST.get('article_id') up_ret=models.UpAndDown.objects.filter(user_id=user_id,article_id=article_id).first() if up_ret: response['code'] = 102 response['msg'] = '您已经点过了' else: #事务性的操作 from django.db import transaction #开启事务 with transaction.atomic(): models.UpAndDown.objects.create(article_id=article_id,user_id=user_id,is_up=is_up) if is_up: #文章表点赞字段加一 models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1) response['msg'] = '点赞成功' else: models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1) response['msg'] = '点踩成功' else: response['code']=101 response['msg']='请先登陆' return JsonResponse(response,safe=False)
b.评论列表:
<div> {#评论相关#} <div> {#评论列表#} <p>评论列表</p> <ul class="list-group commit_content"> {% for commit in article.commit_set.all %} <li class="list-group-item"> <p> <span>#{{ forloop.counter }} 楼</span> <span>{{ commit.create_time|date:"Y-m-d H:i" }}</span> <span>{{ commit.user.username }}</span> <span class="pull-right reply" username="{{ commit.user.username }}" commit_id="{{ commit.pk }}"><a>回复</a></span> </p> {% if commit.parent %} <p class="well">@{{ commit.parent.user.username }}----{{ commit.parent.content }}</p> {% endif %} {{ commit.content }} </li> {% endfor %} </ul> </div>
c.发表评论:
<div> {#发表评论#} <p>发表评论</p> <p> 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user.username }}"> </p> <p>评论内容</p> <textarea name="" id="id_textarea" cols="80" rows="10"> </textarea> <p> <button class="btn btn-success" id="btn_submit">提交</button> </p> </div>
js:
var parent_id = '' //评论相关 $("#btn_submit").click(function () { var content = $("#id_textarea").val() if (parent_id) { //有值,截取前面的@姓名 //indexof 截取 \n的索引位置 var index = content.indexOf('\n') + 1 content = content.slice(index) } //谁对那篇文章评论了什么内容 $.ajax({ url: '/commit/', type: 'post', data: { article_id: '{{article.pk}}', content: content, csrfmiddlewaretoken: '{{ csrf_token }}', parent_id: parent_id }, success: function (data) { console.log(data) //清除输入框的数据 $("#id_textarea").val("") if (data.code == 100) { var username = data.username var reply_content = data.reply_content //往后追加内容 if (parent_id) { var parent_name = data.parent_name var s = ` <li class="list-group-item"> <p> <span>${username}</span> </p> <p class="well">@${parent_name}</p> ${reply_content} </li> ` } else { //追加根评论的内容 //es6的字符串替换 var s = ` <li class="list-group-item"> <p> <span>${username}</span> </p> ${reply_content} </li> ` } $(".commit_content").append(s) } } }) }) //回复相关 $(".reply").click(function () { var username = '@' + $(this).attr('username') + '\n' parent_id = $(this).attr('commit_id') //光标聚焦到该控件上 $("#id_textarea").focus() $("#id_textarea").val(username) })
评论视图函数:开启事务,在评论表添加记录的同时,也在文章表添加评论数
def commit(request): response = {'code': 100, 'msg': None} # 当前登陆用户id if request.user.is_authenticated(): user_id = request.user.nid article_id = request.POST.get('article_id') content=request.POST.get('content') parent_id=request.POST.get('parent_id') # 事务性的操作 from django.db import transaction # 开启事务 with transaction.atomic(): ret=models.Commit.objects.create(article_id=article_id,content=content,user_id=user_id,parent_id=parent_id) models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1) response['username']=ret.user.username response['reply_content']=ret.content if parent_id: response['parent_name'] = ret.parent.user.username response['msg'] = '评论成功' else: response['code'] = 101 response['msg'] = '请先登陆' return JsonResponse(response, safe=False)
二.后台展示,添加文章
路由url.py:
# 后台管理首页 url(r'^backend/', views.home_backend),
视图函数:
from django.contrib.auth.decorators import login_required @login_required(login_url='/login/') def home_backend(request): #查询该人的所有文章 ariticle_list=models.Article.objects.filter(blog=request.user.blog) return render(request,'backend/home_backend.html',locals())
前端渲染母版backend_base.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>后台管理</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
<script src="/static/jquery-3.3.1.js"></script>
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
</head>
<body>
<div class="head" style="height: 60px;background-color: #2b669a">
<p>后台管理</p>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
aria-expanded="true" aria-controls="collapseOne">
文章管理
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<a href="/add_article/">添加文章</a>
跳到添加文章路由
</div>
</div>
<div id="xxx" class="panel-collapse collapse in" role="tabpanel"
aria-labelledby="headingOne">
<div class="panel-body">
<a href="">添加随笔</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div>
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
data-toggle="tab">文章</a></li>
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab"
data-toggle="tab">随笔</a>
</li>
<li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
data-toggle="tab">交友</a>
</li>
<li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
data-toggle="tab">相册</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="home">
{% block content %}
{% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="profile">随笔</div>
<div role="tabpanel" class="tab-pane" id="messages">交友</div>
<div role="tabpanel" class="tab-pane" id="settings">相册</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
后台管理前端home_backend.html:
{% extends 'backend/backend_base.html' %}
{% block content %}
<table class="table table-striped">
<thead>
<tr>
<th>标题</th>
<th>评论数</th>
<th>点赞数</th>
<th>操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for ariticle in ariticle_list %}
<tr>
<td><a href="/{{ request.user.username }}/article/{{ ariticle.pk }}">{{ ariticle.title }}</a></td>
跳到文章详情路由
<td>{{ ariticle.commit_num }}</td>
<td>{{ ariticle.up_num }}</td>
<td><a href="">删除</a></td>
<td><a href="">编辑</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
添加文章路由:
# 添加文章 url(r'^add_article/', views.add_article),
添加文章视图函数:(用bs4来解析html文档,删除script标签内容来防止xss
@login_required(login_url='/login/') def add_article(request): if request.method=='GET': return render(request,'backend/add_article.html') else: title=request.POST.get('title') text_content=request.POST.get('text_content') #通过bs4 处理xss攻击,html文档解析库 #pip3 install beautifulsoup4 from bs4 import BeautifulSoup soup=BeautifulSoup(text_content,'html.parser') #查找所有的标签 tags=soup.find_all() for tag in tags: if tag.name=='script': #从文档中删除该标签 tag.decompose() #soup.text 文档的内容,不包含标签 desc=soup.text[0:150] models.Article.objects.create(title=title,desc=desc,content=str(soup),blog=request.user.blog) return redirect('/backend/')
添加文章add_article.html(借助富文本编辑器):
{% extends 'backend/backend_base.html' %}
{% block content %}
<div>
<p>添加文章</p>
<form action="/add_article/" method="post">
{% csrf_token %}
<div class="form-group">
<label for="id_name">文章标题</label>
<input type="text" name="title" id="id_title" class="form-control">
</div>
<p>内容(KindEditor编辑器,不支持拖放/粘贴上传图片) </p>
<textarea name="text_content" id="editor_id" cols="30" rows="10"></textarea>
<p>
<button class="btn btn-success">提交</button>
</p>
</form>
</div>
<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
width: '100%',
height: '400px',
resizeType: 0,
{#items: [#}
{# 'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',#}
{# 'plainpaste', 'wordpaste', '|'#}
{#]#}
uploadJson: '/uploadimg/',
extraFileUploadParams: {
csrfmiddlewaretoken: '{{ csrf_token }}',
}
});
});
</script>
{% endblock %}
在富文本编辑器中上传图片,图片地址放在media/file下即可:
@login_required(login_url='/login/') def uploadimg(request): response={ "error" : 0, "url" : None } fil=request.FILES.get('imgFile') with open('media/file/'+fil.name,'wb') as f: for line in fil: f.write(line) response['url']='/media/file/'+fil.name return JsonResponse(response)

浙公网安备 33010602011771号