Django - BBS - 项目学习 - 文章详情页面实现(文章内容、赞踩操作、评论)

本文详细介绍了如何使用Django构建一个BBS论坛的文章详情页面,涵盖了路由设计、视图函数的编写,包括文章内容展示、赞踩功能的实现以及评论系统的集成。前端部分讨论了母版页和子版页(article_detail.html)的继承设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、路由设计

二、视图函数

2-1 文章内容

2-2 赞踩操作

2-3 评论操作

三、前端设计

3-1 母版

3-2 继承子版(article_detail.html)


一、路由设计

from django.conf.urls import url
from django.contrib import admin
from bbs import views

from django.views.static import serve
from mybbs.settings import MEDIA_ROOT

urlpatterns = [
    url(r'^$', views.index),

    url(r'^admin/', admin.site.urls),
    url(r'^login/', views.login),
    # 用于login前端页面对于验证码的获取
    url(r'^get_valid_code/', views.get_valid_code),

    url(r'^index/', views.index),
    url(r'^register/', views.register),
    url(r'^logout/', views.logout),
    url(r'^change_pwd/', views.change_pwd),
    url(r'^change_avatar/', views.change_avatar),

    url(r'^media/(?P<path>.*)', serve, {"document_root": MEDIA_ROOT}),

    # 点赞的路由
    url(r'^diggit/$', views.diggit),
    # 评论路由
    url(r'^commit_content/$', views.commit_content),

    # 三个过滤(分类,标签,归档) - 用于进入点击左边导航后的个人站点的文章显示
    # 分组分出三个(用户名,category|tag|archive中的一个,可能是分类id,tag_id,时间)
    url(r'^(?P<username>[\w]+)/(?P<condition>category|tag|archive)/(?P<param>.*)', views.user_blog),
    # 个人主页的文章查看路由
    url(r'^(?P<username>[\w]+)/article/(?P<id>\d+)', views.article_detail),

    # 上方未匹配到,接收参数匹配个人站点 有名分组
    url(r'^(?P<username>[\w]+)$', views.user_blog),

]

二、视图函数

2-1 文章内容

from django.shortcuts import render, HttpResponse, redirect
from bbs import models
import json
from django.db import transaction
from django.db.models import F

def article_detail(request, username, id, *args, **kwargs):
    '''
     文章内容查看,直接更新在个人站点的中心内容内
    :param request:
    :param username: 前端传输当前用户
    :param id: 文章的id号
    :param args:
    :param kwargs:
    :return:
    '''
    username = username
    user = models.UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'error.html')

    blog = user.blog
    article = models.Article.objects.filter(pk=id).first()
    content_list = article.commit_set.all().order_by('pk')

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

2-2 赞踩操作

from django.shortcuts import render, HttpResponse, redirect
from bbs import models
import json
from django.db import transaction
from django.db.models import F

def diggit(request):
    '''
    点赞函数
    :param request:
    :return:
    '''
    response = {'status': 100, 'msg': None}
    if request.user.is_authenticated():
        # 从前端传过来的数据,都转成str类型(json格式)
        article_id = request.POST.get('article_id')
        is_up = request.POST.get('is_up')
        is_up = json.loads(is_up)
        # 原子性操作.用事务;使用F函数对文章的点赞量进行增加操作:一旦其中有一项失败则回滚
        # 可以通过数据库查询是否用户对该篇文章进行赞踩操作(不推荐)
        # ret=models.UpAndDown.objects.filter(user_id=user.pk,article_id=article_id).exists()
        # 使用异常捕捉获取数据报错
        try:
            with transaction.atomic():
                # 使用 UpAndDown 表记录用户和文章的点赞关系
                # 若无法创建,表示记录已存在则报错
                models.UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
                # models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
                article = models.Article.objects.filter(pk=article_id)
                if is_up:
                    article.update(up_num=F('up_num') + 1)
                    response['msg'] = '点赞成功'
                else:
                    article.update(down_num=F('down_num') + 1)
                    response['msg'] = '反对成功'
        except Exception as e:
            response['msg'] = '您已经点过赞,请勿重复点赞!'
    else:
        response['msg'] = '请先登录'
        response['status'] = 101
    return JsonResponse(response)

2-3 评论操作

from django.shortcuts import render, HttpResponse, redirect
from bbs import models
import json
from django.db import transaction
from django.db.models import F

def commit_content(request):
    '''
    评论操作
    :param request:
    :return:
    '''
    response = {'status': 100, 'msg': None}
    if request.is_ajax():
        if request.user.is_authenticated():
            user = request.user
            article_id = request.POST.get('article_id')
            content = request.POST.get('content')
            pid = request.POST.get('pid')
            # 注意字段parent_id_id,由于我的models内外键字段为parent_id,而表内的字段会自动添加_id
            with transaction.atomic():
                ret = models.Commit.objects.create(user=user, article_id=article_id, content=content, parent_id_id=pid)
                models.Article.objects.filter(pk=article_id).update(commit_num=F('commit_num') + 1)

            response['msg'] = '评论成功'
            response['content'] = ret.content
            # 把datetime类型转成字符串,因为json是无法序列化datetime
            response['time'] = ret.commit_time.strftime('%Y-%m-%d %X')
            response['user_name'] = ret.user.username
            if pid:
                # 如果是子评论,返回父评论的名字
                response['parent_name'] = ret.parent_id.user.username
        else:
            response['status'] = 101
            response['msg'] = '您没有登录'
    else:
        response['status'] = 101
        response['msg'] = '您请求非法'
    return JsonResponse(response)

三、前端设计

3-1 母版

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {# blog对象的反向查询 表名小写#}
    <title>{{ blog.userinfo.username }}-的个人博客</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    {# 使用当前用户的样式文件 #}
    <link rel="stylesheet" href="/static/css/{{ blog.theme }}">
    <link rel="stylesheet" href="/static/css/default.css">

    <script src="/static/jquery-3.3.1.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

    </style>
</head>
<body>
<div class="head">
    <h1>{{ blog.title }}</h1>
</div>
<div class="container-fluid">
    <div class="row">
        {# 侧栏导航 模板的导入#}
        <div class="col-md-3">
            {# 也可以通过自定义过滤器进行互相传参操作#}
            {% load my_tag %}
            {% classify username %}
            {#            通过模板导入具有的局限性:每个视图函数内必须返回所需的参数才能构建一个网站#}
            {#            {% include 'classify.html' %}#}
        </div>
        {# 中心内容 模板的继承#}
        <div class="col-md-9">
            {% block content %}
            {% endblock %}
        </div>
    </div>
</div>
</div>
</body>
</html>

3-2 继承子版(article_detail.html)

{% extends 'base.html' %}
{% block content %}

    {#{% csrf_token %}#}

    <div>
        <p><h4 class="text-center">{{ article.title }}</h4></p>
        <div>
            {#safe操作,对代码进行转义显示#}
            {{ article.content|safe }}
        </div>
        {#赞踩部分#}
        <div class="clearfix">
            <div id="div_digg">
                <div class="diggit action">
                    <span class="diggnum" id="digg_count">{{ article.up_num }}</span>
                </div>
                <div class="buryit action">
                    <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>
            <ul class="list-group cotent_ul">
                {% for content in content_list %}
                    <li class="list-group-item">
                        {# 查看循环信息:{{ forloop }} ;forloop.counter循环的次数#}
                        <p><span>#{{ forloop.counter }}楼</span>
                            <span>{{ content.create_time|date:'Y-m-d H:i:s' }}</span>
                            <span><a href="/{{ content.user.username }}">{{ content.user.username }}</a></span>
                            <span class="pull-right replay" username="{{ content.user.username }}"
                                  content_id="{{ content.pk }}">回复</span>
                        </p>
                        {% if content.parent_id %}
                            {#content.parent_id 拿到父评论的对象#}
                            <p class="well">@{{ content.parent_id.user.username }}</p>
                        {% endif %}
                        {{ content.content }}
                    </li>
                {% endfor %}
            </ul>
        </div>
        {#发表评论#}
        <div>
            <p>发表评论</p>
            <p>
                昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50"
                          value="{{ request.user }}">
            </p>
            <p>评论内容:</p>
            <p>
                <textarea name="" id="content" cols="60" rows="10"></textarea>
            </p>
            <button class="btn btn-primary submit">提交</button>


        </div>
    </div>

    <script>
        {#赞踩部分#}
        $(".action").click(function () {
            {#若点击类为action的并且是diggit点赞部分的保存isup为true#}
            var is_up = $(this).hasClass('diggit');
            var obj = $(this).children('span');
            {# ajax响应赞踩 #}
            $.ajax({
                {#通过路由响应点赞函数#}
                url: '/diggit/',
                type: 'post',
                //当前文章pk可从后台取
                data: {article_id: '{{ article.pk }}', is_up: is_up, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                success: function (data) {
                    {#在赞踩部分最后错误信息处展示后台返回信息内容#}
                    $("#digg_tips").html(data.msg);
                    alert(data.msg);
                    {#若赞踩成功,无需刷新直接在前端进行数量增加#}
                    if (data.status == 100) {
                        {#方式一(推荐)#}
                        obj.text(Number(obj.text()) + 1);
                        {# 方式二#}
                        /*if(is_up){
                            //获取页面内值方式(str):text()
                           //var count= $("#digg_count").text()
                            //把count转成int类型(两种方式parseInt、Number)
                            //$("#digg_count").text(parseInt(count)+1)
                            $("#digg_count").text(Number(count)+1)
                        }else{
                            var count= $("#bury_count").text()
                            $("#bury_count").text(Number(count)+1)
                        }*/
                    }
                }
            })
        });

        var pid = '';
        {#发表根评论#}
        $(".submit").click(function () {
            var content = $("#content").val();
            $("#content").val("");

            {#对parent内容切掉父级名称#}
            if (pid) {
                //拿到content要切的起始位置indexOf(),取到指定值的索引值(0,4)
                //取到 \n 索引位置的后一位
                var index = content.indexOf('\n') + 1;
                //slice传一个起始位置,一个结束位置,就可以切出来
                content = content.slice(index)
            }
            $.ajax({
                url: '/commit_content/',
                type: 'post',
                data: {
                    'article_id': '{{ article.pk }}',
                    'content': content,
                    'pid': pid,
                    'csrfmiddlewaretoken': '{{ csrf_token }}'
                },
                {#设置提交数据成功之后马上相应的前端操作(假发表)#}
                success: function (data) {
                    {#console.log(data);#}
                    //拼  用户名:时间
                    //    评论内容    这些数据都应该从后台返回
                    var time = data.time;
                    var content = data.content;
                    var user_name = data.user_name;
                    var ss = '';
                    if (pid) {
                        //需要清空一下父评论的id
                        {#防止发表根评论时候粘留上次的父级id#}
                        pid = '';
                        var parent_name = data.parent_name;
                        ss = `
                         <li class="list-group-item">
                         <p>
                         <span>${ user_name }</span>
                            <span>${ time }</span>
                        </p>
                            <p class="well">@${parent_name }</p>
                        ${content}
                    </li>
                        `
                    } else {
                        ss = `
                      <li class="list-group-item">
                        <p>
                            <span>${ user_name }</span>:
                            <span>${ time }</span>
                        </p>
                         ${content}
                    </li>
                    `
                    }

                    $(".cotent_ul").append(ss)

                }
            })

        });
        {#发表子评论 点击回复自动在评论框中,将父级用户名和评论内容进行拼接操作,并设置提交评论需要的pid#}
        $(".replay").click(function () {
            //拿到span标签中username属性对应的值,attr() 方法设置或返回被选元素的属性值
            var username = $(this).attr('username');
            //把拼接的字符串放到content内,并且换行
            $('#content').val('@' + username + '\n');
            //让光标聚焦到这个控件,重置光标位于content内容尾部
            $('#content').focus();
            //给pid赋值(pid是父评论的id)
            pid = $(this).attr('content_id')
        })
    </script>


{% endblock %}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值