Loading

BBS(仿博客园系统)项目03(主页搭建、个人站点搭建(侧边栏分类展示、标签展示、日期归档)、Django admin后台管理、media暴露给外界(静态文件)、文章详情页相关功能实现)

摘要:

  • 主页面的搭建(导航条下面的区域)
  • 个人站点
    • 侧边栏分类展示
    • 侧边栏标签展示
    • 侧边栏日期归档
  • 文章详情页
    • 文章内容
    • 文章点赞点踩
    • 文章评论

一、主页面home.html的搭建(进一步完善)

home.html页面主要框架描述:

①导航条(已经完成)

②主页左侧边栏信息展示:分类、标签、日期归档,右侧边栏信息展示:广告...

③中间文章列表展示:文章标题、摘要、作者头像显示、创建日期,点赞数

第一步:先将主页面的大致框架布局搭出来:

(先布局282,然后两边用bootstrap的面板来显示两边信息布局)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <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="#">BBS</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">
                <li class="active"><a href="#">文章 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">随笔</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="">欢迎您</a></li>
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操作 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="/set_password/">密码修改</a></li>
                            <li><a href="#">修改头像</a></li>

                            <li role="separator" class="divider"></li>
                            <li><a href="/logout/">注销</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/login/">登录</a></li>
                    <li><a href="/register/">注册</a></li>
                {% endif %}
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        # 左侧信息栏占用2个栅格
        <div class="col-md-2 col-sm-2 col-xs-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">坚持就是胜利</h3>
                </div>
                <div class="panel-body">
                    一起加油!
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">一点一滴</h3>
                </div>
                <div class="panel-body">
                    一分一秒!
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">寻寻觅觅,冷冷清清,凄凄惨惨戚戚</h3>
                </div>
                <div class="panel-body">
                    乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急!
                </div>
            </div>
        </div>
        # 中间显示文章相关信息占用10个栅格
        <div class="col-md-8 col-sm-8 col-xs-6">

        </div>
        # 右侧信息栏占用2个栅格
        <div class="col-md-2 col-sm-2 col-xs-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">坚持就是胜利</h3>
                </div>
                <div class="panel-body">
                    一起加油!
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">一点一滴</h3>
                </div>
                <div class="panel-body">
                    一分一秒!
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">寻寻觅觅,冷冷清清,凄凄惨惨戚戚</h3>
                </div>
                <div class="panel-body">
                    乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急!
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

 第二步:使用Django admin后台管理,快速导入相关文章信息

Django admin后台管理相关设置:

① 必须是超级用户才可以登录后台管理:在pycharm菜单栏Tools下,使用Run manage.py Task..,以命令行形式:createsuperuser来创建超级管理员用户

② 需要将需要操作的模型表(models.py)全部都注册到对应的应用名(app01)下的admin.py文件中

app01下admin.py文件中:

from django.contrib import admin
from app01 import models
# Register your models here.

# 注册顺序没有关系,主要注册即可
admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)

 这里说个小知识点:上图黑框部分表明都是英文,不方便查看,可以设置成中文:

 在models.py文件中:UserInfo类下面添加代码:(其它表同理改之)

    class Meta:
        verbose_name_plural = '用户表'
        # verbose_name = '用户表'  # 用这个默认会在用户名后面加个s后缀,不建议使用

注意别忘了数据库迁移命令,让其生效

 ③ 通过Django administration为文章表添加文章信息:

文章添加完成会发现一个问题:


解决方法:models.py文件:UserInfo表的类下面添加:其它表的显示同设置即可

    def __str__(self):
        return self.title

强调一下:设置用户表进行博客站点的关联:(点击进入用户表>>>点击需要关联博客站点的用户名>>>最下面设置:)

 

第三步:将文章信息渲染到主页面,同时添加文章下方相关信息的展示(作者、发布时间、评论数、点赞数)

信息录入完成,后开始渲染页面:(使用bootstrap组件中的媒体对象列表模板,实现博客园类似文章显示效果)

先查看页面渲染效果:

接下来详细说说,用户头像如何显示的操作:

我们知道:用户在注册时候头像文件是单独放在一个叫avatar的文件夹下面:

我们知道:在Django中

网站的静态文件都放在static文件夹下,并且需要在settings配置文件中进行添加代码进行设置,所以:

用户上传的静态文件也需要单独放在另外一个文件夹(media文件夹)下

在media文件夹下再存放对应文件夹:
        avatar文件夹(用户上传的头像)

        files文件夹(用户上传的文件)

        biography文件夹(用户上传的简历文件)

        ......等等

所以在Django Web框架下的项目,如果想获取到用户上传的头像图片文件,就需要进行相关设置:
我们需要做 件事:

第一件事:规定用户上传的文件都统一放在media文件夹下,settings文件中配置用户上传的文件存放位置

# 在settings.py文件添加代码:

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 用户上传的文件会自动存放到该文件夹并且该文件夹不要你手动创建 

只做第一件事用户图像亦然不会显示:

第二件事:将media文件夹暴露给外界可以直接访问

需要urls.py文件中开一个路由:

# 需要导入一个serve模块:
from django.views.static import serve
from BBS01 import settings

# 手动配置media文件路径(暴露media文件夹内容)
url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})

 此时media文件夹就暴露给外界,我们再在home页面重新修改一下img头像src路径

home.html页面文件内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <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="#">BBS</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">
                <li class="active"><a href="#">文章 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">随笔</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="">欢迎您</a></li>
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操作 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="/set_password/">密码修改</a></li>
                            <li><a href="#">修改头像</a></li>

                            <li role="separator" class="divider"></li>
                            <li><a href="/logout/">注销</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/login/">登录</a></li>
                    <li><a href="/register/">注册</a></li>
                {% endif %}
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        {#        左侧信息栏占用2个栅格#}
        <div class="col-md-2 col-sm-2 col-xs-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">坚持就是胜利</h3>
                </div>
                <div class="panel-body">
                    一起加油!
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">一点一滴</h3>
                </div>
                <div class="panel-body">
                    一分一秒!
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">寻寻觅觅,冷冷清清,凄凄惨惨戚戚</h3>
                </div>
                <div class="panel-body">
                    乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急!雁过也,正伤心,却是旧时相识。
                    满地黄花堆积,憔悴损,如今有谁堪摘!守著窗儿,独自怎生得黑!梧桐更兼细雨,到黄昏、点点滴滴。这次第,怎一个愁字了得!
                </div>
            </div>
        </div>
        {#        中间显示文章相关信息占用10个栅格#}
        <div class="col-md-8 col-sm-8 col-xs-6">
            {% for article in article_list %}
            <ul class="media-list">
                <li class="media">
                    <h4 class="media-heading"><a href="">{{ article.title }}</a></h4>
                    <div class="media-left">
                        <a href="#">
                            <img class="media-object img-thumbnail" src="/media/{{ article.blog.userinfo.avatar }}" width="80">
                        </a>
                    </div>
                    <div class="media-body">
                        {{ article.desc }}
                    </div>
                </li>
            </ul>
{#              Pykk2019 发布于 2019-06-21 评论(0)点赞(71)#}
                <span><a href="">{{ article.blog.userinfo.username }}</a></span>
                <span>&nbsp;发布于&nbsp;</span>
                <span>{{ article.create_time|date:"Y-m-d" }}&nbsp;</span>
                <span class="glyphicon glyphicon-comment">评论({{ article.comment_num }})</span>
                <span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_num }})</span>
            <hr>
            {% endfor %}

        </div>
        {#        右侧信息栏占用2个栅格#}
        <div class="col-md-2 col-sm-2 col-xs-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">坚持就是胜利</h3>
                </div>
                <div class="panel-body">
                    一起加油!
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">一点一滴</h3>
                </div>
                <div class="panel-body">
                    一分一秒!
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">寻寻觅觅,冷冷清清,凄凄惨惨戚戚</h3>
                </div>
                <div class="panel-body">
                    乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急!雁过也,正伤心,却是旧时相识。
                    满地黄花堆积,憔悴损,如今有谁堪摘!守著窗儿,独自怎生得黑!梧桐更兼细雨,到黄昏、点点滴滴。这次第,怎一个愁字了得!
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>
home.html 

此处需要重要强调一下:

基于此方法,你可以做到任意暴露后端任何资源给用户
在作死的边缘试探
settings.py:     MEDIA_ROOT = os.path.join(BASE_DIR,'app01') 
urls.py:    url(r'^app01/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
上述配置会将后端逻辑代码数据库代码全部暴露给用户,慎重使用

第四步:配置blog个人站点路由及文章展示渲染、error.html配置、图片防盗链知识、

 添加blog站点路由:

# 个人站点路由
    url(r'^(?P<blog_name>\w+)$', views.blog),

首先先把访问不存在的用户的站点时候跳转的页面error.html做出来:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Error_404_资源不存在</title>
    <style type="text/css">
    body{margin:8% auto 0;max-width: 550px; min-height: 200px;padding:10px;font-family: Verdana,Arial,Helvetica,sans-serif;font-size:14px;}p{color:#555;margin:10px 10px;}img {border:0px;}.d{color:#404040;}
    </style>
</head>
<body>
<a href="/home/"><img src="/static/error/logo_small.gif" alt="cnblogs"/></a>
<p><b>404.</b> 抱歉! 您访问的资源不存在!</p>
<p class="d">请确认您输入的网址是否正确,如果问题持续存在,请发邮件至514464944&#64;qq.com与我联系。</p>
<p><a href="/home/">返回网站首页</a></p>
</body>
</html>

上面的页面就是我们自己的error,这里需要了解一个知识点:图片防盗链

http 协议中,如果从一个网页跳到另一个网页,http 头字段里面会带个 Referer。图片服务器通过检测 Referer 是否来自规定域名,来进行防盗链。该功能最好在Django的中间件中实现。
接下来搭建blog.html个人站点页面:

一步一步来,先实现文章随用户站点展示:

创建一个blog.html页面:(布局为:顶部标题栏最左边显示站点名称(blog_name),下面3|9布局,方法:把home页面复制过来改动一下就搞定)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <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="#">{{ blog.blog_name }}</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">
                <li class="active"><a href="#">文章 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">随笔</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {% if request.user.is_authenticated %}
                    <li><a href="">欢迎您</a></li>
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多操作 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="/set_password/">密码修改</a></li>
                            <li><a href="#">修改头像</a></li>

                            <li role="separator" class="divider"></li>
                            <li><a href="/logout/">注销</a></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/login/">登录</a></li>
                    <li><a href="/register/">注册</a></li>
                {% endif %}
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        {#        左侧信息栏占用3个栅格#}
        <div class="col-md-3 col-sm-3 col-xs-4">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">坚持就是胜利</h3>
                </div>
                <div class="panel-body">
                    一起加油!
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">一点一滴</h3>
                </div>
                <div class="panel-body">
                    一分一秒!
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">寻寻觅觅,冷冷清清,凄凄惨惨戚戚</h3>
                </div>
                <div class="panel-body">
                    乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急!
                </div>
            </div>
        </div>
        {#        右侧显示文章相关信息占用9个栅格#}
        <div class="col-md-9 col-sm-9 col-xs-8">
            {% for article in article_list %}
            <ul class="media-list">
                <li class="media">
                    <h4 class="media-heading"><a href="">{{ article.title }}</a></h4>
                    <div class="media-left">
                        <a href="#">
                            <img class="media-object img-thumbnail" src="/media/{{ article.blog.userinfo.avatar }}" width="80">
                        </a>
                    </div>
                    <div class="media-body">
                        {{ article.desc }}
                    </div>
                </li>
            </ul>
{#              Pykk2019 发布于 2019-06-21 评论(0)点赞(71)#}
                <span><a href="">{{ article.blog.userinfo.username }}</a></span>
                <span>&nbsp;发布于&nbsp;</span>
                <span>{{ article.create_time|date:"Y-m-d" }}&nbsp;</span>
                <span class="glyphicon glyphicon-comment">评论({{ article.comment_num }})</span>
                <span class="glyphicon glyphicon-thumbs-up">点赞({{ article.up_num }})</span>
            <hr>
            {% endfor %}

        </div>
    </div>
</div>
</body>
</html>
blog站点页面(右边文章显示渲染)

 

这里分析一下:改动地方其实就在这三处,将导航条下方栅格布局282改为39布局,然后视图函数出查出需要的数据,通过locals中传入前端,使用模板语法渲染页面,同样是article_list,和home页面里面的不一样,因为blog页面的article_list是过滤过后的每一个用户的文章列表对象。同时导航条右侧用户信息的相关显示无需变动也无需传参,因为它的渲染是跟用户登录状态的auth模块相关的,它是全局的。

接下来模拟一下每个用户的主题样式:为每个用户建立对应的css文件,大致设置一下a标签的字体、颜色,做个区分,实现它应有的功能就行

 

 第五步:个人站点左侧边栏分类、标签。日期归档展示

 分类:

view.py视图函数数据获取:

blog.html前端页面分类部分渲染:

 这里需要注意一点:我们在Django admin后台管理添加一篇篇文章的时候,做过一件事,就是将每篇文章关联了分类

标签

 标签的渲染过程其实和分类差不多:唯一需要注意的就是将文章与标签进行关联

然后将文章与标签关联:

刷新页面看看效果:

 

日期归档

 这里为了演示多个日期归档,先去Navicat将文章的创建日期稍微改不一样,方便区分

将文章按日期分组归档:

分析:文章创建日期是精确到日的,如果单纯用日期归档,那么这样分类就太精确了而且不合理,一般是按照年月归档就可以了,但是实际表中却没有这个年月的字段,所以我们需要借助一个Django官网提供的模块:TruncMonth,按年月截取拿到对应字段进行相关分组实现我们的目的

-官方提供
            from django.db.models.functions import TruncMonth
            models.Article.objects
            .annotate(month=TruncMonth('create_time'))  # Truncate to month and add to select list
            .values('month')  # Group By month
            .annotate(c=Count('id'))  # Select the count of the grouping
            .values('month', 'c')  # (might be redundant, haven't tested) select month and count    

 使用方法其实上面的英文单词已经说了,这里再详细分析一下我们再使用:

views.py中:
先导入TruncMonth模块

blog.html渲染:

 

看看效果:

 

 第六步:将侧边栏分类的归档信息点击后一一显示渲染到右侧

首先清楚需求:
按标签分类的标签一、二、三点击之后右侧会显示对应标签下的所有文章

按分类的分类一、二、三点击之后右侧会显示对应分类下的所有文章

按日期分类的日期点击之后右侧会显示对应的日期下的所有文章

然后分析实现过程:

① 三种分类的文章显示应该会有三种不同的路由,但是只是在右侧显示,说明走的都是blog视图函数,也就是需要区分判断

② 点击任意一个分类的a标签,我们如果参考博客园的示例,会发现网页路由会有/category/1/、/tag/2/、/achive/2018-12/的内容,这就说明在路由层面进行了筛选判断的同时在前端对应a标签做了对应的路由拼接,以区分需要过滤显示的文章信息

③ 这三种分类如果都指向一个视图函数blog,那么试着用一个路由解决问题

④ 视图函数对路由传来的有名分组参数的判断,以返回过滤不同情况后的article_list。

所以接下来根据上面的分析做出如下行为:

urls.py中添加路由:

url(r'^(?P<username>\w+)/(?P<condition>category|tag|achive)/(?P<param>.*)/$', views.blog),

对视图函数blog进行处理:

def blog(request, username, *args, **kwargs):
    user_obj = models.UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog
    article_list = blog.article_set.all()
    if not user_obj:
        return render(request, 'error.html')
    if kwargs:
        condition = kwargs.get('condition')
        param = kwargs.get('param')
        if condition == 'category':
            article_list = article_list.filter(category_id=param)
        elif condition == 'tag':
            article_list = article_list.filter(tag__pk=param)
        else:
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)
    category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
    date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time'))\
        .values('month').annotate(c=Count('pk')).values_list('month', 'c')
    return render(request, 'blog.html', locals())

 效果展示:

渲染前端页面,实现点击对应分类展示对应文章的目的:

 

二、文章详情页搭建

详情页布局搭建及问题解决:

这里就需要用到模板的继承inclusion_tag:

首先创建一个模板,以blog.html页面为参考依据创建模板:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
    <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/theme/{{ blog.blog_theme }}">
    {% block css %}

    {% endblock %}
</head>
<body>
{#导航条:需要用到用户名对应的blog站点对象,上面的blog_theme样式也需要#}
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <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="#">{{ blog.blog_title }}</a>
        </div>
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        {#        左侧信息栏占用3个栅格#}
{#    左侧信息展示:需要参数:tag_list category_list date_list user_obj#}
        <div class="col-md-3 col-sm-3 col-xs-4">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">随笔标签</h3>
                </div>
                <div class="panel-body">
                    {% for tag in tag_list %}
                        <p><a href="/{{ user_obj.username }}/tag/{{ tag.2 }}/">{{ tag.0 }}({{ tag.1 }})</a></p>
                    {% endfor %}
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">随笔分类</h3>
                </div>
                <div class="panel-body">
                    {% for category in category_list %}
                        <p><a href="/{{ user_obj.username }}/category/{{ category.2 }}/">{{ category.0 }}({{ category.1 }})</a></p>
                    {% endfor %}
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">随笔日期归档</h3>
                </div>
                <div class="panel-body">
                    {% for date in date_list %}
                         <p><a href="/{{ user_obj.username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p>
                    {% endfor %}

                </div>
            </div>
        </div>
        {#        右侧显示文章相关信息占用9个栅格#}
{#      右侧文章列表需要参数:article_list#}
        <div class="col-md-9 col-sm-9 col-xs-8">
        {% block content %}

        {% endblock %}
        </div>
    </div>
</div>
{% block js %}

{% endblock %}
</body>
</html>
base.html模板 初代

新建路由:

url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/', views.article_detail),

 article_detail.html页面继承base.html模板

{% extends 'base.html' %}

{% block content %}
文章详情
{% endblock %}

先看结果再分析情况:

继续分析:

使用模板继承base.html页面,如果每次都需要在视图函数传入动态渲染的参数,未免过于繁琐,这里就需要用到自定义标签的一个方法:inclusion_tag的应用

app01下新建文件夹:templatetags

在此文件夹下新建随便起名的py文件:mytag.py

在该py文件下导入template模块,对注册inclusion_tag,然后开始自定义

from django import template
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = template.Library()

@register.inclusion_tag('left_menu.html', name='left_menu')
# 分析,我们需要的路由有名分组对应的视图函数,传参都会有一个username,
# 所以自定义的inclusion_tag传参可以传入username
def left_menu(username):
    # 将侧边信息栏渲染需要的参数查出,然后返回给left_menu.html
    user_obj = models.UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog
    category_list = models.Category.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
    tag_list = models.Tag.objects.filter(blog=blog).annotate(c=Count('article')).values_list('name', 'c', 'pk')
    date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time'))\
        .values('month').annotate(c=Count('pk')).values_list('month', 'c')
    return {'user_obj': user_obj, 'blog': blog, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list}

left_menu.html,将base.html中左侧栏渲染的html代码剪切过来

<div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">随笔标签</h3>
                </div>
                <div class="panel-body">
                    {% for tag in tag_list %}
                        <p><a href="/{{ user_obj.username }}/tag/{{ tag.2 }}/">{{ tag.0 }}({{ tag.1 }})</a></p>
                    {% endfor %}
                </div>
            </div>
<div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">随笔分类</h3>
                </div>
                <div class="panel-body">
                    {% for category in category_list %}
                        <p><a href="/{{ user_obj.username }}/category/{{ category.2 }}/">{{ category.0 }}({{ category.1 }})</a></p>
                    {% endfor %}
                </div>
            </div>
<div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">随笔日期归档</h3>
                </div>
                <div class="panel-body">
                    {% for date in date_list %}
                         <p><a href="/{{ user_obj.username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p>
                    {% endfor %}

                </div>
            </div>

此时自定义inclusion_tag成功,开始使用:

在base.html模板中使用:

此时左侧动态渲染的left_menu信息就通过inclusion_tag来实现,不管在任何试图函数返回的页面只要继承base.html模板,左侧依旧会动态渲染出来。

然后发现article_detail.html页面在渲染时候查一个blog对象参数,这里只需要在article_detail视图函数中添加即可:

def article_detail(request, username, article_id):
    article_obj = models.Article.objects.filter(pk=article_id).first()
    blog = models.UserInfo.objects.filter(username=username).first().blog
    return render(request, 'article_detail.html', locals())
 文章详情页展示及相关信息展现

① 先把点赞点踩图标渲染出来(去博客园copy一下,html和css都要copy)

article_detail.html:

{% extends 'base.html' %}
{% block 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/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: gray;
}
    </style>
{% endblock %}

{% block content %}
    <h2>{{ article_obj.title }}</h2>
    {{ article_obj.content|safe }}

    <div id="div_digg">
    <div class="diggit">
        <span class="diggnum" id="digg_count">0</span>
    </div>
    <div class="buryit">
        <span class="burynum" id="bury_count">0</span>
    </div>
    <div class="clear"></div>
    <div class="diggword" id="digg_tips">
    </div>
</div>
{% endblock %}

{% block js %}
    <script>

    </script>
{% endblock %}

② 判断用户到底是点的赞还是踩,捕捉时间,得到is_up的值

③ 开始使用ajax发送请求:(需要新开一个路由专门处理点赞点踩)

 

前端发送数据:

后端视图函数up_down处理并返回结果数据:

def up_down(request):
    # 此视图函数只做ajax发送请求用,所以:
    if request.is_ajax():
        article_id = request.POST.get('article_id')
        is_up = request.POST.get('is_up')
        # 最后需要给ajax返回数据,所以先定义一个字典
        back_dic = {'code': 100, 'msg': ''}
        ''' 
        # 点赞点踩必须要思考着4步条件:
         1.是否登录
         2.当前文章是否是自己的写的,自己不能给自己点赞
         3.判断是否已经点赞或点踩了
         4.需要在2张表中进行数据操作
        '''
        # 1.判断用户是否登录
        if request.user.is_authenticated():
            # 2.判断是否在点自己的文章
            article_obj = models.Article.objects.filter(pk=article_id).first()
            if not article_obj.blog.userinfo.pk == request.user.id:
                # 3.判断该文章是否被此用户点过踩或赞
                if not models.UpAndDown.objects.filter(article=article_obj, user=request.user):
                    # 4.此时就可以对数据进行操作了,需要修改2张表的数据
                    # 先改article表的数据,因为这张表需要判断是否点赞还是点踩:
                    is_up = json.loads(is_up)  # 用json反序列化字符串false或true得到布尔类型值
                    if is_up:
                        # 使用F查询实现记录+1(导入:from django.db.models import F)
                        models.Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
                        back_dic['msg'] = '点赞成功'
                    else:
                        models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
                        back_dic['msg'] = '点踩成功'
                    # 再新增UpAndDown表用户与文章点赞点踩关联记录
                    models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
                else:
                    back_dic['code'] = 202
                    back_dic['msg'] = '你已经点过了'
            else:
                back_dic['code'] = 201
                back_dic['msg'] = '不能给自己文章点赞/踩'
        else:
            back_dic['code'] = 200
            # 后端渲染,使用mark_safe(导入:from django.utils.safestring import mark_safe)
            back_dic['msg'] = mark_safe('未登录,请先<a href="/login/">登录</a>')
        return JsonResponse(back_dic)

 前端接收后端处理的数据:

{% block content %}
    <h2>{{ article_obj.title }}</h2>
    {{ article_obj.content|safe }}

    <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>
{% endblock %}

{% block js %}
<script>
    $('.action').on('click',function () {
        let isUp = $(this).hasClass('diggit');
        let $span = $(this).children();
        let $info = $('#digg_tips');
        $.ajax({
            // 这里判断点赞点踩的操作涉及多种情况的判断,应该单独创建一个url路由
            url:'/up_down/',
            type: 'post',
            // 这里我们只需要传2个关键参数:文章id和点赞还是点踩到后端就可以了,因为在后端可以通过request.user来获取到用户对象
            data: {
                'article_id': {{ article_obj.pk }},
                // 需要注意这里的isUp是字符串类型的true或false,在后端需要转换一下才行
                'is_up': isUp,
                // 别忘了csrf_token
                'csrfmiddlewaretoken': '{{ csrf_token }}'},
            success:function (data) {
                if(data.code==100){
                   $span.html(Number($span.html())+1)
                }else {
                    $info.html(data.msg)
                }
            }
        })
    })
</script>
{% endblock %}

 

 

至此文章详情的点赞点踩逻辑处理完成,刷新页面看看效果:

 

posted @ 2019-06-23 20:57  MrSu  阅读(857)  评论(1编辑  收藏  举报