bbs项目(部分讲解)

文章详情和点赞点踩

左侧列表组使用inclusion_tag实现

# 自定义标签
1. 在应用下创建templatetags包,必须是templatetags
2. 在templatetags中新建一个new_tag.py文件,py文件名随意。
from django import template
from blog.models import Classify, Tag, Article, UserInfo
from django.db.models import Count
from django.db.models.functions import TruncMonth

register = template.Library()  # 生成一个Library对象 名字必须叫register 


# 装饰函数
@register.inclusion_tag(filename='left.html', name='left') # 返回html片段,第一个参数是html文件
def left(name):
	# user 当前根据用户名查到的用户,需要传入用户名,一定会有user
    user = UserInfo.objects.filter(username=name).first()
    # 需要标签名和统计标签内文章数
    classify_res = Classify.objects.all().filter(blog=user.blog).values('id').annotate(
        c=Count('article__id')).values_list('id', 'name', 'c')
    tag_res = Tag.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list(
        'id', 'name', 'c')
    date_res = Article.objects.all().filter(blog=user.blog).annotate(year_month=TruncMonth('create_time')).values(
        'year_month').annotate(c=Count('id')).values_list('year_month', 'c')
    return {'classify_res': classify_res, 'tag_res': tag_res, 'date_res': date_res, 'user':user}  # 字典中的数据可以在left中使用

left.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="list-group">
    <a href="#" class="list-group-item active">
        我的标签
    </a>
    {% for foo in tag_res %}
        <div class="list-group">
            <a href="/{{ user.username }}/tag/{{ foo.0 }}.html"
               class="list-group-item"><span>{{ foo.1 }}</span>
                <span>({{ foo.2 }})</span></a>
        </div>
    {% endfor %}

</div>
<div class="list-group">
    <a href="#" class="list-group-item active">
        我的分类
    </a>
    {% for foo in classify_res %}
        <div class="list-group">
            <a href="/{{ user.username }}/classify/{{ foo.0 }}.html"
               class="list-group-item"><span>{{ foo.1 }}</span>
                <span>({{ foo.2 }})</span></a>
        </div>
    {% endfor %}
</div>
<div class="list-group">
    <a href="#" class="list-group-item active">
        随笔分类
    </a>
    {% for foo in date_res %}
        <div class="list-group">
            <a href="/{{ user.username }}/archive/{{ foo.0|date:'Ym' }}.html"
               class="list-group-item"><span>{{ foo.0|date:'Y年m月' }}</span>
                <span>({{ foo.1 }})</span></a>
        </div>
    {% endfor %}

</div>
</body>
</html>

base.html

base中的左侧栅格使用inclusion_tag
需要先将自定义标签load过来,在使用标签并传入参数

<div class="col-md-2">
    {% load new_tag %}
    {% left name %}
</div>
  • 渲染site.html页面时,返回的locals(),所以可以用到site函数的所有变量,site函数的name是它的形参,是点击首页博主用户名跳转过来的,name参数就是用文章取到的博主用户名,所以base可以用到name属性

  • 将那么属性传到new_tag文件中的left函数中,执行该函数。进行标签等数据的过滤,然后返回参数供left.html文件使用,left.html文件渲染完后,贴在base.html的相应位置。views.py中的

注意:添加templatetags模块后 需要重启服务器 才可以使用标签

点赞点踩样式

  1. 直接拷贝博客园样式即可 主要除了html还有css
  2. 针对路由匹配
    含有动态匹配的路由很多时候可能会出现顶替的情况
    这个时候我们可以将简单的路由放前面 复杂的放后面 甚至修改匹配策略

点击首页文章和个人站点中的文章跳转到文章详情页面去

path('<str:name>/articles/<int:article_id>', views.article_detail),

views.py

def article_detail(request, name, article_id):
	# 文章博主
    user = UserInfo.objects.filter(username=name).first()
    # 文章
    article = Article.objects.filter(id=article_id).first()
    if user and article:
        return render(request, 'article.html', context={'user': user, 'article': article, 'name': name})
    return render(request, 'error.html')

article.html

{% extends 'base.html' %}

{% block title %}
    {{ article.title }}
{% endblock %}

{% block link %}
    <link rel="stylesheet" href="/static/css/up.css">
{% endblock %}

{% block handle %}
    <div class="my_nav">
        <nav class="navbar navbar-inverse">
            <div class="container-fluid">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                            data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                        <span class="sr-only">Toggle navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" href="#">{{ user.username }}</a>
                </div>

                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">
                    </ul>
                    <ul class="nav navbar-nav navbar-right">
                        <li>
                            <button type="button" class="btn btn-danger navbar-btn">管理</button>
                        </li>
                    </ul>
                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>
    </div>
{% endblock %}

{% block crticle %}
    <div>
        <h3>{{ article.title }}</h3>
    </div>

    <div>
        {{ article.content }}
    </div>
    <!--点赞点踩样式 直接copy-->
    <div id="div_digg" class="pull-right">
        <div class="diggit is_up">
            <span class="diggnum" id="digg_count">{{ article.up_num }}</span>
        </div>
        <div class="buryit is_up">
            <span class="burynum" id="bury_count">{{ article.down_num }}</span>
        </div>
        <div class="clear"></div>
        <div class="diggword" id="digg_tips">
        </div>
    </div>
{% endblock %}

/static/css/up.css

.diggit {
    float: left;
    width: 46px;
    height: 52px;
    background: url(/static/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/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;
}

点赞点踩前端js

<script>
	// 将点赞点踩设置成一个点击事件
    $('.is_up').click(function () {
        let is_up = ($(this).hasClass('diggit'))//根据类属性来判断是点赞还是点踩
        $.ajax({
            url: '/is_up/',  //处理点赞点踩的接口
            type: 'post',
            // 需要传谁给哪篇文章点赞还是点踩了 谁点赞可以不传 只要后端登陆了就可以查到
            data: {
                article_id:{{ article.id }},
                is_up: is_up,
                csrfmiddlewaretoken: '{{ csrf_token }}'
            },
            success: function (data) {
                if (data.code == 100) {
                	// 如果成功,点赞数+1
                    $('#digg_count').html({{ article.up_num }} +1)
                } else if (data.code == 103) {
                	// 如果失败,点踩数+1
                    $('#bury_count').html({{ article.down_num }} +1)
                }
                // 每次打印提示信息
                $('.diggword').html(data.msg)
            }
        })
    })
</script>

设置路由

# is_up 处理点赞相关路由
path('is_up/', views.is_up),

点赞点踩后端

def is_up(request):
    article_id = request.POST.get('article_id')
    is_up = json.loads(request.POST.get('is_up'))  # 直接取出来是字符串 需要转成bool值
    res = {'code': 100, 'msg': '点赞成功了'}
    # 1. 判断当前用户是否登录
    if not request.user.is_authenticated:  # 只要用户登录就是当前用户 没有登陆就是匿名用户
        res['code'] = 101
        res['msg'] = '没有登录点击跳转<a href="/login/">登录</a>'
        return JsonResponse(res)
    # 2. 判断当前用户是否已经给这篇文章点过赞或踩了
    if UpAndDown.objects.filter(user=request.user, article_id=article_id).first():
        res['code'] = 102
        res['msg'] = '已经点赞或点踩了'
        return JsonResponse(res)
    # 3. 用户是点赞还是点踩 存入点赞点踩表和文章表 并将点赞点踩数返回bbs
    # 开启事务
    with transaction.atomic():
        UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up)
        if is_up:
            # 文章表点赞数加1
            Article.objects.filter(id=article_id).update(up_num=F('up_num') + 1)
        else:
            Article.objects.filter(id=article_id).update(down_num=F('down_num') + 1)
            res['code'] = 103
            res['msg'] = '点踩成功了'
    return JsonResponse(res)

评论前端页面

    <div class="comment-show">
        <div style="margin-top: 60px">
            <b>评论列表</b>
        </div>


        <ul class="list-group comment-ajax">
            {% for foo in comment %}
                <li class="list-group-item">
                    <div>
                        <span># {{ forloop.counter }} 楼</span> <span
                            style="margin-left: 20px">{{ foo.create_time|date:'Y-m-d H:i' }}</span>
                        <a href="/{{ foo.user.username }}/"><span
                                style="margin-left: 20px">{{ foo.user.username }}</span></a>
                        <div class="fa-pull-right">
                            <a class="reply" parent_id="{{ foo.article.id }}" username="{{ foo.user.username }}">回复</a>
                        </div>

                    </div>
                    {% if foo.parent_id %}
                        <p style="margin-top: 10px">@ {{ foo.parent.user.username }}</p>
                        <p>{{ foo.content|safe }}</p>
                    {% else %}
                        <p style="margin-top: 10px">{{ foo.content }}</p>
                    {% endif %}
                </li>
            {% endfor %}
        </ul>


    </div>
    
    <div>
        <a href="">刷新页面</a>
    </div>
    <div style="margin-top: 60px">
        <i class="fa fa-commenting-o" aria-hidden="true"></i>
        <b>发表评论</b>
    </div>
    {% if request.user.is_authenticated %} <!--判断用户是否登录-->
        <div>
            <label for="content"></label>
            <textarea name="" id="content" cols="170" rows="10"></textarea>
        </div>
        <div class="pull-right">
            <button class="btn btn-info" id="comment" style="margin-bottom: 50px">提交评论</button>
        </div>
    {% else %}
        <div>
            <i class="fa fa-commenting-o" aria-hidden="true"></i> <span style="margin-left: 10px">登录后才能发表评论,立即 <a
                href="/login/">登录</a> 或者 <a
                href="/">逛逛</a> 首页</span>
        </div>
    {% endif %}

js代码

	</script>
        // 评论按钮点击事件
        var parent_id = ''
        $('#comment').click(function () {
            // 取出评价内容 包括子评论和跟评论
            var content = $('#content').val()
            // 判断 如果是子评论要删除 @ 名字 换行
            if (parent_id) {
                console.log(content)
                var i = content.indexOf('\n')//取到换行的索引
                content = content.slice(i)//从索引位置往后切
            }
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {// 谁给哪篇文章评论了什么 父评论的id
                    parent_id: parent_id,
                    article_id: {{ article.id }},
                    content: content,
                    csrfmiddlewaretoken: '{{ csrf_token }}'  // 坑!!! 一定要加引号
                },
                success: function (data) {
                    console.log(data)
                    if (data.code == 100) {
                        $('#content').val('')  //评论成功将评论区文字清空
                        var cur_name = data.people // 当前评论人
                        var content = data.content // 评论内容
                        var s = ''  // 将评论拼接到评论列表中
                        if (data.comment_name) {//如果是子评论
                            var comment_name = data.comment_name
                            s = `
                <li class="list-group-item" style="margin-top: 20px">
                <i class="fa fa-commenting" aria-hidden="true"></i>
                <b><span>${cur_name}:</span></b>
                <div><span>@${comment_name}</span></div>
                <div><span>${content}</span></div>


                </li>`
                        } else {
                            s = `
                <li class="list-group-item" style="margin-top: 20px">
                <i class="fa fa-commenting" aria-hidden="true"></i>
                <b><span>${cur_name}:</span></b>
                <div>
                <span>${content}</span>
                </div>
                </li>`
                        }
                    }
                    $('.comment-ajax').append(s)//追加到评论组的最后边 ajax提交跟评论和子评论
                }
            })
        })

        // 回复事件
        $('.reply').click(function () {
            parent_id = $(this).attr('parent_id')
            console.log(parent_id)
            var name = $(this).attr('username')
            // 将 @ 名字 换行 加到输入框中
            $('#content').val(`@${name}\n`).focus()//光标聚焦
        })
    </script>

评论后端

def comment(request):
    res = {'code': 100, 'msg': '评论成功'}
    if request.user.is_authenticated:
        article_id = request.POST.get('article_id')
        content = request.POST.get('content')
        parent_id = request.POST.get('parent_id')
        # 保存评论
        # 开启事务
        with transaction.atomic():
            res_comment = Comment.objects.create(content=content, user=request.user, article_id=article_id,
                                                 parent_id=parent_id)
            # 文章表中评论数加1
            Article.objects.filter(id=article_id).update(comment_num=F('comment_num') + 1)
        # 评论成功发送邮件
        # 使用多线程
        article_title = Article.objects.filter(pk=article_id).first().title
        send = Article.objects.filter(pk=article_id).first().blog.userinfo.email
        t = Thread(target=send_mail,
                   args=(f'[博客评论通知]Re:{article_title}', content, settings.EMAIL_HOST_USER, [send]))
        t.start()
        # send_mail(f'[博客评论通知]Re:{article_title}', content, settings.EMAIL_HOST_USER, ['xuxiaoxu152@163.com'])  # subject, message, from_email, recipient_list,
        # 返回给前端当前评论人 和评论内容
        res['people'] = request.user.username
        res['content'] = content
        if parent_id:  # 如果这是一条子评论 将他评论的这条评论的博主名返回
            res['comment_name'] = res_comment.parent.user.username
        return JsonResponse(res)
    res['code'] = 101
    res['msg'] = '未登录 不能评论'
    return JsonResponse(res)
posted @   吴仁耀  阅读(48)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
  1. 1 原来你也在这里 周笔畅
  2. 2 世间美好与你环环相扣 柏松
  3. 3 起风了 吴青峰
  4. 4 极恶都市 夏日入侵企划
原来你也在这里 - 周笔畅
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 姚谦

作曲 : 中島みゆき

编曲 : Terence Teo

制作人 : 朱敬然

请允许我尘埃落定

请允许我尘埃落定

用沉默埋葬了过去

满身风雨我从海上来

才隐居在这沙漠里

该隐瞒的事总清晰

千言万语只能无语

爱是天时地利的迷信

喔 原来你也在这里

啊 那一个人

是不是只存在梦境里

为什么我用尽全身力气

却换来半生回忆

若不是你渴望眼睛

若不是我救赎心情

在千山万水人海相遇

喔 原来你也在这里

请允许我尘埃落定

请允许我尘埃落定

用沉默埋葬了过去

满身风雨我从海上来

才隐居在这沙漠里

该隐瞒的事总清晰

千言万语只能无语

爱是天时地利的迷信

喔 原来你也在这里

啊 那一个人

是不是只存在梦境里

为什么我用尽全身力气

却换来半生回忆

若不是你渴望眼睛

若不是我救赎心情

在千山万水人海相遇

喔 原来你也在这里

啊 那一个人

啊 那一个人

是不是只存在梦境里

为什么我用尽全身力气

却换来半生回忆

若不是你渴望眼睛

若不是我救赎心情

在千山万水人海相遇

喔 原来你也在这里

该隐瞒的事总清晰

千言万语只能无语

爱是天时地利的迷信

喔 原来你也在这里

OT: AISARERU HANA AISAREXIU HANA

OT: AISARERU HANA AISAREXIU HANA

(中文版:原来你也在这里)

OP: Yamaha Music Publishing Inc

SP:百代音乐版权代理(北京)有限公司

配唱制作人:翁乙仁

点击右上角即可分享
微信分享提示