Django项目实战:BBS+Blog项目开发之二页面数据和渲染

上接Django项目实战:BBS+Blog项目开发之一登录和注册功能

二九. 博客系统之系统首页的导航区域  index.thml

29.1 最终效果

29.2 从bootstrap中复制相关的样式,修改index.html

  上bootstrap网站, ---组件---导航条----默认样式的导航条,复制代码到index.html,并修改,删除不用的部分.

  最终效果

 

29.3 index.html源代码, 注意判断用户是否登录右上角的不同,使用{% if request.user.is_authenticated %}判断是否登录状态.

  注意:用户登录名的左边有个人型头像,是bootstrap组件中的user字体图标. <style>#avater_icon{vertical-align: -3px}<style>微调头像的上下位置,与后面的名字持平.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>
    <style>
        #avater_icon{
            font-size: 5px;
            margin-right: 10px;
        }
    </style>
</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="#">博客园</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>
                <li><a href="#">博文</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {#   下面这句等同于{% if request.user.username %}, 就是判断用户是否已经登录 #}
                {% if request.user.is_authenticated %}
                    <li><a href="#"><span id="avater_icon" class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li>
                    <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">Dropdown <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">修改密码</a></li>
                        <li><a href="#">修改头像</a></li>
                        <li><a href="/logout/">注销</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                    </ul>
                </li>
                {% else %}
                    {#    用户未登录显示 注册  登录      #}
                    <li><a href="/login/">登录</a></li>
                    <li><a href=/reg/>注册</a></li>
                {% endif %}

            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

</body>
</html>
index.thml

29.4 注销功能视图

def logout(request):
    auth.logout(request)  # 等同于执行了,request.session.flush()
    return redirect('/login/')

 三零. 博客系统之系统首页的主体布局

30.1 主体部分布局,采用左中右布局, bootstrap网站---全局css样式----栅格系统-----实例:流式布局容器-----实例:从堆叠到水平排列

<div class="container-fluid">
    <div class="row">
        {#  左中右布局     #}
        <div class="col-md-3">111</div>
        <div class="col-md-6">222</div>
        <div class="col-md-3">333</div>
</div>

30.2 bootstrap网站---组件------面板.    将面板内容复制到col列中,放置多个面板,修改panel-primary, 这里在左右部分一共放了六个面板, 六中颜色情境效果.

        <div class="col-md-3">
            {#  bootstrap,组件中的面板  #}
            <div class="panel panel-primary">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
          </div>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>
    <style>
        #avater_icon {
            font-size: 5px;
            margin-right: 10px;
        }
    </style>
</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="#">博客园</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>
                <li><a href="#">博文</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {#   下面这句等同于{% if request.user.username %}, 就是判断用户是否已经登录 #}
                {% if request.user.is_authenticated %}
                    <li><a href="#"><span id="avater_icon"
                                          class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">Dropdown <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#">修改密码</a></li>
                            <li><a href="#">修改头像</a></li>
                            <li><a href="/logout/">注销</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="#">Separated link</a></li>
                        </ul>
                    </li>
                {% else %}
                    {#    用户未登录显示 注册  登录      #}
                    <li><a href="/login/">登录</a></li>
                    <li><a href=/reg/>注册</a></li>
                {% endif %}

            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        {#  左中右布局     #}
        <div class="col-md-3">
            {#  bootstrap,组件中的面板          #}
            <div class="panel panel-primary">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-success">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
        <div class="col-md-6">222</div>
        <div class="col-md-3">
            <div class="panel panel-warning">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-default">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>
index.thml

30.3 完整效果

 三一. 博客系统之admin的简单实用  网站后台管理, 方便管理数据库

31.1 明确一点,必须是超级用户才能用admin. admin是Django系统默认开启的功能.

31.2 使用前必须先把models.py中的表注册一下

  在blog文件夹下admin.py文件中注册, 注意注册表的固定写法

from django.contrib import admin
from blog import models

# Register your models here.
# 注册数据库中的表, 注册过就可以在admin后台看到了
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.ArticleUpDown)
admin.site.register(models.Comment)

31.3 后台登录效果

 三二. 博客系统之基于admin录入文章数据

32.1 按照下面的顺序添加:

  先有用户user,再有博客blogs, 然后每个博客中增加分类categorys, 最后录入文章内容articles.录入文章时,注意作者和对应的分类不能弄混淆.

 

 三三. 博客园系统之系统首页的文章列表的渲染1

33.1 最终效果, 渲染了文章标题, 头像, 摘要,下划线

 

 33.2 视图函数中关键代码, 取出文章表中所有信息

def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})

33.3 index.html模板中关键代码, 注意循环取出文章, 头像的路径, 头像和摘要是通过media-left和media-right来分成左右两边的.

        <div class="col-md-6">
            <div class="article-list">
                {% for article in article_list %}
                    <div class="article-item">
                        <h5><a href="">{{ article.title }}</a></h5>
                        <div class="article-desc">
                            <span class="media-left">
                                <a href=""><img width="56" height="56" srcset="/media/{{ article.user.avatar }}"></a>
                            </span>
                            <span class="media-right">
                                {{ article.desc }}
                            </span>
                        </div>
                    </div>
                    <hr>
                {% endfor %}
            </div>
        </div>

 三四. 博客系统之系统首页的文章列表的渲染2

34.1 效果, 渲染文章作者姓名, 发布时间, 评论数, 点赞数,这里的图标使用了bootstrap字体图标组件

 

34.2 index.html关键代码

        .pub-info{
            margin-top: 10px;
        }
                    {# small表示让字小一些 #}
                    <div class="small pub-info">
                        <span><a href="">{{ article.user.username }}</a></span>&nbsp;&nbsp;&nbsp;&nbsp;
                        <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;&nbsp;
                        <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }})&nbsp;&nbsp;&nbsp;&nbsp;
                        <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }})
                    </div>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>
    <style>
        #avater_icon {
            font-size: 5px;
            margin-right: 10px;
        }
        .pub-info{
            margin-top: 10px;
        }
    </style>
</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="#">博客园</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>
                <li><a href="#">博文</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                {#   下面这句等同于{% if request.user.username %}, 就是判断用户是否已经登录 #}
                {% if request.user.is_authenticated %}
                    <li><a href="#"><span id="avater_icon"
                                          class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">Dropdown <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#">修改密码</a></li>
                            <li><a href="#">修改头像</a></li>
                            <li><a href="/logout/">注销</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="#">Separated link</a></li>
                        </ul>
                    </li>
                {% else %}
                    {#    用户未登录显示 注册  登录      #}
                    <li><a href="/login/">登录</a></li>
                    <li><a href=/reg/>注册</a></li>
                {% endif %}

            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
    <div class="row">
        {#  左中右布局     #}
        <div class="col-md-3">
            {#  bootstrap,组件中的面板          #}
            <div class="panel panel-primary">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-success">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
        <div class="col-md-6">
            <div class="article-list">
                {% for article in article_list %}
                    <div class="article-item">
                        <h5><a href="">{{ article.title }}</a></h5>
                        <div class="article-desc">
                            <span class="media-left">
                                <a href=""><img width="56" height="56" srcset="/media/{{ article.user.avatar }}"></a>
                            </span>
                            <span class="media-right">
                                {{ article.desc }}
                            </span>
                        </div>
                    </div>
                    {# small表示让字小一些 #}
                    <div class="small pub-info">
                        <span><a href="">{{ article.user.username }}</a></span>&nbsp;&nbsp;&nbsp;&nbsp;
                        <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;&nbsp;
                        <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }})&nbsp;&nbsp;&nbsp;&nbsp;
                        <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }})
                    </div>
                    <hr>
                {% endfor %}

            </div>
        </div>
        <div class="col-md-3">
            <div class="panel panel-warning">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
            <div class="panel panel-default">
                <div class="panel-heading">Panel heading without title</div>
                <div class="panel-body">
                    Panel content
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>
index.thml

 三五. 博客系统之个人站点页面的文章查询

35.1 个人站点页面设计(ORM跨表与分组查询)

35.2 通过URL输入用户名称访问个人博客站点, 关键代码.

# urls.py

    # 个人站点页面
    re_path(r'^(?P<username>\w+)$', views.home_site),
# views.py

def home_site(request, username):
    """
    个人站点视图函数
    :param request:
    :param username:  URL中的路径,即博客用户的名称
    :return:
    """
    # UserInfo.objects.filter(username=username).exists()  # 判断是否有该用户, 和下面的功能基本相同
    user = UserInfo.objects.filter(username=username).first()
    # 判断用户是否存在
    if not user:
        return render(request, 'not_fount.html')

    # 查询当前站点对象
    blog = user.blog

    # 获取该用户或站点对象对应的所有文章,两种查询方法. ORM跨表与分组查询
    # 基于对象查询,反向查询,由用户查出文章
    # article_list = user.article_set.all()
    # 基于双下划线查询__
    article_list = models.Article.objects.filter(user=user)

    return render(request, 'home_site.html')

 三六. 博客系统值个人站点页面的标签与分类查询

36.1 查询每一个标签的文章总数, 每一个分类的文章总数等, 就是要查询下图所示的内容.

 

36.2 查询代码, ORM, 正向查询,反向查询, 基于双下划线查询方法等等 

def home_site(request, username):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    # print(user.email)
    # print(blog)
    

    # (反向查询)查询username用户的所有文章
    article_list = models.Article.objects.filter(user=user)
    article_list2 = user.article_set.all()
    # print(article_list)
    # print(article_list2)


    # 查询当前站点的每一个分类名称以及对应的文章数,分两步:
    # 1.查询每一个分类名称以及对应的文章数
    ret = models.Category.objects.values('pk').annotate(c=Count('article__title')).values('title', "c")
    # 2.查询当前站点的每一个分类名称以及对应的文章数
    ret = models.Category.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values('title', "c")


    # 查询当前站点的每一个标签名称以及对应的文章数,分两步:
    # 1.查询每一个标签名称以及对应的文章数
    ret = models.Tag.objects.values('pk').annotate(c=Count('article__title')).values('title', 'c')
    print(ret)
    # 2.查询当前站点的每一个标签名称以及对应的文章数
    # ret = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values('title', 'c')
    ret = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list('title', 'c')
    print(ret)
    """
    <QuerySet [{'title': '明星Tag', 'c': 2}, {'title': '旧闻Tag', 'c': 1}, {'title': 'Vue3技术Tag', 'c': 1}]>
    <QuerySet [('明星Tag', 2), ('旧闻Tag', 1)]>
    """

    return render(request, 'home_site.html')

 三七. 博客系统值个人站点的日期查询1  待熟练后再学习这一块内容

37.1 博客中一般会按年月对文章进行归档(按年月计算文章数量)

  问题Articles表中存储的create_time创建文章的日期是年月日时分秒毫秒格式,不是年月的格式,怎么办?    

        

 

  解决问题办法: 在文章表中创建一个字段,用户存储年月,就可以了.

37.2 如何在文章中创建年月字段?

 三八. 博客系统之个人站点页面的日期查询2

 

三九. 博客系统之个人站点页面的日期查询3  以后再学

 

四零. 博客系统之个人站点页面的日期查询4  django模块的解决模块

    # 日期归档(django的解决方案)
    from django.db.models.functions import TruncMonth  # 指截断日期到月
    ret = models.Article.objects.filter(user=user)\
        .annotate(month=TruncMonth('create_time'))\
        .values('month')\
        .annotate(c=Count('nid'))\
        .values('month', 'c')
    print(ret)  # <QuerySet [{'month': datetime.datetime(2021, 2, 1, 0, 0, tzinfo=<UTC>), 'c': 2}]>
views.py

四一. 博客系统之个人站点的渲染布局1  头部

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            width: 100%;
            height: 60px;
            background-color: #369;
        }
        .header .title{
            font-size: 18px;
            font-weight: 100;
            line-height: 60px;
            color: white;
            margin-left: 10px;
            margin-top: -10px;
        }
        .backend{
            float: right;
            color: white;
            text-decoration: none;
            font-size: 14px;
            margin-right: 10px;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="header">
        <div class="content">
            <p class="title">
                <span>{{ blog.title }}</span>
                <a href="" class="backend">管理</a>
            </p>
        </div>
    </div>

</body>
</html>
home_site.html

 四二. 博客系统之个人站点页面的渲染布局2  右边  文章列表

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .header {
            width: 100%;
            height: 60px;
            background-color: #369;
        }

        .header .title {
            font-size: 18px;
            font-weight: 100;
            line-height: 60px;
            color: white;
            margin-left: 10px;
            margin-top: -10px;
        }

        .backend {
            float: right;
            color: white;
            text-decoration: none;
            font-size: 14px;
            margin-right: 10px;
            margin-top: 10px;
        }

        .pub-info {
            color: slategray;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div class="header">
    <div class="content">
        <p class="title">
            <span>{{ blog.title }}</span>
            <a href="" class="backend">管理</a>
        </p>
    </div>
</div>
<div class="container">
    <div class="row">
        <div class="col-md-3"></div>
        <div class="col-md-9">
            <div class="article-list">
                {% for article in article_list %}
                    <div class="article-item  clearfix">
                        <h5><a href="">{{ article.title }}</a></h5>
                        <div class="article-desc">
                            {{ article.desc }}
                        </div>
                        <div class="small pub-info pull-right">
                            <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }})&nbsp;&nbsp;&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }})
                        </div>
                    </div>
                    <hr>
                {% endfor %}
            </div>
        </div>
    </div>
</div>

</body>
</html>
home_site.html

 四三. 博客系统之个人站点页面的渲染布局3   左侧  标签,分类,归档

 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .header {
            width: 100%;
            height: 60px;
            background-color: #369;
        }

        .header .title {
            font-size: 18px;
            font-weight: 100;
            line-height: 60px;
            color: white;
            margin-left: 10px;
            margin-top: -10px;
        }

        .backend {
            float: right;
            color: white;
            text-decoration: none;
            font-size: 14px;
            margin-right: 10px;
            margin-top: 10px;
        }

        .pub-info {
            color: slategray;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div class="header">
    <div class="content">
        <p class="title">
            <span>{{ blog.title }}</span>
            <a href="" class="backend">管理</a>
        </p>
    </div>
</div>
<div class="container">
    <div class="row">
        <div class="col-md-3">
            <div class="panel panel-warning">
              <div class="panel-heading">我的标签</div>
              <div class="panel-body">
                {% for tag in tag_list %}
                    <p>{{ tag.0 }}({{ tag.1 }})</p>
                {% endfor %}
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">随笔分类</div>
              <div class="panel-body">
                {% for category in category_list %}
                    <p>{{ category.0 }}({{ category.1 }})</p>
                {% endfor %}
              </div>
            </div>
            <div class="panel panel-success">
              <div class="panel-heading">日期归档</div>
              <div class="panel-body">
                {% for date in date_list %}
                    <p>{{ date.0|date:"Y-m" }}({{ date.1 }})</p>
                {% endfor %}
              </div>
            </div>
        </div>
        <div class="col-md-9">
            <div class="article-list">
                {% for article in article_list %}
                    <div class="article-item  clearfix">
                        <h5><a href="">{{ article.title }}</a></h5>
                        <div class="article-desc">
                            {{ article.desc }}
                        </div>
                        <div class="small pub-info pull-right">
                            <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }})&nbsp;&nbsp;&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }})
                        </div>
                    </div>
                    <hr>
                {% endfor %}
            </div>
        </div>
    </div>
</div>

</body>
</html>
home_site.html
from django.shortcuts import render, HttpResponse, redirect
from django.contrib import auth
from django.http import JsonResponse
from django.db.models import Count  # 导入聚合函数
from blog import models
from blog.models import UserInfo
from blog.myforms import RegForm  # 导入自定义验证


def reg(request):
    if request.is_ajax():  # 和request.method == "POST"效果一样
        # print(request.POST)
        form = RegForm(request.POST)  # 把传来的数据进行校验
        response = {'user': None, 'msg': None}
        if form.is_valid():
            response['user'] = form.cleaned_data.get('user')
            # 生成一条用户记录
            user = form.cleaned_data.get('user')
            pwd = form.cleaned_data.get('pwd')
            email = form.cleaned_data.get('email')
            avatar_obj = request.FILES.get('avatar')  # 注意凡是客户端传过来的文件,都从FILES中取

            extra = {}
            if avatar_obj:  # 当用户上传了头像时
                extra['avatar'] = avatar_obj
            UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
        else:
            # print(form.cleaned_data)  # 所有校验成功的信息
            # print(form.errors)  # 所有校验失败的信息
            response['msg'] = form.errors
        return JsonResponse(response)

    form = RegForm()
    return render(request, 'reg.html', {'form': form})


def login(request):
    if request.method == 'POST':
        response = {'user': None, 'msg': None}
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')
        valid_code_str = request.session.get('valid_code')
        if valid_code.upper() == valid_code_str.upper():
            user = auth.authenticate(username=user, password=pwd)
            if user:
                auth.login(request, user)
                response['user'] = user.username
            else:
                response['msg'] = '用户名或密码错误'
        else:
            response['msg'] = '验证码错误'
        return JsonResponse(response)

    return render(request, 'login.html')

def logout(request):
    auth.logout(request)  # 等同于执行了,request.session.flush()
    return redirect('/login/')


def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})


def get_validCode_img(request):
    """生成验证码图片"""

    from blog.utils.validCode import get_validCode_img
    data = get_validCode_img(request)

    # 将图片渲染到模板页面
    return HttpResponse(data)


def home_site(request, username):
    """
    个人站点视图函数
    :param request:
    :param username:  URL中的路径,即博客用户的名称
    :return:
    """
    # UserInfo.objects.filter(username=username).exists()  # 判断是否有该用户, 和下面的功能基本相同
    user = UserInfo.objects.filter(username=username).first()
    # 判断用户是否存在
    if not user:
        return render(request, 'not_fount.html')

    # 查询当前站点对象
    blog = user.blog

    # 获取该用户或站点对象对应的所有文章,两种查询方法
    # 基于对象查询,反向查询,由用户查出文章
    # article_list = user.article_set.all()
    # 基于双下划线查询__
    article_list = models.Article.objects.filter(user=user)

    # 查询当前站点的每一个分类名称以及对应的文章数
    category_list = models.Category.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c')
    # print(ret)

    # 查询当前站点的每一个标签名称以及对应的文章数
    tag_list = models.Tag.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c')
    # print(ret)

    # 日期归档(django的解决方案)
    from django.db.models.functions import TruncMonth  # 指截断日期到月
    date_list = models.Article.objects.filter(user=user)\
        .annotate(month=TruncMonth('create_time'))\
        .values('month')\
        .annotate(c=Count('nid'))\
        .values_list('month', 'c')
    # print(date_list)  # <QuerySet [(datetime.datetime(2021, 2, 1, 0, 0, tzinfo=<UTC>), 2)]>
    return render(request, 'home_site.html', {'blog': blog, 'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list})
views.py

 四四. 博客系统之个人站点页面的跳转过滤功能的实现1    URL

re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site),  # archive用于日期归档的路径

 四五. 博客系统之个人站点页面的跳转过滤功能的实现2  视图

45.1 最终效果, 通过URL跳转到指定的分类中

 

45.2 关键代码:本质是从URL中提取出路径,然后对文章进行筛选, 注意kwargs.

def home_site(request, username, **kwargs):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    # print(user.email)
    # print(blog)

    if kwargs:
        # 个人站点跳转
        condition = kwargs.get('condition')  # 使用**kwargs目的是可以接受任意多个参数
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = models.Article.objects.filter(user=user).filter(tags__title=param)
            # print(article_list)
        elif condition == 'category':
            article_list = models.Article.objects.filter(user=user).filter(category__title=param)
        else:
            # 日期归档
            year, month = param.split('-')
            article_list = models.Article.objects.filter(user=user).filter(create_time__year=year, create_time__month=month)
    else:
        # 个人站点
        # (反向查询)查询username用户的所有文章
        article_list = models.Article.objects.filter(user=user)
        # article_list2 = user.article_set.all()
        # print(article_list)
        # print(article_list2)

    # 查询当前站点的每一个分类名称以及对应的文章数,分两步:
    # 1.查询每一个分类名称以及对应的文章数
    # ret = models.Category.objects.values('pk').annotate(c=Count('article__title')).values('title', "c")
    # 2.查询当前站点的每一个分类名称以及对应的文章数
    category_list = models.Category.objects.filter(blog=blog).values('pk').annotate(
        c=Count('article__title')).values_list('title', "c")

    # 查询当前站点的每一个标签名称以及对应的文章数,分两步:
    # 1.查询每一个标签名称以及对应的文章数
    # ret = models.Tag.objects.values('pk').annotate(c=Count('article__title')).values('title', 'c')
    # 2.查询当前站点的每一个标签名称以及对应的文章数
    # ret = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values('title', 'c')
    tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
        'title', 'c')

    # 按月份将文章归档
    from django.db.models.functions import TruncMonth
    date_list = models.Article.objects.filter(user=user) \
        .annotate(month=TruncMonth('create_time')) \
        .values('month') \
        .annotate(c=Count('nid')) \
        .values_list('month', 'c')

    return render(request, 'home_site.html',
                  {'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list,
                   'date_list': date_list, 'blog': blog})
views.py
"""cnblog URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve
from blog import views
from cnblog import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('logout/', views.logout),
    path('get_validCode_img/', views.get_validCode_img),
    path('reg/', views.reg),
    re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

    # 个人站点跳转
    re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site),
    # 个人站点
    re_path(r'^(?P<username>\w+)/$', views.home_site),

]
urls.py

 四六. 博客系统值个人站点页面的跳转过滤功能的实现3

优化代码,和增加<a>标签,点击实现跳转

 

"""cnblog URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve  # serve器

from blog import views
from cnblog import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('logout/', views.logout),
    path('get_validCode_img/', views.get_validCode_img),
    path('reg/', views.reg),

    # media配置, 外网直接访问用户上传文件的media文件夹的路由
    re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),  # 固定写法

    # 个人站点页面
    re_path(r'^(?P<username>\w+)/$', views.home_site),

    # 个人站点页面跳转
    re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site),

]
urls.py
from django.shortcuts import render, HttpResponse, redirect
from django.contrib import auth
from django.http import JsonResponse
from django.db.models import Count  # 导入聚合函数
from blog import models
from blog.models import UserInfo
from blog.myforms import RegForm  # 导入自定义验证


def reg(request):
    if request.is_ajax():  # 和request.method == "POST"效果一样
        # print(request.POST)
        form = RegForm(request.POST)  # 把传来的数据进行校验
        response = {'user': None, 'msg': None}
        if form.is_valid():
            response['user'] = form.cleaned_data.get('user')
            # 生成一条用户记录
            user = form.cleaned_data.get('user')
            pwd = form.cleaned_data.get('pwd')
            email = form.cleaned_data.get('email')
            avatar_obj = request.FILES.get('avatar')  # 注意凡是客户端传过来的文件,都从FILES中取

            extra = {}
            if avatar_obj:  # 当用户上传了头像时
                extra['avatar'] = avatar_obj
            UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
        else:
            # print(form.cleaned_data)  # 所有校验成功的信息
            # print(form.errors)  # 所有校验失败的信息
            response['msg'] = form.errors
        return JsonResponse(response)

    form = RegForm()
    return render(request, 'reg.html', {'form': form})


def login(request):
    if request.method == 'POST':
        response = {'user': None, 'msg': None}
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')
        valid_code_str = request.session.get('valid_code')
        if valid_code.upper() == valid_code_str.upper():
            user = auth.authenticate(username=user, password=pwd)
            if user:
                auth.login(request, user)
                response['user'] = user.username
            else:
                response['msg'] = '用户名或密码错误'
        else:
            response['msg'] = '验证码错误'
        return JsonResponse(response)

    return render(request, 'login.html')

def logout(request):
    auth.logout(request)  # 等同于执行了,request.session.flush()
    return redirect('/login/')


def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})


def get_validCode_img(request):
    """生成验证码图片"""

    from blog.utils.validCode import get_validCode_img
    data = get_validCode_img(request)

    # 将图片渲染到模板页面
    return HttpResponse(data)


def home_site(request, username, **kwargs):  # kwargs是用于区分是否是个人页面跳转
    """
    个人站点视图函数
    :param request:
    :param username:  URL中的路径,即博客用户的名称
    :return:
    """
    # UserInfo.objects.filter(username=username).exists()  # 判断是否有该用户, 和下面的功能基本相同
    user = UserInfo.objects.filter(username=username).first()
    # 判断用户是否存在
    if not user:
        return render(request, 'not_fount.html')

    # 查询当前站点对象
    blog = user.blog

    # 获取该用户或站点对象对应的所有文章,两种查询方法
    # 基于对象查询,反向查询,由用户查出文章
    # article_list = user.article_set.all()
    # 基于双下划线查询__
    article_list = models.Article.objects.filter(user=user)
    if kwargs:
        condition = kwargs.get('condition')
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = article_list.filter(tags__title=param)
        elif condition == 'category':
            article_list = article_list.filter(category__title=param)
        else:
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)


    # 查询当前站点的每一个分类名称以及对应的文章数
    category_list = models.Category.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c')
    # print(ret)

    # 查询当前站点的每一个标签名称以及对应的文章数
    tag_list = models.Tag.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c')
    # print(ret)

    # 日期归档(django的解决方案)
    from django.db.models.functions import TruncMonth  # 指截断日期到月
    date_list = models.Article.objects.filter(user=user)\
        .annotate(month=TruncMonth('create_time'))\
        .values('month')\
        .annotate(c=Count('nid'))\
        .values_list('month', 'c')
    # print(date_list)  # <QuerySet [(datetime.datetime(2021, 2, 1, 0, 0, tzinfo=<UTC>), 2)]>
    return render(request, 'home_site.html', {'username': username, 'blog': blog, 'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list})
views.py
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .header {
            width: 100%;
            height: 60px;
            background-color: #369;
        }

        .header .title {
            font-size: 18px;
            font-weight: 100;
            line-height: 60px;
            color: white;
            margin-left: 10px;
            margin-top: -10px;
        }

        .backend {
            float: right;
            color: white;
            text-decoration: none;
            font-size: 14px;
            margin-right: 10px;
            margin-top: 10px;
        }

        .pub-info {
            color: slategray;
            margin-top: 10px;
        }
    </style>
</head>
<body>
<div class="header">
    <div class="content">
        <p class="title">
            <span>{{ blog.title }}</span>
            <a href="" class="backend">管理</a>
        </p>
    </div>
</div>
<div class="container">
    <div class="row">
        <div class="col-md-3">
            <div class="panel panel-warning">
              <div class="panel-heading">我的标签</div>
              <div class="panel-body">
                {% for tag in tag_list %}
                    <p><a href="/{{ username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a></p>
                {% endfor %}
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">随笔分类</div>
              <div class="panel-body">
                {% for category in category_list %}
                    <p><a href="/{{ username }}/category/{{ category.0 }}/">{{ category.0 }}({{ category.1 }})</a></p>
                {% endfor %}
              </div>
            </div>
            <div class="panel panel-success">
              <div class="panel-heading">日期归档</div>
              <div class="panel-body">
                {% for date in date_list %}
                    <p><a href="/{{ username }}/archive/{{ date.0|date:"Y-m" }}/">{{ date.0|date:"Y-m" }}({{ date.1 }})</a></p>
                {% endfor %}
              </div>
            </div>
        </div>
        <div class="col-md-9">
            <div class="article-list">
                {% for article in article_list %}
                    <div class="article-item  clearfix">
                        <h5><a href="">{{ article.title }}</a></h5>
                        <div class="article-desc">
                            {{ article.desc }}
                        </div>
                        <div class="small pub-info pull-right">
                            <span>发布于&nbsp;&nbsp;{{ article.create_time|date:"Y-m-d H:i" }}</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }})&nbsp;&nbsp;&nbsp;&nbsp;
                            <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }})
                        </div>
                    </div>
                    <hr>
                {% endfor %}
            </div>
        </div>
    </div>
</div>

</body>
</html>
home_site.html

 四七. 博客系统值文章详情页的设计  详情页的url,

通过类型/papa/article/1/路径访问详情页

    # 文章详情页
    re_path(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/$', views.article_detail),

 四八. 博客系统文章详情页的数据构建

48.1 文章详情页和个人站点首页的头部、左侧是相同的,这里可以使用模板继承进行

  先建立一个base.thml基础模板,存放顶部和左侧内容.留出右侧,做个空盒子.

  空盒子关键代码: 其它页面继承该页面后,在盒子处进行扩展

        <div class="col-md-9">
            {% block content %}
                {#      预留盒子, 进行扩展      #}
            {% endblock %}
        </div>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            width: 100%;
            height: 60px;
            background-color: #369;
        }
        .header .title{
            margin-left: 10px;
            font-weight: 100;
            line-height: 60px;
            color: white;
            font-size: 22px;
        }
        .backend{
            float: right;
            margin-right: 10px;
            font-size: 14px;
            color: white;
            margin-top: 30px;
            text-decoration: none;
        }
        .pub-info{
            margin-top: 10px;
            color: darkslategray;
        }

    </style>
</head>
<body>
<div class="header">
    <div class="content clearfix">
        <span class="title">{{ blog.title }}</span>
        <a href="" class="backend">管理</a>

    </div>
</div>
<div class="container">
    <div class="row">
        <div class="col-md-3">
            <div class="panel panel-warning">
              <div class="panel-heading">我的标签</div>
              <div class="panel-body">
                 {% for tag in tag_list %}
                    <p><a href="/{{ username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a></p>
                  {% endfor %}
              </div>
            </div>
            <div class="panel panel-danger">
              <div class="panel-heading">文章分类</div>
              <div class="panel-body">
                  {% for category in category_list %}
                    <p><a href="/{{ username }}/category/{{ category.0 }}/">{{ category.0 }}({{ category.1 }})</a></p>
                  {% endfor %}
              </div>
            </div>
            <div class="panel panel-info">
              <div class="panel-heading">日期归档</div>
              <div class="panel-body">
                  {% for date in date_list %}
                    <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y-m' }}({{ date.1 }})</a></p>
                  {% endfor %}
              </div>
            </div>
        </div>
        <div class="col-md-9">
            {% block content %}
                {#      预留盒子, 进行扩展      #}
            {% endblock %}
        </div>
    </div>
</div>

</body>
</html>
base.html

48.2 个人站点首页home_site.html,和文章详情页面article_detail.html,先继承base.html,然后再扩写.就可以拥有相同的顶部和左侧.

  继承{% extends 'base.html' %}, 扩写看下面.

{% extends 'base.html' %}

{% block content %}
    {#  在这里扩写 #}
{% endblock %}
{% extends 'base.html' %}

{% block content %}
    <div class="article-list">
                    {% for article in article_list %}
                        <div class="article-item clearfix">
                            <h5><a href="">{{ article.title }}</a></h5>
                            <div class="article-desc">
                                {{ article.desc }}
                            </div>
                            <div class="small pub-info pull-right">
                                <span>发布于&nbsp;&nbsp;{{ article.create_time|date:'Y-m-d H:i' }}</span>&nbsp;&nbsp;&nbsp;&nbsp;
                                <span class="glyphicon glyphicon-comment">评论数({{ article.comment_count }})</span>&nbsp;&nbsp;&nbsp;&nbsp;
                                <span class="glyphicon glyphicon-thumbs-up">点赞数({{ article.up_count }})</span>&nbsp;&nbsp;&nbsp;&nbsp;
                            </div>
                        </div>

                        <hr>
                    {% endfor %}
                </div>
{% endblock %}
home_site.html
{# 继承 #}
{% extends 'base.html' %}


{% block content %}
    {# 扩写盒子 #}
{% endblock %}
article_detail.html

 48.3 视图函数中建立文章详请页面的函数article_detail, 注意这里为了减少重复代码,将页面顶部和左侧涉及的数据封装成了函数.返回值是{'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}

def get_query_data(username):
    """优化代码,代码复用"""

    user = UserInfo.objects.filter(username=username).first()
    blog = user.blog
    # article_list = models.Article.objects.filter(user=user)
    category_list = models.Category.objects.filter(blog=blog).values('pk').annotate(
        c=Count('article__title')).values_list('title', "c")
    tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
        'title', 'c')

    date_list = models.Article.objects.filter(user=user) \
        .annotate(month=TruncMonth('create_time')) \
        .values('month') \
        .annotate(c=Count('nid')) \
        .values_list('month', 'c')
    return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}
def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """

    context = get_query_data(username)

    return render(request, 'article_detail.html', context)  # 注意这里的返回值参数context其是上面的字典, 在模板中可直接使用字典中的键.
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse  # 传递json数据类型
from django.contrib import auth  # 导入用户组件,用于auth_user表的操作
from django.db.models import Avg, Max, Min, Count
from django.db.models.functions import TruncMonth
from blog import models

from blog.models import UserInfo
from blog.myforms import RegForm  # 导入自定义forms验证规则


def reg(request):
    """用户注册视图"""
    if request.is_ajax():
        form = RegForm(request.POST)
        respone = {'user': None, 'msg': None}
        if form.is_valid():
            respone['user'] = form.cleaned_data.get('user')
            user = form.cleaned_data.get('user')
            pwd = form.cleaned_data.get('pwd')
            email = form.cleaned_data.get('email')
            avatar_obj = request.FILES.get('avatar')
            extra = {}
            if avatar_obj:
                extra['avatar'] = avatar_obj
            user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
        else:
            respone['msg'] = form.errors
        return JsonResponse(respone)

    form = RegForm()  # 带入规则
    return render(request, 'reg.html', {"form": form})


def login(request):
    if request.method == "POST":
        response = {'user': None, 'msg': None}  # 构造登录提示信息
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')  # 获取用户输入的验证码
        valid_code_str = request.session.get('valid_code')  # 从session中提取自动生成的验证码字符串
        if valid_code_str.upper() == valid_code.upper():  # 全部转为大写字母
            user = auth.authenticate(username=user, password=pwd)
            if user:
                auth.login(request, user)  # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量
                response['user'] = user.username  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.
            else:
                response['msg'] = '用户名或密码错误!'
        else:
            response['msg'] = '验证码错误!'
        return JsonResponse(response)  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.

    return render(request, 'login.html')


def get_validCode_img(request):
    """生成验证码图片"""
    from blog.utils.validCode import get_validCode_img  # 导入自定义的生成验证码功能函数
    data = get_validCode_img(request)
    return HttpResponse(data)


def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})


def logout(request):
    auth.logout(request)
    return redirect('/login/')


def get_query_data(username):
    """优化代码,代码复用"""

    user = UserInfo.objects.filter(username=username).first()
    blog = user.blog
    # article_list = models.Article.objects.filter(user=user)
    category_list = models.Category.objects.filter(blog=blog).values('pk').annotate(
        c=Count('article__title')).values_list('title', "c")
    tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
        'title', 'c')

    date_list = models.Article.objects.filter(user=user) \
        .annotate(month=TruncMonth('create_time')) \
        .values('month') \
        .annotate(c=Count('nid')) \
        .values_list('month', 'c')
    return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}


def home_site(request, username, **kwargs):
    # 此处也可以去除重复的代码.
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    article_list = models.Article.objects.filter(user=user)
    if kwargs:
        # 个人站点跳转
        condition = kwargs.get('condition')  # 使用**kwargs目的是可以接受任意多个参数
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = article_list.filter(tags__title=param)
            # print(article_list)
        elif condition == 'category':
            article_list = article_list.filter(category__title=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).values('pk').annotate(
        c=Count('article__title')).values_list('title', "c")

    tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
        'title', 'c')

    # 按月份将文章归档
    from django.db.models.functions import TruncMonth
    date_list = models.Article.objects.filter(user=user) \
        .annotate(month=TruncMonth('create_time')) \
        .values('month') \
        .annotate(c=Count('nid')) \
        .values_list('month', 'c')

    return render(request, 'home_site.html',
                  {'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list,
                   'date_list': date_list, 'blog': blog, 'username': username})


def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """

    context = get_query_data(username)

    return render(request, 'article_detail.html', context)
views.py

 四九. 博客系统值文章详情页的inclution_tag  类似模板自定义标签过滤器

数据传递的另一种方法

49.1 一个自定义的inclution_tag,模板自定义标签过滤器.实现乘法3*7=21. 直接在模板中得出结果.

  在blog文件夹下新建templatetags文件夹(这个名字不能改成其他名字),新建my_tags.py文件

# my_tags.py

from django import template

register = template.Library()


# 自定义乘法标签过滤器
@register.simple_tag  # 可在模板中直接调用这个函数
def multi_tag(x, y):
    # 实现乘法
    return x * y
# article_detail.html
{% extends 'base.html' %} {% block content %} {# 引入自定义的标签 3*7 #} {% load my_tags %} {% multi_tag 3 7 %} {% endblock %}
# views.py
def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """


    return render(request, 'article_detail.html')

 49.2 另一中数据传递给模板的的方法, 类似于上面的模板自定义函数标签, 的inclutigon_tag方法

  将数据与样式结合成一体, 直接在模板中调用标签就可以了.

  首先, 将views.py中的get_classification_style()函数移动到my_tags.py中, 

@register.inclusion_tag('calssification.html')
def get_classification_style(username):
    """
    获取个人站点左侧数据, (文章详情页左侧数据)
    自定义顶部与左侧的获取数据的标签
    get_classification_style函数获取到数据后,直接传递给模板classification.html
    :param username: 当前地址栏中的用户名
    :return: 左侧所需的所有数据
    """
    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog
    # article_list = models.Article.objects.filter(user=user)
    category_list = models.Category.objects.filter(blog=blog).values('pk').annotate(
        c=Count('article__title')).values_list('title', "c")
    tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
        'title', 'c')

    date_list = models.Article.objects.filter(user=user) \
        .annotate(month=TruncMonth('create_time')) \
        .values('month') \
        .annotate(c=Count('nid')) \
        .values_list('month', 'c')
    return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}

  

from django import template
from blog import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
from django.shortcuts import render

register = template.Library()


# 自定义乘法标签过滤器
@register.simple_tag  # 可在模板中直接调用这个函数
def multi_tag(x, y):
    # 实现乘法
    return x * y


@register.inclusion_tag('calssification.html')
def get_classification_style(username):
    """
    获取个人站点左侧数据, (文章详情页左侧数据)
    自定义顶部与左侧的获取数据的标签
    get_classification_style函数获取到数据后,直接传递给模板classification.html
    :param username: 当前地址栏中的用户名
    :return: 左侧所需的所有数据
    """
    user = models.UserInfo.objects.filter(username=username).first()
    blog = user.blog
    # article_list = models.Article.objects.filter(user=user)
    category_list = models.Category.objects.filter(blog=blog).values('pk').annotate(
        c=Count('article__title')).values_list('title', "c")
    tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
        'title', 'c')

    date_list = models.Article.objects.filter(user=user) \
        .annotate(month=TruncMonth('create_time')) \
        .values('month') \
        .annotate(c=Count('nid')) \
        .values_list('month', 'c')
    return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}
my_tags.py

  然后, 建立calssification.html模板文件,将base.thml中的网站左侧部分移动到新建的模板文件中.下面的代码是base.thml中左侧移动后的:

        <div class="col-md-3">
            {#   个人站点左侧数据和模板 username是参数      #}
            {% load my_tags %}
            {% get_classification_style username %}
        </div>
{# 网站左侧模板和数据 #}
<div>
    <div class="panel panel-warning">
      <div class="panel-heading">我的标签</div>
      <div class="panel-body">
         {% for tag in tag_list %}
            <p><a href="/{{ username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a></p>
          {% endfor %}
      </div>
    </div>
    <div class="panel panel-danger">
      <div class="panel-heading">文章分类</div>
      <div class="panel-body">
          {% for category in category_list %}
            <p><a href="/{{ username }}/category/{{ category.0 }}/">{{ category.0 }}({{ category.1 }})</a></p>
          {% endfor %}
      </div>
    </div>
    <div class="panel panel-info">
      <div class="panel-heading">日期归档</div>
      <div class="panel-body">
          {% for date in date_list %}
            <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y-m' }}({{ date.1 }})</a></p>
          {% endfor %}
      </div>
    </div>
</div>
calssification.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .header{
            width: 100%;
            height: 60px;
            background-color: #369;
        }
        .header .title{
            margin-left: 10px;
            font-weight: 100;
            line-height: 60px;
            color: white;
            font-size: 22px;
        }
        .backend{
            float: right;
            margin-right: 10px;
            font-size: 14px;
            color: white;
            margin-top: 30px;
            text-decoration: none;
        }
        .pub-info{
            margin-top: 10px;
            color: darkslategray;
        }

    </style>
</head>
<body>
<div class="header">
    <div class="content clearfix">
        <span class="title">{{ blog.title }}</span>
        <a href="" class="backend">管理</a>

    </div>
</div>
<div class="container">
    <div class="row">
        <div class="col-md-3">
            {#   个人站点左侧数据和模板 username是参数      #}
            {% load my_tags %}
            {% get_classification_style username %}
        </div>
        <div class="col-md-9">
            {% block content %}
                {#      预留盒子, 进行扩展      #}
            {% endblock %}
        </div>
    </div>
</div>

</body>
</html>
base.html

  最后, 修改views.py中的个人站点视图

def home_site(request, username, **kwargs):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    article_list = models.Article.objects.filter(user=user)
    if kwargs:
        # 个人站点跳转
        condition = kwargs.get('condition')  # 使用**kwargs目的是可以接受任意多个参数
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = article_list.filter(tags__title=param)
        elif condition == 'category':
            article_list = article_list.filter(category__title=param)
        else:
            # 日期归档
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

    return render(request, 'home_site.html', {'username': username, 'blog': blog,'article_list': article_list})


def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """

    # context = get_query_data(username)

    # return render(request, 'article_detail.html', context)
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')
    # 查询username用户的blog对象
    blog = user.blog
    return render(request, 'article_detail.html', locals())  # locals()不能丢
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse  # 传递json数据类型
from django.contrib import auth  # 导入用户组件,用于auth_user表的操作
from django.db.models import Avg, Max, Min, Count
from django.db.models.functions import TruncMonth
from blog import models

from blog.models import UserInfo
from blog.myforms import RegForm  # 导入自定义forms验证规则


def reg(request):
    """用户注册视图"""
    if request.is_ajax():
        form = RegForm(request.POST)
        respone = {'user': None, 'msg': None}
        if form.is_valid():
            respone['user'] = form.cleaned_data.get('user')
            user = form.cleaned_data.get('user')
            pwd = form.cleaned_data.get('pwd')
            email = form.cleaned_data.get('email')
            avatar_obj = request.FILES.get('avatar')
            extra = {}
            if avatar_obj:
                extra['avatar'] = avatar_obj
            user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
        else:
            respone['msg'] = form.errors
        return JsonResponse(respone)

    form = RegForm()  # 带入规则
    return render(request, 'reg.html', {"form": form})


def login(request):
    if request.method == "POST":
        response = {'user': None, 'msg': None}  # 构造登录提示信息
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')  # 获取用户输入的验证码
        valid_code_str = request.session.get('valid_code')  # 从session中提取自动生成的验证码字符串
        if valid_code_str.upper() == valid_code.upper():  # 全部转为大写字母
            user = auth.authenticate(username=user, password=pwd)
            if user:
                auth.login(request, user)  # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量
                response['user'] = user.username  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.
            else:
                response['msg'] = '用户名或密码错误!'
        else:
            response['msg'] = '验证码错误!'
        return JsonResponse(response)  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.

    return render(request, 'login.html')


def get_validCode_img(request):
    """生成验证码图片"""
    from blog.utils.validCode import get_validCode_img  # 导入自定义的生成验证码功能函数
    data = get_validCode_img(request)
    return HttpResponse(data)


def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})


def logout(request):
    auth.logout(request)
    return redirect('/login/')


# def get_query_data(username):
#     """优化代码,代码复用"""
#
#     user = UserInfo.objects.filter(username=username).first()
#     blog = user.blog
#     # article_list = models.Article.objects.filter(user=user)
#     category_list = models.Category.objects.filter(blog=blog).values('pk').annotate(
#         c=Count('article__title')).values_list('title', "c")
#     tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
#         'title', 'c')
#
#     date_list = models.Article.objects.filter(user=user) \
#         .annotate(month=TruncMonth('create_time')) \
#         .values('month') \
#         .annotate(c=Count('nid')) \
#         .values_list('month', 'c')
#     return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}


def home_site(request, username, **kwargs):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    article_list = models.Article.objects.filter(user=user)
    if kwargs:
        # 个人站点跳转
        condition = kwargs.get('condition')  # 使用**kwargs目的是可以接受任意多个参数
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = article_list.filter(tags__title=param)
            # print(article_list)
        elif condition == 'category':
            article_list = article_list.filter(category__title=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).values('pk').annotate(
    #     c=Count('article__title')).values_list('title', "c")
    #
    # tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list(
    #     'title', 'c')
    #
    # # 按月份将文章归档
    # from django.db.models.functions import TruncMonth
    # date_list = models.Article.objects.filter(user=user) \
    #     .annotate(month=TruncMonth('create_time')) \
    #     .values('month') \
    #     .annotate(c=Count('nid')) \
    #     .values_list('month', 'c')

    # return render(request, 'home_site.html',
    #               {'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list,
    #                'date_list': date_list, 'blog': blog, 'username': username})
    return render(request, 'home_site.html', {'username': username, 'blog': blog,'article_list': article_list})


def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """

    # context = get_query_data(username)

    # return render(request, 'article_detail.html', context)
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')
    # 查询username用户的blog对象
    blog = user.blog
    return render(request, 'article_detail.html', locals())  # locals()不能丢
views.py

目录结构:

五零. 博客系统值文章详情页渲染的标签字符串转义1

50.1 渲染出文章详情页后,会发现内容部分是纯粹的无格式的杂乱字符串.无法显示图片等相关的html内容, 这就是django模板语法的转义.

 

 

 

 五一. 博客系统值文章详情页渲染的标签字符串转义2  加safe不让其转义

# article_detail.html
{% extends 'base.html' %}

{% block content %}

    <h3 class="text-center">{{ article_obj.title }}</h3>
    <div class="cont">
        {{ article_obj.content|safe }}
    </div>
{% endblock %}

显示就正确了,渲染出了网页效果.

 

 

转义的目的是,安全,防止用户输入<script>等代码.防止xss攻击.会有安全隐患.

在用户提交文章内容时,就应当拒绝用户存储<script>等有安全隐患的代码.

 五二. 博客系统之文章点赞样式的构建

52.1 将base.html中的样式与模板进行分离, 在静态文件夹下建立home_site.css和article_detail.css样式文件.点赞的html css代码可以直接从博客园中复制.

博客园有反爬虫机制, 需要把点赞和反对的静态图片下载到本地.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="/static/css/home_site.css">
    <link rel="stylesheet" href="/static/css/article_detail.css">
</head>

<body>
<div class="header">
    <div class="content clearfix">
        <span class="title">{{ blog.title }}</span>
        <a href="" class="backend">管理</a>

    </div>
</div>
<div class="container">
    <div class="row">
        <div class="col-md-3">
            {#   个人站点左侧数据和模板 username是参数      #}
            {% load my_tags %}
            {% get_classification_style username %}
        </div>
        <div class="col-md-9">
            {% block content %}
                {#      预留盒子, 进行扩展      #}
            {% endblock %}
        </div>
    </div>
</div>

</body>
</html>
base.html
*{
    margin: 0;
    padding: 0;
}
.header{
    width: 100%;
    height: 60px;
    background-color: #369;
}
.header .title{
    margin-left: 10px;
    font-weight: 100;
    line-height: 60px;
    color: white;
    font-size: 22px;
}
.backend{
    float: right;
    margin-right: 10px;
    font-size: 14px;
    color: white;
    margin-top: 30px;
    text-decoration: none;
}
.pub-info{
    margin-top: 10px;
    color: darkslategray;
}
.container{
    margin-top: 10px;
}
home_site.css
.article_info .title{
    margin-bottom: 20px;
}

#div_digg{
    word-break: break-all;
    line-height: 1.5;
    color: #000;
    float: right;
    margin-bottom: 10px;
    margin-right: 30px;
    font-size: 12px;
    width: 125px;
    text-align: center;
    margin-top: 10px;
}

#div_digg .diggit{
    word-break: break-all;
    line-height: 1.5;
    color: #000;
    font-size: 12px;
    float: left;
    width: 46px;
    height: 52px;
    background: url('https://static.cnblogs.com/images/upup.gif') no-repeat;
    text-align: center;
    cursor: pointer;
    margin-top: 2px;
    padding-top: 5px;
}
#div_digg .buryit{
    word-break: break-all;
    line-height: 1.5;
    color: #000;
    font-size: 12px;
    float: right;
    margin-left: 20px;
    width: 46px;
    height: 52px;
    background: url('https://static.cnblogs.com/images/upup.gif') no-repeat;
    text-align: center;
    cursor: pointer;
    margin-top: 2px;
    padding-top: 5px;
}
#div_digg .diggword{
    word-break: break-all;
    line-height: 1.5;
    text-align: center;
    margin-top: 5px;
    margin-left: 0;
    font-size: 12px;
    color: red;
}

#div_digg .clear{
    clear: both;
}
article_detail.css

 

 

五三. 博客系统值文章点赞的事件绑定

{# 继承 #}
{% extends 'base.html' %}

{% block content %}
    <div class="article_info">
        <h3 class="text-center">{{ article_obj.title }}</h3>
        <div class="cont">
            {{ article_obj.content|safe }}
        </div>
        <div id="div_digg">
            {#    diggit action   推荐       #}
            <div class="diggit action">
                <span class="diggnum" id="digg_count">1</span>
            </div>
            {#    diggit action   反对       #}
            <div class="buryit action">
                <span class="burynum" id="bury_count">0</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red;"></div>
        </div>
        <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                alert(is_up);
            })
        </script>
    </div>
{% endblock %}

 

 五四. 博客系统值文章点赞的保存

54.1 article_detail.html关键代码

        <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                $.ajax({
                    url: '/digg/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),
                        'article_id': {{ article_obj.pk }},
                        'is_up': is_up,
                    },
                    success: function (data) {
                        console.log(data)
                    }
                })
            })
        </script>
{# 继承 #}
{% extends 'base.html' %}

{% block content %}
    <div class="article_info">
        {% csrf_token %}
        <h3 class="text-center">{{ article_obj.title }}</h3>
        <div class="cont">
            {{ article_obj.content|safe }}
        </div>
        <div id="div_digg">
            {#    diggit action   推荐       #}
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            {#    diggit action   反对       #}
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red;"></div>
        </div>
        <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                $.ajax({
                    url: '/digg/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),
                        'article_id': {{ article_obj.pk }},
                        'is_up': is_up,
                    },
                    success: function (data) {
                        console.log(data)
                    }
                })
            })
        </script>
    </div>
{% endblock %}
article_detail.html

54.2 url.py建立路由, 注意路由向上放.防止被下面的通配符通配.

    # 点赞
    path('digg/', views.digg),
"""cnblog URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from django.views.static import serve  # serve器

from blog import views
from cnblog import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('logout/', views.logout),
    path('get_validCode_img/', views.get_validCode_img),
    path('reg/', views.reg),
    path('digg/', views.digg),

    # media配置, 外网直接访问用户上传文件的media文件夹的路由
    re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),  # 固定写法

    # 个人站点页面
    re_path(r'^(?P<username>\w+)/$', views.home_site),

    # 个人站点页面跳转
    re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site),

    # 个人站点文章详情页面
    re_path(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/$', views.article_detail),


]
urls.py

54.3 视图文件,建立点赞视图

def digg(request):
    print(request.user)
    import json
    article_id = request.POST.get('article_id')
    is_up = json.loads(request.POST.get('is_up'))  # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值
    user_id = request.user.pk  # 直接获取user_id,没有必要通过客户端传送

    ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)

    return HttpResponse('OK')

 五五. 博客系统之纹章点赞数的数据同步  用到F查询,点赞总数自加1

def digg(request):
    print(request.user)
    import json
    article_id = request.POST.get('article_id')
    is_up = json.loads(request.POST.get('is_up'))  # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值
    user_id = request.user.pk  # 直接获取user_id,没有必要通过客户端传送

    ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)

    from django.db.models import F
    if is_up:
        models.Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)

    return HttpResponse('OK')
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse  # 传递json数据类型
from django.contrib import auth  # 导入用户组件,用于auth_user表的操作
from django.db.models import Avg, Max, Min, Count
from django.db.models.functions import TruncMonth
from blog import models

from blog.models import UserInfo
from blog.myforms import RegForm  # 导入自定义forms验证规则


def reg(request):
    """用户注册视图"""
    if request.is_ajax():
        form = RegForm(request.POST)
        respone = {'user': None, 'msg': None}
        if form.is_valid():
            respone['user'] = form.cleaned_data.get('user')
            user = form.cleaned_data.get('user')
            pwd = form.cleaned_data.get('pwd')
            email = form.cleaned_data.get('email')
            avatar_obj = request.FILES.get('avatar')
            extra = {}
            if avatar_obj:
                extra['avatar'] = avatar_obj
            user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
        else:
            respone['msg'] = form.errors
        return JsonResponse(respone)

    form = RegForm()  # 带入规则
    return render(request, 'reg.html', {"form": form})


def login(request):
    if request.method == "POST":
        response = {'user': None, 'msg': None}  # 构造登录提示信息
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')  # 获取用户输入的验证码
        valid_code_str = request.session.get('valid_code')  # 从session中提取自动生成的验证码字符串
        if valid_code_str.upper() == valid_code.upper():  # 全部转为大写字母
            user = auth.authenticate(username=user, password=pwd)
            if user:
                auth.login(request, user)  # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量
                response['user'] = user.username  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.
            else:
                response['msg'] = '用户名或密码错误!'
        else:
            response['msg'] = '验证码错误!'
        return JsonResponse(response)  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.

    return render(request, 'login.html')


def get_validCode_img(request):
    """生成验证码图片"""
    from blog.utils.validCode import get_validCode_img  # 导入自定义的生成验证码功能函数
    data = get_validCode_img(request)
    return HttpResponse(data)


def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})


def logout(request):
    auth.logout(request)
    return redirect('/login/')


def home_site(request, username, **kwargs):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    article_list = models.Article.objects.filter(user=user)
    if kwargs:
        # 个人站点跳转
        condition = kwargs.get('condition')  # 使用**kwargs目的是可以接受任意多个参数
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = article_list.filter(tags__title=param)
            # print(article_list)
        elif condition == 'category':
            article_list = article_list.filter(category__title=param)
        else:
            # 日期归档
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

    return render(request, 'home_site.html', {'username': username, 'blog': blog, 'article_list': article_list})


def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """

    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')
    # 查询username用户的blog对象
    blog = user.blog
    article_obj = models.Article.objects.filter(pk=article_id).first()
    return render(request, 'article_detail.html', locals())  # locals()不能丢


def digg(request):
    print(request.user)
    import json
    article_id = request.POST.get('article_id')
    is_up = json.loads(request.POST.get('is_up'))  # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值
    user_id = request.user.pk  # 直接获取user_id,没有必要通过客户端传送

    ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)

    from django.db.models import F
    if is_up:
        models.Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1)

    return HttpResponse('OK')
views.py

 五六. 博客系统之文章点赞的提示重复操作  判断是否已经点过赞,已经点过怎么处理

56.1 views.py

def digg(request):
    print(request.user)
    import json
    article_id = request.POST.get('article_id')
    is_up = json.loads(request.POST.get('is_up'))  # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值
    user_id = request.user.pk  # 直接获取user_id,没有必要通过客户端传送

    # 判断是否已经点过赞
    response = {'state': True}  # 返回的状态,将传递给模板中的回调函数,默认是True成功, 表示为点过赞或者反对.
    ret_obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
    if not ret_obj:  # 没点过赞
        ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
        # 将点赞写入数据库中
        from django.db.models import F
        article_obj = models.Article.objects.filter(pk=article_id)
        if is_up:
            article_obj.update(up_count=F("up_count")+1)
        else:
            article_obj.update(down_count=F("down_count")+1)
    else:  # 已经点过赞或反对了
        response['state'] = False  # 表示失败
        response['handled'] = ret_obj.is_up  # 获取当前数据库中是点赞过还是点反对过.

    return JsonResponse(response)  # 将状态传递给模板中的回调函数
        <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                $.ajax({
                    url: '/digg/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),
                        'article_id': {{ article_obj.pk }},
                        'is_up': is_up,
                    },
                    success: function (data) {
                        if (data.state){
                            {#  state为True表示之前未点过  #}
                        }else{
                            {# 之前已经点过赞或者点过反对了 #}
                            if (data.handled){
                                $(".diggword").html('您已经点过赞了!')
                            }else {
                                $(".diggword").html('您已经点过反对了!')
                            };
                            setTimeout(function () {
                                 $(".diggword").html('')
                            }, 1000)

                        }
                    }
                })
            })
        </script>

 五七. 博客系统之文章点赞数的Ajax更新

57.1 模板中如何更新点赞数,直接在前端完成,自加1, 无需后台传送数据.

        <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                $.ajax({
                    url: '/digg/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),
                        'article_id': {{ article_obj.pk }},
                        'is_up': is_up,
                    },
                    success: function (data) {
                        if (data.state) {
                            {#  state为True表示之前未点过  #}
                            {#  is_up为True表示是点赞, False表示反对  #}
                            if (is_up) {
                                var val = parseInt($("#digg_count").text());
                                $("#digg_count").text(val + 1);
                                $(".diggword").html('点赞成功!')
                            } else {
                                var val = parseInt($("#bury_count").text());
                                $("#bury_count").text(val + 1);
                                $(".diggword").html('反对成功!')
                            }
                        } else {
                            {# 之前已经点过赞或者点过反对了 #}
                            if (data.handled) {
                                $(".diggword").html('您已经点过赞了!')
                            } else {
                                $(".diggword").html('您已经点过反对了!')
                            }
                            ;
                            setTimeout(function () {
                                $(".diggword").html('')
                            }, 1000)

                        }
                    }
                })
            })
        </script>

 五八. 博客系统之文章点赞代码优化

 

  <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                var $count_obj = $(this).children('span');  // 获取用于显示点赞数\或者是反对数的span标签对象
                $.ajax({
                    url: '/digg/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),
                        'article_id': {{ article_obj.pk }},
                        'is_up': is_up,
                    },
                    success: function (data) {
                        if (data.state) {
                            {#  state为True表示之前未点过  #}
                            var val = parseInt($count_obj.text());  // 获取点赞(反对)的数值
                            $count_obj.text(val+1);  // 点赞(反对)数值加1
                        } else {
                            {# 三元运算符, True时,val=冒号前的内容, False时val=冒号后的内容 #}
                            var val = data.handled?'您已经点过赞了!':"您已经点过反对了!"
                            $(".diggword").html(val);
                            setTimeout(function () {
                                $(".diggword").html('')
                            }, 1000)

                        }
                    }
                })
            })
        </script>
{# 继承 #}
{% extends 'base.html' %}

{% block content %}
    <div class="article_info">
        {% csrf_token %}
        <h3 class="text-center">{{ article_obj.title }}</h3>
        <div class="cont">
            {{ article_obj.content|safe }}
        </div>
        <div id="div_digg">
            {#    diggit action   推荐       #}
            <div class="diggit action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span>
            </div>
            {#    diggit action   反对       #}
            <div class="buryit action">
                <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red;"></div>
        </div>
        <script>
            // 注意 class属性是.action的有两个, 那么到底是点了哪一个,
            $('.action').click(function () {
                var is_up = $(this).hasClass('diggit');  // 有diggit属性,就是True, 否则就是False
                var $count_obj = $(this).children('span');  // 获取用于显示点赞数\或者是反对数的span标签对象
                $.ajax({
                    url: '/digg/',
                    type: 'post',
                    data: {
                        'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(),
                        'article_id': {{ article_obj.pk }},
                        'is_up': is_up,
                    },
                    success: function (data) {
                        if (data.state) {
                            {#  state为True表示之前未点过  #}
                            var val = parseInt($count_obj.text());  // 获取点赞(反对)的数值
                            $count_obj.text(val+1);  // 点赞(反对)数值加1
                        } else {
                            {# 三元运算符, True时,val=冒号前的内容, False时val=冒号后的内容 #}
                            var val = data.handled?'您已经点过赞了!':"您已经点过反对了!"
                            $(".diggword").html(val);
                            setTimeout(function () {
                                $(".diggword").html('')
                            }, 1000)

                        }
                    }
                })
            })
        </script>
    </div>
{% endblock %}
article_detail.html
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse  # 传递json数据类型
from django.contrib import auth  # 导入用户组件,用于auth_user表的操作
from django.db.models import Avg, Max, Min, Count
from django.db.models.functions import TruncMonth
from blog import models

from blog.models import UserInfo
from blog.myforms import RegForm  # 导入自定义forms验证规则


def reg(request):
    """用户注册视图"""
    if request.is_ajax():
        form = RegForm(request.POST)
        respone = {'user': None, 'msg': None}
        if form.is_valid():
            respone['user'] = form.cleaned_data.get('user')
            user = form.cleaned_data.get('user')
            pwd = form.cleaned_data.get('pwd')
            email = form.cleaned_data.get('email')
            avatar_obj = request.FILES.get('avatar')
            extra = {}
            if avatar_obj:
                extra['avatar'] = avatar_obj
            user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra)
        else:
            respone['msg'] = form.errors
        return JsonResponse(respone)

    form = RegForm()  # 带入规则
    return render(request, 'reg.html', {"form": form})


def login(request):
    if request.method == "POST":
        response = {'user': None, 'msg': None}  # 构造登录提示信息
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        valid_code = request.POST.get('valid_code')  # 获取用户输入的验证码
        valid_code_str = request.session.get('valid_code')  # 从session中提取自动生成的验证码字符串
        if valid_code_str.upper() == valid_code.upper():  # 全部转为大写字母
            user = auth.authenticate(username=user, password=pwd)
            if user:
                auth.login(request, user)  # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量
                response['user'] = user.username  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.
            else:
                response['msg'] = '用户名或密码错误!'
        else:
            response['msg'] = '验证码错误!'
        return JsonResponse(response)  # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转.

    return render(request, 'login.html')


def get_validCode_img(request):
    """生成验证码图片"""
    from blog.utils.validCode import get_validCode_img  # 导入自定义的生成验证码功能函数
    data = get_validCode_img(request)
    return HttpResponse(data)


def index(request):
    article_list = models.Article.objects.all()
    return render(request, 'index.html', {'article_list': article_list})


def logout(request):
    auth.logout(request)
    return redirect('/login/')


def home_site(request, username, **kwargs):
    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')

    # 查询username用户的blog对象
    blog = user.blog
    article_list = models.Article.objects.filter(user=user)
    if kwargs:
        # 个人站点跳转
        condition = kwargs.get('condition')  # 使用**kwargs目的是可以接受任意多个参数
        param = kwargs.get('param')
        if condition == 'tag':
            article_list = article_list.filter(tags__title=param)
            # print(article_list)
        elif condition == 'category':
            article_list = article_list.filter(category__title=param)
        else:
            # 日期归档
            year, month = param.split('-')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

    return render(request, 'home_site.html', {'username': username, 'blog': blog, 'article_list': article_list})


def article_detail(request, username, article_id):
    """
    文章详情页视图
    http://127.0.0.1:8000/papa/article/2/
    """

    user = UserInfo.objects.filter(username=username).first()
    if not user:
        return render(request, 'not_fount.html')
    # 查询username用户的blog对象
    blog = user.blog
    article_obj = models.Article.objects.filter(pk=article_id).first()
    return render(request, 'article_detail.html', locals())  # locals()不能丢


def digg(request):
    print(request.user)
    import json
    article_id = request.POST.get('article_id')
    is_up = json.loads(request.POST.get('is_up'))  # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值
    user_id = request.user.pk  # 直接获取user_id,没有必要通过客户端传送

    # 判断是否已经点过赞
    response = {'state': True}  # 返回的状态,将传递给模板中的回调函数,默认是True成功, 表示为点过赞或者反对.
    ret_obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first()
    if not ret_obj:  # 没点过赞
        ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
        # 将点赞写入数据库中
        from django.db.models import F
        article_obj = models.Article.objects.filter(pk=article_id)
        if is_up:
            article_obj.update(up_count=F("up_count")+1)
        else:
            article_obj.update(down_count=F("down_count")+1)
    else:  # 已经点过赞或反对了
        response['state'] = False  # 表示失败
        response['handled'] = ret_obj.is_up  # 获取当前数据库中是点赞过还是点反对过.

    return JsonResponse(response)  # 将状态传递给模板中的回调函数
views.py

 

posted @ 2021-02-27 22:08  蓝蓝的白云天!  阅读(267)  评论(0编辑  收藏  举报