打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Django blog项目《十一》:文章模块2 《文章列表、详情、评论、回复》

文章显示列表这里采用动态加载的方法,文章回复采用二级评论的方法来实现。

一、文章列表功能

1. urls.py配置

news/urls.py

from django.urls import pathfrom news import viewsapp_name = "news"urlpatterns = [    path("article_list/", views.ArticleView.as_view(), name="article_list"),]

2. 后端view逻辑处理

news/views.py

import loggingfrom django.views import Viewfrom django.shortcuts import renderfrom django.core.paginator import Paginator, EmptyPage, PageNotAnIntegerfrom news import contains  # 上一章已有from news import models as _modelsfrom utils.res_code.json_function import to_json_datalogger = logging.getLogger("django")class ArticleView(View):    def get(self, request):        # 1. 获取前端传来的数据文章类型id        try:            tag_id = int(request.GET.get("tag_id", 0))        except Exception as e:            logger.error("文章标签id参数错误:{}".format(e))            tag_id = 0        # 2. 获取前端传来的数据页码编号 page_num        try:            page_num = int(request.GET.get("page", 1))        except Exception as e:            logger.error("文章页码参数错误:{}".format(e))            page_num = 1        # 3. 从数据库中获取文章表签id为tag_id的数据        article_queryset_total = _models.Articles.objects.select_related("author", "tag").only("id", "title", "digest",                                                                                    "update_time",                                                                                    "clicks","image_url",                                                                                    "author__username",                                                                                    "tag__name").filter(is_delete=False)        article_tag_queryset = article_queryset_total.filter(is_delete=False, tag_id=tag_id) or                           article_queryset_total.filter(is_delete=False)        # 4. 对数据尽心分页        pagen = Paginator(article_tag_queryset,contains.PER_PAGE_DATA_NUMBER)  # 传递待分页对象 、每页多少个        # 5. 返回当页数据        try:            article_list = pagen.page(page_num)        except EmptyPage:            logger.error("访问页数超过总页数")            article_list = pagen.page(pagen.num_pages)        # 6. 数据序列化        article_per_page_list = []        for article in article_list:            article_per_page_list.append({                "id": article.id,                "title": article.title,                "digest": article.digest,                "update_time": article.update_time.strftime("%Y年%m月%d日 %H:%M"),                "clicks": article.clicks,                "image_url": article.image_url,                "author": article.author.username,                "tag_name": article.tag.name,            })        data = {            "total_page": pagen.num_pages,            "article": article_per_page_list        }        return to_json_data(data=data)

3. 前端js实现

js/news/index.js

$(function () {    let iPage = 1;  // 设定默认的page页码为1    let sCurrentTagId = 0; // 设定默认的tag_id为0    let $newsList = $(".news-nav ul li"); //获取到标签栏    let iTotalPage = 1;  //设定默认的总页数为1    let bIsLoadData = true;   //是否正在向后端传递参数    fn_load_content();   //调用向后端请求数据函数    $newsList.click(function () {        //点击分类标签,则为点击的标签加上一个class属性为active        //冰衣橱其他兄弟元素的上的值为active的class属性        $(this).addClass("active").siblings("li").attr("active");        //获取绑定在当前选中分类上的data-id属性值        let sClickTagId = $(this).children("a").attr("data-id");        if (sClickTagId !== sCurrentTagId) {            sCurrentTagId = sClickTagId; //记录当前的分类id            iPage = 1;            iTotalPage = 1;            fn_load_content()        }    });    // 滚动鼠标动态加载数据    $(window).scroll(function () {        // 浏览器窗口高度        let showHeight = $(window).height();        //整个网页的高度        let pageHeight = $(document).height();        //页面可以滚动的距离        let canScrollHeight = pageHeight - showHeight;        // 页面滚动了多少,整个是睡着页面滚动实时变化的        let nowScroll = $(document).scrollTop();        if ((canScrollHeight - nowScroll) < 100) {            //判断页数,去更新新闻数据            if (!bIsLoadData) {                bIsLoadData = true;                //如果当前页面数据如果小于总页数,那么才去加载数据                if (iPage < iTotalPage) {                    iPage += 1;                    $(".btn-more").remove(); //删除标签                    //去加载数据                    fn_load_content()                } else {                    alert("已经全部加载,没有更多内容了!");                    $(".btn-more").remove(); //删除标签                    $(".news-list").append($('<a href="javascript:void(0)" class="btn-more">已全部加载 </a>'))                }            }        }    });    // 向前端发送请求获取数据    function fn_load_content() {        //构建参数        let sDataParams = {            "page": iPage,            "tag_id": sCurrentTagId,        };        //发送ajax请求获取数据        $.ajax({            url: "/news/article_list",            type: "GET",            data: sDataParams,            dataType: "json",            success: function (res) {                if (res.errno === "200") {                    iTotalPage = res.data.total_page;  //获取到总页数                    if (iPage === 1) {                        $(".news-list").html("");                    }                    res.data.article.forEach(function (one_article) {                        let content = `                            <li class="news-item">                                <a href="/news/${ one_article.id }/" class="news-thumbnail"                                 target="_blank">                                    <img src="${ one_article.image_url }" alt="${ one_article.title }" title="${ one_article.title }">                                </a>                                <div class="news-content">                                    <h4 class="news-title">                                        <a href="/news/${ one_article.id }/">${ one_article.title }</a>                                    </h4>                                    <p class="news-details">${ one_article.digest }</p>                                    <div class="news-other">                                        <span class="news-type">${ one_article.tag_name }</span>                                        <span class="news-clicks">${ one_article.clicks }</span>                                        <span class="news-time">${ one_article.update_time}</span>                                        <span class="news-author">${ one_article.author}</span>                                    </div>                                </div>                            </li>                        `;                        $(".news-list").append(content)                    });                    $(".news-list").append($('<a href="javascript:void(0);" class="btn-more">点击加载更多</a>'));                    bIsLoadData = false                }                else {                    alert(res.errmsg)                }            },            error: function () {                alert("服务器请求超时,请重试")            }        })    }});

二、文章详情

1. urls.py配置

news/urls.py

from django.urls import pathfrom news import viewsapp_name = "news"urlpatterns = [    path("<int:article_id>/", views.ArticleDetailView.as_view(), name="article_detail"),]

2. 后端view逻辑处理

news/views.py

from django.views import Viewfrom django.shortcuts import renderfrom django.http import HttpResponseNotFoundfrom news import models as _modelsclass ArticleDetailView(View):    """    news detail    """    def get(self, request, article_id):        # 1. 从数据库Articles中获取id=article_id的数据:title、update_time、content、tag_name、author,        article = _models.Articles.objects.select_related("author", "tag").only("id", "title", "update_time", "content",                                                                                "tag__name",                                                                                "author__username").filter(is_delete=False, id=article_id).first()        # 2. 获取文章评论数据        comment_queryset_list = _models.Comments.objects.select_related("author", "parent").only("content",                                                                                                 "update_time",                                                                                                 "author__username",                                                                                                 "parent__author__username",                                                                                                 "parent__content",                                                                                                 "parent__update_time").filter(            is_delete=False, article_id=article_id)        comment_list = []        for comment in comment_queryset_list:            comment_list.append(comment.to_dict_data())  # 引用Comments中自定义的字典转换        le = len(comment_list)  # 文章评论数        # 3. 判断是否取到文章数据        if article:            return render(request, "news/news_detail.html", locals())        else:            return HttpResponseNotFound("<h1>Page Not Found<h1>")

3. 前端html填充

news/article_detail.html

<div class="news-info">    <div class="news-info-left">        <span class="news-author">{{ article.author.username }}</span>        <span class="news-pub-time">{{ article.update_time }}</span>        <span class="news-type">{{ article.tag.name }}</span>    </div></div><br/><br/><article class="news-content">    {{ article.content | safe }}</article>

三、文章评论和回复

1. urls.py配置

news/urls.py

from django.urls import pathfrom news import viewsapp_name = "news"urlpatterns = [    path("<int:article_id>/comments/", views.ArticleCommentView.as_view(), name="comments"),]

2. 后端view逻辑处理

评论和回复用的是同一套处理逻辑

news/views.py

import jsonfrom django.views import Viewfrom news import models as _modelsfrom utils.res_code.res_code import Code, error_mapfrom utils.res_code.json_function import to_json_dataclass ArticleCommentView(View):    """    news comment replay view    route: /news/<int:article_id>/comments    """    # 1. 创建1个post,url带有article_id参数    def post(self, request, article_id):        # 要先判断是否用户已经登录(必须登录后才能进行评论)        if not request.user.is_authenticated:            return to_json_data(errno=Code.SESSIONERR, errmsg=error_map[Code.SESSIONERR])        # 2. 获取前端传来的参数        try:            json_data = request.body            if not json_data:                return to_json_data(errno=Code.PARAMERR, errmsg="参数错误,请重新输入")            dict_data = json.loads(json_data)        except Exception as e:            return to_json_data(errno=Code.UNKOWNERR, errmsg=error_map[Code.UNKOWNERR])        # 3. 获取前端传来的回复内容        content = dict_data.get("content")        # 4. 判断content是否为空        if not content:            return to_json_data(errno=Code.PARAMERR, errmsg="评论内容为空,请输入!")        # 5. 获取父评论id        parent_id = dict_data.get("parent_id")        try:            if parent_id:                parent_id = int(parent_id)                # 判断文章id和父评论id是否和传来过同时满足                if not _models.Comments.objects.only("id").filter(is_delete=False, id=parent_id,                                                              article_id=article_id).exists():                    return to_json_data(errno=Code.PARAMERR, errmsg=error_map[Code.UNKOWNERR])        except Exception as e:            logger.error("父评论id异常:{}".format(e))            return to_json_data(errno=Code.PARAMERR, errmsg="父评论ID参数异常")        # 6. 保存到数据库        article_comment = _models.Comments()  # 获取评论实例        article_comment.content = content  # 保存评论内容        article_comment.article_id = article_id  # 保存评论文章id        article_comment.author = request.user        article_comment.parent_id = parent_id if parent_id else None  # 判断是否为空        article_comment.save()  # 保存实例        count = _models.Comments.objects.only("id").filter(is_delete=False, article_id=article_id).count()  # 评论条数实时加载        return to_json_data(data={            "data": article_comment.to_dict_data(),            "count": count,        }        )

3. 前端js实现

评论和回复的js实现基本一致

js/news/index.js

$(function () {    // 未登录提示框    let $loginComment = $('.please-login-comment input');  //获取请登录框    let $sendComment = $('.logged-comment .comment-btn');  //获取到评论按钮    let $commentCount = $('.new-comment .comment-count');   //获取到评论条数    $('.comment-list').delegate('a,input', 'click', function () {  //delegate委托事件        let sClassValue = $(this).prop('class'); //取出class属性值        if (sClassValue.indexOf('reply_a_tag') >= 0) {            $(this).next().toggle(); //交叉显示,点击时显示,再次点击就不显示        }        if (sClassValue.indexOf("reply_cancel") >= 0) {            $(this).parent().toggle();//交叉显示,点击时显示,再次点击就不显示        }        //回复评论        if (sClassValue.indexOf('reply_btn') >= 0) {            //进行发送ajax请求数据给后端            let $this = $(this);  //获取当前点击回复            let article_id = $this.parent().attr("article_id");  //上一个评论的的文章id            let parent_id = $this.parent().attr("comment_id");   //上一个评论的id            let content = $this.prev().val();//评论内容            //判断评论内容是是否为空            if (!content) {                alert("评论内容为空,请重新输入");            }            //构造ajax请求参数            let sDataParams = {                "content": content,                "parent_id": parent_id,            };            //发送ajax请求            $.ajax({                url: '/news/' + article_id + "/comments/",                type: "POST",                contentType: "application/json, charset=utf-8",                data: JSON.stringify(sDataParams),                dataType: 'json',            })                .done(function (res) {                    if (res.errno === "200") {                        let comment = res.data.data;                        let html_content = ``;                        $commentCount.empty();  //移除文本内容                        $commentCount.text(res.data.count); //添加新的评论数                        html_content += `                        <li class="comment-item">                            <div class="comment-info clearfix">                                <img src="../../static/images/avatar.jpeg" alt="avatar" class="comment-avatar">                                <span class="comment-user">${ comment.author }</span>                                <span class="comment-pub-time">${ comment.update_time }                            </div>                            <div class="comment-content">${ comment.content }</div>                            <div class="parent_comment_text">                                <div class="parent_username">${ comment.parent.author }</div>                                <br/>                                <div class="parent_content_text">${ comment.parent.content }</div>                            </div>                                                                                <div class="comment_time left_float">${comment.update_time}</div>                            <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a>                            <form class="reply_form left_float" comment_id="${comment.comment_id}" article_id="${comment.article_id}">                                <textarea class="reply_input"></textarea>                                <input type="button" value="回复" class="reply_btn right_float">                                <input type="reset" name="" value="取消" class="reply_cancel right_float">                            </form>                        </li>`;                        $('.comment-list').prepend(html_content);                        $this.prev().val("");   //清空输入框                        $this.parent().hide();   //关闭评论框                    } else if (res.errno = "4101") {                        alert("请登录后再评论");                        setTimeout(function () {                            //重定向到登录界面                            window.location.href = "/users/login"                        }, 800)                    } else {                        alert(res.errmsg)                    }                })                .fail(function () {                    alert("服务器超时,请重试!")                })        }    });    //点击评论框,重定向到用户登录页面    $loginComment.click(function () {        $.ajax({            url: "/news/" + $(".please-login-comment").attr("article_id") + "/comments/",            type: "POST",            contentType: "applications/json, charset=utf-8",            dataType: "json",        })            .done(function (res) {                if (res.errno === "4101") {                    setTimeout(function () {                        window.location.href = "/users/login";                    }, 800)                } else {                    alert(res.message);                }            })            .fail(function () {                alert("服务器超时,请重试!");            })    });    // 发表评论    $sendComment.click(function () {        //获取到文章的id,评论的id,评论内容        let $this = $(this);        let article_id = $this.parent().attr("article_id");        let content = $this.prev().val();        if (!content) {            alert("评论内容为空,请重新输入!");            return        }        let sDataParams = {            "content": content,        };        $.ajax({            url: '/news/' + article_id + "/comments/",            type: "POST",            contentType: "application/json, charset=utf-8",            data: JSON.stringify(sDataParams),            dataType: 'json',        })            .done(function (res) {                if (res.errno === "200") {                    let comment = res.data.data;                    let html_content = ``;                    $commentCount.empty();                    $commentCount.text(res.data.count); //添加新的评论数                    html_content += `                        <li class="comment-item">                            <div class="comment-info clearfix">                                <img src="../../static/images/avatar.jpeg" alt="avatar" class="comment-avatar">                                <span class="comment-user">${ comment.author }</span>                                <span class="comment-pub-time">${ comment.update_time }</span>                            </div>                            <div class="comment-content">${ comment.content }</div>                                                      <div class="comment_time left_float">${ comment.update_time }</div>                            <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a>                            <form class="reply_form left_float" comment_id="${comment.comment_id}" article_id="${comment.article_id}">                                <textarea class="reply_input"></textarea>                                <input type="button" value="回复" class="reply_btn right_float">                                <input type="reset" name="" value="取消" class="reply_cancel right_float">                            </form>                        </li>`;                    $('.comment-list').prepend(html_content);                    $this.prev().val("");   //清空输入框                } else if (res.errno = "4101") {                    alert("请登录后再评论");                    setTimeout(function () {                        //重定向到登录界面                        window.location.href = "/users/login";                    }, 800)                } else {                    alert(res.errmsg)                }            })            .fail(function () {                alert("服务器超时,请重试!")            })    });    // get cookie using jQuery    function getCookie(name) {        let cookieValue = null;        if (document.cookie && document.cookie !== '') {            let cookies = document.cookie.split(';');            for (let i = 0; i < cookies.length; i++) {                let cookie = jQuery.trim(cookies[i]);                // Does this cookie string begin with the name we want?                if (cookie.substring(0, name.length + 1) === (name + '=')) {                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));                    break;                }            }        }        return cookieValue;    }    function csrfSafeMethod(method) {        // these HTTP methods do not require CSRF protection        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));    }    // Setting the token on the AJAX request    $.ajaxSetup({        beforeSend: function (xhr, settings) {            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {                xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));            }        }    });});

4. 前端html填充

news/index.html

<div class="comment-contain">    <div class="comment-pub clearfix">        <div class="new-comment">文章评论(<span class="comment-count">{{ le }}</span>)</div>        {# 用户登录后才能评论 #}        {% if user.is_authenticated %}        <div class="comment-control logged-comment" article_id="{{ article.id }}">            <input type="text" placeholder="请填写评论">            <button class="comment-btn">发表评论</button>        </div>        {% else %}        <div class="comment-control please-login-comment" article_id="{{ article.id }}"             style="display: none">            <input type="text" placeholder="请登录后参加评论" readonly>            <button class="comment-btn">发表评论</button>        </div>        {% endif %}    </div>    <ul class="comment-list">        {# 评论内容 #}        {% for comment in comment_list %}        <li class="comment-item">            <div class="comment-info clearfix">                <img src="../../static/images/avatar.jpeg" alt="avatar" class="comment-avatar">                <span class="comment-user">{{ comment.author }}</span>                <span class="comment-pub-time">{{ comment.update_time }}</span>            </div>            <div class="comment-content">{{ comment.content }}</div>            {# 子评论内容 #}            {% if comment.parent %}            <div class="parent_comment_text">                <div class="parent_username">{{ comment.parent.author }}</div>                <br/>                <div class="parent_content_text">{{ comment.parent.content }}</div>            </div>            {% endif %}            <div class="comment_time left_float">{{ comment.parent.update_time }}</div>            <a href="javascript:void(0);" class="reply_a_tag right_float">回复</a>            <form class="reply_form left_float" comment_id="{{ comment.comment_id }}" article_id="                                                                                                  {{ comment.article_id }}">                <textarea class="reply_input"></textarea>                <input type="button" value="回复" class="reply_btn right_float">                <input type="reset" name="" value="取消" class="reply_cancel right_float">            </form>        </li>        {% endfor %}    </ul></div>
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
Django项目: 5.新闻主页
初识 Django | Django 文档 | Django
Django匆匆一眼却解答了多年疑惑
Python做一个属于自己的web网站「下」
Django AJAX : How to use AJAX in Django Template...
python测试开发django-57.xadmin选项二级联动
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服