BBS项目day04 文章详情页、点赞点菜、评论功能

一、路由

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)
相关推荐
暮雪倾风9 分钟前
【WPF开发】超级详细的“文件选择”(附带示例工程)
windows·wpf
白拾18 分钟前
使用Conda管理python环境的指南
开发语言·python·conda
是刃小木啦~38 分钟前
三维模型点云化工具V1.0使用介绍:将三维模型进行点云化生成
python·软件工程·pyqt·工业软件
总裁余(余登武)44 分钟前
算法竞赛(Python)-万变中的不变“随机算法”
开发语言·python·算法
一个闪现必杀技1 小时前
Python练习2
开发语言·python
Eric.Lee20211 小时前
音频文件重采样 - python 实现
人工智能·python·深度学习·算法·audio·音频重采样
大神薯条老师1 小时前
Python从入门到高手5.1节-Python简单数据类型
爬虫·python·深度学习·机器学习·数据分析
学习使我快乐011 小时前
AJAX 2——Bootstrap弹框使用、图书管理案例、图片上传方法
ajax·okhttp·bootstrap
Mr.D学长1 小时前
毕业设计 深度学习社交距离检测系统(源码+论文)
python·毕业设计·毕设
wdxylb2 小时前
解决Python使用Selenium 时遇到网页 <body> 划不动的问题
python