BBS-文章详情页面


点击文章标题跳转到具体的文章中去

重点功能:

  • 点赞点踩数前端实时显示
  • 后端要对is_up做反序列化
  • 评论临时渲染
  • 对于子评论,要获取parent_id的值

一、添加路由

urls.py

1、详情页路由

    # 文章详情页路由:/站点名/article/文章的id
    url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)$', views.article_detail),

这个路由放到位置在这三个中是最下的

2、点赞点踩路由

    # 点赞点踩路由
    url(r'^up_and_down/$', views.up_and_down),

3、评论功能路由

    # 评论功能路由
    url(r'^comment/$', views.comment),

二、文章详情功能前端

文章详情页前端步骤

1. 继承base.html页面
2. 模板变量显示文章标题和正文内容
3. 点赞点踩样式。复制博客园的样式。
4. 点赞点踩绑定事件
5. 评论功能
6. 评论按钮绑定点击事件
7. 评论列表
8. 回复功能绑定事件
9. 第6步中加多行子评论中的parent_id参数

点赞点踩js步骤

1. html标签搭建好。复制博客园的样。
2. 复制html,css样式(所有标签的样式都要复制),对有图片防盗链的图片,复制到我们的项目中,更改图片路径。
3. 数据用文章表中点赞数点踩数展示
4. 点赞点踩js,绑定点击事件。给点赞点踩的div定义相同的类,给这个类绑定事件
    4.1 区分开到底点的是赞还是踩,判断某个元素有没有这个类:hasClass()方法,返回布尔值
    4.2 获取数据,获取is_up字段数据,文章id数据
    4.3 发起ajax请求,把数据提交到django 的后端
    4.4 接收后端的数据
    4.5 点赞成功显示的文字信息
    4.6 不成功显示文字信息
    4.7 点赞数实时展示,用js。逻辑:要先拿到原来的数量,然后+1,再写进去

博客园点赞点踩绑定事件思路分析:

<div id="div_digg">
    <div class="diggit" onlick="votePost(17391134, 'Digg')"><!--绑定事件的另一种写法-->
        <span class="diggnum" id="digg_count">{{ article_detail.up_num }}</span><!--点赞数-->
    </div>
    <div class="buryit" onlick="votePost(17391134, 'Bury')">
        <span class="burynum" id="bury_count">{{ article_detail.down_num }}</span><!--点踩数-->
    </div>

function votePost(id, s) {
    if (s == 'Digg') {
    	// 点赞逻辑
    } else {
    	// 点踩逻辑
    }
    }

评论功能

前端逻辑:

# 评论功能
样式,发表评论文字,一个大段文本框,一个按钮  # 可以使用富文本编辑器


# 评论按钮绑定点击事件
1. 获取参数,直接去comment表中查看有哪些字段必须从前端获取
2. 发起ajax请求,把数据提交到后端
3. 临时渲染评论的内容,需要评论用户和文本内容,参数中增加一个获取用户(sessionu获取)
    3.1 es6中的模板语法,反引号
    3.2 把html追加到指定元素之后,$(A).append(B)
    3.3 清空输入框里面的评论内容
4. 加参数数据:父id。根评论,parent_id为空。子评论,parent_id有值
    # 在事件之前定义全局变量,parent_id

评论列表

1. 复制一个列表标签
2. 数据展示
    几楼,用forloop.counter计数,从1开始
    评论时间,格式化
    当前评论的用户,评论表找用户表,正向
    回复a链接,href="javascript:;",增加两个属性,当前评论用户名,当前评论用户id。回复事件中使用
    评论内容
    

# 回复功能绑定事件
1. 点击回复后文本框中显示@用户名加换行符,获取焦点事件
2. 给回复标签加两个属性当前用户当前id,js中直接获取标签属性
3. parent_id的值获取,是回复的评论id

前端代码

文章详情页面,在templates文件夹中新建article_detial.html,继承的基础模板base.html

<!-- 文章详情页面 -->
{% extends 'base.html' %}

{% block css %}
    {# 点赞点踩css样式开始 #}
    <style>
        #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/upup.png') 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.png') 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;
        }
    </style>
    {# 点赞点踩css样式结束 #}
{% endblock %}

{% block content %}
    {# 文章内容展示开始 #}
    <h1 class="article_title">{{ article_obj.title }}</h1><!-- 文章标题 -->
    <div class="article_content"><!-- 文章内容 -->
        {{ article_obj.content|safe }}<!-- safe能显示标签样式 -->
    </div>
    {# 文章内容展示结束 #}

    {# 点赞点踩样式开始 #}
    <div class="clearfix"><!-- 解决父标签塌陷,加个类clearfix -->
        <div id="div_digg">
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span><!--点赞数,从文章表中查询出来的-->
            </div>
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_num }}</span><!--点踩数,从文章表中查询出来的-->
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red"></div><!--一个空div,用来添加点赞之后成功或者失败显示的文本-->
        </div>
    </div>
    {# 点赞点踩样式结束 #}

    {# 评论列表展示开始 #}
    <div>
        <p><span>评论列表</span></p>
        <ul class="list-group">
            {% for comment in comment_list %}
                <li class="list-group-item">
                    {# #1楼 2023-05-11 17:32 哈哈哈哼#}
                    <span># {{ forloop.counter }}楼 </span><!-- 几楼,用forloop计数,从1开始 -->
                    <span>{{ comment.comment_time|date:'Y-m-d H:i' }}</span><!-- 评论时间,格式化 -->
                    <!-- 通过当前评论找用户,正向 -->
                    <span>{{ comment.user.username }}</span><!-- 评论的用户 -->
                    <span class="pull-right"><!-- 回复链接 -->
                        <!-- href为空,点击就会刷新页面,解决方法1:"#",解决方法2:href="javascript:;"-->
                        <!-- javascript:,冒号后面可以写js代码,执行了a标签后就会直接执行冒号后的js代码 -->
                            <a href="javascript:;" comment_username="{{ comment.user.username }}"
                               comment_id="{{ comment.pk }}" class="reply">回复</a>
                        </span>
                    <p>
                        {{ comment.content }}
                    </p><!-- 评论内容 -->
                </li>
            {% endfor %}
        </ul>
    </div>
    {# 评论列表展示结束 #}

    {# 评论功能开始  #}
    <div>
        <p><span class="glyphicon glyphicon-comment">发表评论</span></p>
        <p>
            <textarea name="" id="content" cols="30" rows="10"></textarea><!-- 大段文本框 -->
        </p>
        <button class="btn btn-success id_comment">提交评论</button>
    </div>
    {# 评论功能结束  #}

    <div style="height: 500px"></div>
{% endblock %}


{% block js %}
    <script>
        {# 点赞点踩js开始 #}
        $(".action").click(function () {  // 给点赞点踩的div定义相同的类,给这个类绑定事件
            var _this = $(this);

            // 1.区分开到底点的是赞还是踩,判断某个元素有没有这个类:hasClass()方法,返回布尔值
            // $(this).hasClass('diggit');  // 如果返回的是true,说明点的是赞,否则是踩

            // 2.获取数据
            // 2.1 获取is_up字段的数据
            var is_up = $(this).hasClass('diggit');
            // console.log(is_up, typeof is_up)  // true 'boolean'

            // 2.2 获取文章id,文章详情对象点pk
            var article_id = '{{ article_obj.pk }}'

            // 2.3 用户id,后端从可以session里获取

            // 3.发起ajax请求,把数据提交到django 的后端
            $.ajax({
                url: '/up_and_down/',  // 点赞点踩路由
                type: 'post',
                data: {is_up: is_up, article_id: article_id, csrfmiddlewaretoken: '{{ csrf_token }}'},
                success: function (res) {
                    // 4.接收后端的数据
                    if (res.code == 200) {
                        // 5. 点赞成功显示的文字信息
                        $("#digg_tips").text(res.msg);  // 文本操作

                        // 7.点赞数实时展示,用js。逻辑:要先拿到原来的数量,然后+1,再写进去
                        // 7.1 在外面先用一个变量把当前的action的jquery对象保存下来:_this
                        {# 这里用this指的是function函数 #}

                        // 7.2 先拿到原来的值
                        {# _this:表示外面的$(".action")标签,这样点赞点踩都能拿到;.children():是拿到儿子元素的div,就是具体的点赞或点踩的div;.text() #}
                        var old_num = _this.children().text();
                        // console.log(old_num, typeof old_num);  // 3 string

                        // 7.3 值加一后添加到原来的位置
                        // 字符串需要转格式才能相加减
                        _this.children().text(parseInt(old_num) + 1);
                    } else {
                        // 6. 不成功,给div加个文本
                        // 识别标签的用html(value)设置值
                        $("#digg_tips").html(res.msg);
                    }
                }
            })
        })
        {# 点赞点踩js结束 #}

        {# 评论按钮绑定点击事件开始 #}
        // 根评论和子评论都是使用这个按钮
        // 定义全局变量,直接在两个事件之前就定义parent_id
        var parent_id = null;

        $(".id_comment").click(function () {
            // 1. 获取参数,直接去comment表中查看有哪些字段必须从前端获取
            // 1.1 评论内容
            var content = $("#content").val();

            // 1.2 获取文章id
            var article_id = '{{ article_obj.pk }}';

            // 用户从哪里来?
            var current_user = '{{ request.session.username }}'

            // 2. 发起ajax请求,把数据提交到后端
            $.ajax({
                url: '/comment/',  // 评论路由
                type: 'post',
                // 4. 加参数数据:父id。根评论,parent_id为空。子评论,parent_id有值
                data: {content: content, article_id: article_id, csrfmiddlewaretoken: '{{ csrf_token }}', parent_id:parent_id},
                success: function () {
                    // 3.临时渲染
                    var html = '';
                    // 3.1 es6中的模板语法,反引号,多行文本, ${变量名占位}
                    html = `
                    <li class="list-group-item">
                        <span>${current_user}</span>
                        <p>
                            ${content}
                        </p>
                    </li>
                    `  // 变量有用户名和评论内容,需要提前获取到

                    // 3.2 把html追加到指定元素之后, $(A).append(B),把B追加到A标签之后
                    $('.list-group').append(html);

                    // 3.3 清空输入框里面的评论内容
                    $('#content').val('');
                }
            });
        });
        {# 评论按钮绑定点击事件结束 #}

        {# 回复功能绑定事件开始 #}
        // 回复功能
        $(".reply").click(function () {
            // 2. 给回复标签加两个属性当前用户名、当前id,js中直接获取标签属性
            // 获取回复标签中的用户名属性的值
            var comment_username = $(this).attr('comment_username')

            // 3. parent_id的值获取,是回复的评论id
            // var parent_id = $(this).attr('comment_id');  这样写parent_id是局部变量,想要按钮点击事件中也能使用,就把parent_id设置为全局变量
            parent_id = $(this).attr('comment_id');

            // 子评论需要哪些参数?
            // 评论的内容
            // 需要评论id

            // 1. 点击回复后文本框中显示@用户名加换行符,获取焦点事件
            $("#content").val('@' + comment_username + '/n').focus();
        })
        {# 回复功能绑定事件结束 #}
    </script>

{% endblock %}

三、添加视图函数

文章详情页

步骤:

1. 首页、站点页中文章标题处添加路由
    href="/{{ username }}/article/{{ article.pk }}"
2. 验证用户是否存在,因为只要跳转,网址是可以更改的
3. 获取网址中的username,查询用户信息,用户不存在返回404页面
4. 根据文章id查询文章对象
5. 读取当前文章的所有评论,按照当前文章id来过滤
``

代码:
```python
@login_auth
def article_detail(request, username, article_id):
    """文章详情页"""
    # 1. 路由
    # 2. 根据用户名查询用户信息
    user_obj = models.UserInfo.objects.filter(username=username).first()
    # 3. 验证用户是否存在,因为只要跳转,网址是可以更改的
    if not user_obj:
        return render(request, '404.html')

    # 4. 根据文章id查询文章对象
    article_obj = models.Article.objects.filter(pk=article_id).first()

    # 5. 读取当前文章的所有评论,按照当前文章id来过滤
    comment_list = models.Comment.objects.filter(article_id=article_id).all()

    return render(request, 'article_detail.html', locals())

点赞点踩

后端步骤:

1. 定义返回给前端的数据格式
2. 接收参数
3. 验证参数
    3.1 用户必须登录
    3.2 自己的文章自己不能点赞,先通过当前文章去查询用户(谁写的),然后与当前登录用户比较
    3.3 验证一篇文章一个人只能点一次。根据文章id和用户id两个条件,去表里面查询,如果查到了,说明已经点过了,不能再点了
4. 正常业务逻辑
    4.1 对is_up做反序列化:json.loads
    4.2 操作文章表,更新数据。点赞点踩要操作不同的字段,需要先判断
    4.3 操作点赞点踩表,增加记录
5. 返回给前端数据

"""问题:"""
为什么ajax提交到后端的数据被序列化了,查看编码方式,查看前端打印的is_up的type类型

代码:

from django.db.models import F  # 数据变动加一
import json  # 序列化模块


@login_auth
# 单独开设一个方法,来处理点赞点踩逻辑
def up_and_down(request):
    """点赞点踩函数,不需要页面"""
    """
    点赞点踩重要功能分析:
        1. 必须登录
        2. 如果没有登录,在页面上显示:请先登录,并且可以点击,跳转到登录页面
        3. 自己的文章不能给自己点
        4. 如果登录成功了,前端页面上点赞数据要+1,显示点赞成功
        5. 如果点赞过,就不能再点了,也就是一篇文章一个用户只能点一次
    """
    if request.method == 'POST':
        # 1. 定义返回给前端的数据格式
        back_dic = {'code': 200, 'msg': '点赞成功'}

        # 2. 接收参数
        is_up = request.POST.get('is_up')  # true <class 'str'>
        print(is_up, type(is_up))  # 为什么格式是序列化后的格式
        article_id = request.POST.get('article_id')

        # 3.验证参数
        # 3.1 用户必须登录
        if not request.session.get('username'):
            back_dic['code'] = 1013
            back_dic['msg'] = '<a href="/login/" style="color:red">请先登录</a>'  # 后端直接返回a标签
            return JsonResponse(back_dic)

        # 3.2 自己的文章自己不能点
        # 先通过当前文章去查询用户(谁写的),然后与当前登录用户比较
        # 文章找作者:文章--> 站点 --> 用户表
        # 先查询出当前的文章对象
        article_obj = models.Article.objects.filter(pk=article_id).first()
        # article_obj.blog.userinfo.username ----> request.session.get(username)
        if article_obj.blog.userinfo.username == request.session.get('username'):
            back_dic['code'] = 1014
            back_dic['msg'] = '不能给自己点赞或点踩'
            return JsonResponse(back_dic)

        # 3.3 验证一篇文章一个人只能点一次
        # 根据文章id和用户id两个条件,去表里面查询,如果查到了,说明已经点过了,不能再点了
        is_click = models.UpAndDown.objects.filter(article_id=article_id, user_id=request.session.get('id')).first()
        if is_click:
            back_dic['code'] = 1015
            back_dic['msg'] = '你已经点过了,不能再点了!!'
            return JsonResponse(back_dic)

        # 4.处理正常的业务逻辑
        # 入库,操作哪些表?  1. 点赞点踩表 2. 文章表:点赞数,点踩数
        # 4.1 这里需要对is_up做反序列化:json.loads
        is_up = json.loads(is_up)
        # 4.2 操作文章表,更新数据
        if is_up:  # 说明是点赞了
            models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)  # 增加数据,用到F查询
        else:  # 说明是点踩了
            models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
            back_dic['msg'] = '我会更加努力的'  # 点踩需要改一下消息

        # 4.3 操作点赞点踩表,增加记录
        models.UpAndDown.objects.create(is_up=is_up, article_id=article_id, user_id=request.session.get('id'))
        # models.UpAndDown.objects.create(is_up=is_up, article(不加id)=article_obj(文章对象))

        return JsonResponse(back_dic)

评论功能

后端步骤:

1. 定义返回前端的数据格式
2. 接收参数
3. 验证参数,内容不能为空
4. 处理正常业务逻辑,操作2张表,1 文章表(评论数),2 评论表
    4.1 更新文章表中的评论数量
    4.2 新增评论表中的一条记录
    4.3 练习一下事务的使用
5.返回给前端数据

后端代码:

@login_auth
def comment(request):
    """
    评论函数,不需要页面
    """
    """
    评论功能分析:
        1. 必须登录之后才能评论,前端的评论框必须是登录之后才显示
        2. 先做根评论
        3. 在做子评论
    """
    if request.method == 'POST':
        # 1. 定义返回前端的数据格式
        back_dic = {'code': 200, 'msg': '评论成功'}

        # 2. 接收参数
        content = request.POST.get('content')
        article_id = request.POST.get("article_id")
        parent_id = request.POST.get("parent_id")

        # 3. 验证参数
        # 内容不能为空
        if not content:
            back_dic['code'] = 1016
            back_dic['msg'] = '评论内容不能为空'
            return JsonResponse(back_dic)

        # 4.处理正常业务逻辑
        # 操作2张表,1 文章表(评论数) 2 评论表

        # 4.3 练习一下事务的使用
        from django.db import transaction
        try:
            with transaction.atomic():
                # 4.1 更新文章表中的评论数
                models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)

                # 4.2 新增评论表中的一条记录
                models.Comment.objects.create(content=content, article_id=article_id, user_id=request.session.get('id'),
                                              parent_id=parent_id)
                # user_id=request.session.get('id'),企业中这些经常使用的会直接封装成函数,我们直接调用函数获得值
        except Exception as e:
            print(e)
            transaction.rollback()

        # 5.返回给前端数据
        return JsonResponse(back_dic)

复习知识点:
1.es6中的模板语法

可以定义大段文本,并且实现格式化字符操作

<!-- es6中的模板语法使用 -->
var name = 'kevin';  
var age = 18;

console.log(`my name is ${name}, my age is ${age}`);

<!-- 输出结果是:my name is kevin, my age is 18 -->

2.forloop的用法:https://www.cnblogs.com/zjyao/p/17360259.html#for标签

posted @ 2023-05-15 21:04  星空看海  阅读(13)  评论(0编辑  收藏  举报