文章详情页面
本功能需求:
-文章详情页展示
-点赞点踩功能(同一用户只能点一次)
-评论功能(包含子评论)
拓展功能:
-引入md编辑器
一、添加路由
文章详情 urls.py
re_path('^(?P<name>\w+)/article/(?P<id>\d+).html$', views.article_detail)
点赞点踩功能urls.py
path('upanddown/', views.upanddown, name='upanddown')
评论功能 urls.py
path('comment/', views.comment, name='comment')
二、添加视图函数
文章详情展示功能视图函数article_detail
def article_detail(request, name, id): user = models.UserInfo.objects.get(username=name) article = models.Article.objects.get(id=id) md = markdown.Markdown(extensions=[ 'markdown.extensions.extra', 'markdown.extensions.codehilite', 'markdown.extensions.toc', ]) content = md.convert(article.markdown) toc = md.toc # article.toc = article.markdown.toc n = content.count('<div class="codehilite">', 0, len(content)) for i in range(n): content = re.sub(r'<div class="codehilite">', '<button id="ecodecopy" class="copybtn btn btn-outline-light btn-sm" ' 'data-clipboard-action="copy" ' 'data-clipboard-target="#code{}">复制</button> ' '<div class="codehilite" id="code{}">'.format(i, i), content, 1) comment_list = models.Comment.objects.filter(article=article) return render(request, 'article/Article_Detail.html', locals())
点赞点踩功能 视图函数 upanddown
@login_required(login_url='/login/') def upanddown(request): res = {'code': 100, 'msg': ''} if request.user.is_authenticated: article_id = request.POST.get('article_id') user_id = request.user.id is_up = json.loads(request.POST.get('is_up')) article_obj = models.Article.objects.filter(pk=article_id).first() clicked = models.UpAndDown.objects.filter(article_id=article_id, user_id=user_id).first() if not request.user == article_obj.blog.userinfo: if clicked: res['code'] = 101 res['msg'] = '您已经支持过' if clicked.is_up else '您已经反对过' else: 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) res['msg'] = '点赞成功' else: models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1) res['msg'] = '点踩成功' else: res['code'] = 103 res['msg'] = '不能推荐自己的内容' if is_up else '不能反对自己的内容' else: res['code'] = 104 res['msg'] = '请先<a href="/login/">登录</a>' return JsonResponse(res)
评论功能视图函数 comment
@login_required(login_url='/login/') def comment(request): res = {'code': 100, 'msg': ''} if request.is_ajax(): article_id = request.POST.get('article_id') content = request.POST.get('content') parent = request.POST.get('parent') if request.user.is_authenticated: article = models.Comment.objects.create(user=request.user, article_id=article_id, content=content, comment_id_id=parent) models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1) res['msg'] = '评论成功' res['username'] = article.user.username res['content'] = article.content if parent: res['parent_name'] = article.comment_id.user.username else: res['code'] = 109 res['msg'] = '请先登录' return JsonResponse(res)
三、文章详情功能涉及的模型层操作
显然,渲染文章详情需要操作文章表Article
点赞点踩要操作表UpAndDown,评论要操作表Comment
尤其需要注意的是,因为评论可能涉及子评论,所以评论表还有子关联的一对多关系
四、文章详情功能前端模板
文章详情页面 在templates文件夹中新建Article_Detail.html(继承前面提到过的基础模板base.html)
{% extends 'template_base/base.html' %} {% block title %} {{ article.title }} {% endblock %} {% block content %} <div class="row"> <div class="col-md-2"> {% load sideBar %} {% left name %} </div> <div class="col-md-8"> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> 当前位置: <li class="breadcrumb-item"><a href="/index/">首页</a></li> <li class="breadcrumb-item"><a href="/{{ article.blog.userinfo.username }}">{{ article.blog.title }}</a> </li> <li class="breadcrumb-item active" aria-current="page">{{ article.title }}</li> {% if request.user.username == article.blog.userinfo.username %} <a href="/update_article/{{ article.pk }}" class="ml-auto btn btn-outline-secondary btn-sm m-0">编辑</a> {% endif %} </ol> </nav> <div class="row mx-1"> <span class="alert alert-warning col-md-4"><strong>发布于:</strong>{{ article.create_time }}</span> <span class="alert alert-info col-md-4"><strong>当前时间:</strong><span id="c_time"></span></span> <span class="alert alert-success col-md-4"><strong>更新于:</strong>{{ article.modify_time }}</span> </div> <div class="bg-white-95 radius-5 p-2"> <h1 class="text-center">{{ article.title }}</h1> <hr> <div class="mb-2 article-content"> {{ content|safe }} </div> <!-- 点赞点踩 --> {% if request.user.username %} <hr> <div class="row"> <div id="div_digg" class="ml-auto mr-4"> <div class="diggit action btn btn-success"> <span class="diggnum" id="digg_count"> <i class="fa fa-thumbs-o-up"></i> <span>{{ article.up_num }}</span> </span> </div> <div class="buryit action btn btn-danger"> <span class="burynum" id="bury_count"> <i class="fa fa-thumbs-o-down"></i> <span>{{ article.down_num }}</span> </span> </div> </div> </div> {% endif %} <hr> <!-- 评论 --> <div style="width: 100%" class="mt-2"> <h1 class="text-center">评论区</h1> <ul class="list-group"> {% for comment in comment_list %} <li class="list-group-item"> <div class="p-0"> <span>#{{ forloop.counter }}楼</span> <span>{{ comment.comment_time|date:'Y-m-d H:i:s' }}</span> <span class="">用户:<a href="/{{ comment.user.username }}">{{ comment.user.username }}</a> </span> {% if request.user.username %} <button class="id_replay btn btn-outline-secondary btn-sm ml-auto d-block" username="{{ comment.user.username }} " style="margin-top: -28px" parent="{{ comment.pk }}">回复 </button> {% endif %} </div> <hr class="my-2"> <div> {% if comment.comment_id_id %} <p>@{{ comment.comment_id.userinfo.username }}</p> <p>{{ comment.content }}</p> {% else %} {{ comment.content }} {% endif %} </div> </li> {% empty %} <div class="alert alert-warning"><h2 class="text-center">当前暂无评论内容</h2></div> {% endfor %} </ul> </div> {% if request.user.is_authenticated %} <div class="mt-2"> <p><textarea name="" class="width-b100 radius-5 p-2" id="id_text" rows="10" placeholder="来都来了,说两句呗!" required></textarea></p> <button class="btn btn-success btn-block mb-2" id="id_comment">发表评论</button> </div> {% else %} <div class="mt-2 ml-1 alert alert-warning"> 登录后才能发表评论,立即 <a href="/login/" class="btn btn-outline-primary btn-sm">登录</a> 或 <a href="/register/" class="btn btn-outline-success btn-sm">注册</a>, 访问 网站首页 </div> {% endif %} </div> </div> <div class="col-md-2 p-0"> {{ toc|safe }} </div> </div> {% endblock %} {% block js %} <script src="/static/js/highlight.js"></script> <script src="/static/js/highlight-lines.js"></script> <script src="/static/js/clipboard.js"></script> <script> hljs.initHighlightingOnLoad(); hljs.initLineNumbersOnLoad(); </script> <script> var clipboard = new Clipboard('.copybtn'); clipboard.on('success', function (e) { swal({ title: '复制成功!', text: '如需转载,请注明出处', type: 'success', timer: 1000, showConfirmButton: false, }) }); clipboard.on('error', function (e) { swal({ title: '复制失败!', text: '在试一试吧!', type: 'danger', timer: 1000, showConfirmButton: false, }) }); </script> <script> //页面加载调用 window.onload = function () { //每1秒刷新时间 setInterval("NowTime()", 1000); } function NowTime() { var myDate = new Date(); var y = myDate.getFullYear(); var M = myDate.getMonth() + 1; //获取当前月份(0-11,0代表1月) var d = myDate.getDate(); //获取当前日(1-31) var h = myDate.getHours(); //获取当前小时数(0-23) var m = myDate.getMinutes(); //获取当前分钟数(0-59) var s = myDate.getSeconds(); //获取当前秒数(0-59) //检查是否小于10 M = check(M); d = check(d); h = check(h); m = check(m); s = check(s); var timestr = y + "年" + M + "月" + d + "日 " + h + ":" + m + ":" + s; document.getElementById("c_time").innerHTML = timestr; } //时间数字小于10,则在之前加个“0”补位。 function check(i) { var num = (i < 10) ? ("0" + i) : i; return num; } </script> <script> var parent_id = '' $(".action").click(function () { var is_up = $(this).hasClass('diggit') var span = $(this).children('span').children('span') $.ajax({ url: '/upanddown/', method: 'post', data: { article_id: '{{ article.id }}', is_up: is_up, csrfmiddlewaretoken: '{{ csrf_token }}' }, success: function (data) { swal({ title: data.msg, }) if (data.code === 100) { var num = Number(span.html()) + 1 span.html(num) } } }) }) $('#id_comment').click(function () { let content = $('#id_text').val() if (parent_id) { let i = content.indexOf('\n') + 1 content = content.slice(i) } $.ajax({ url: '/comment/', method: 'post', data: { article_id: '{{ article.id }}', content: content, parent: parent_id, csrfmiddlewaretoken: '{{ csrf_token }}' }, success: function (data) { if (data.code === 100) { let username = data.username let res_content = data.content let parent_name = data.parent_name let ss = `` if (parent_id) { ss = `<li class="list-group-item"> <div> <span>${username}</span> </div> <div> <p>@${parent_name}</p> ${res_content} </div> </li>` } else { ss = `<li class="list-group-item"> <div> <span>${username}</span> </div> <div> ${res_content} </div> </li>` } $('#id_text').val('') $('.list-group').append(ss) parent_id = '' } } }) }) $('.id_replay').click(function () { let username = $(this).attr('username') parent_id = $(this).attr('parent') $('#id_text').val('@' + username + '\n').focus() }) </script> <script> $('body').attr('style', 'background:url(/media/{{ article.blog.userinfo.bg_img }}) !important;background-attachment: fixed !important;background-size: cover !important;') </script> {% endblock %}