13. Django 博客项目02 站点主页&个人主页

7. 主页

7.1 路由层
    # 4. 主页
    url(r'^home/', views.home),
7.2 视图层
# 5. 主页
def home(request):
    # 5.1 返回主页面
    return render(request, 'home.html')
7.3 主页导航条
主页站点东西太多先一步步的完成
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
    <!--0. 动态获取静态文件路径 -->
    {% load static %}
    <!--1. 导入 jQuery js 文件-->
    <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
    <!--2. 导入 bootstrap css 文件-->
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    <!--3. 导入 bootstrap js 文件-->
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div>
    <!--4. 导入 导航条-->
    <nav class="navbar navbar-inverse">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                        data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">BBS</a>
            </div>

            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
                    <li><a href="#">文章</a></li>
                    <li class="dropdown">
                        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                           aria-expanded="false">更多 <span class="caret"></span></a>
                        <ul class="dropdown-menu">
                            <li><a href="#">Action</a></li>
                            <li><a href="#">Another action</a></li>
                            <li><a href="#">Something else here</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="#">Separated link</a></li>
                            <li role="separator" class="divider"></li>
                            <li><a href="#">One more separated link</a></li>
                        </ul>
                    </li>
                </ul>
                <form class="navbar-form navbar-left">
                    <div class="form-group">
                        <input type="text" class="form-control" placeholder="Search">
                    </div>
                    <button type="submit" class="btn btn-default">Submit</button>
                </form>
                <ul class="nav navbar-nav navbar-right">
                    <!-- 5. 判断是有用户登入 is_authenticated会自动加括号调用函数 CallableBool(True)/ CallableBool(False)-->
                    {% if request.user.is_authenticated %}
                        <li><a href="#">{{ request.user }}</a></li>
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                               aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <li><a href="#">修改密码</a></li>
                                <li><a href="#">修改头像</a></li>
                                <li><a href="#">后台管理</a></li>
                                <li role="separator" class="divider"></li>
                                <li><a href="#">退出登入</a></li>
                            </ul>
                        </li>
                    {% else %}
                        <li><a href="{% url 'register' %}">注册</a></li>
                        <li><a href="{% url 'login' %}">登入</a></li>
                    {% endif %}
                </ul>
            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
</div>
</body>
</html>

8. 导航条右侧菜单

在用户登入的时候能显示 用户名  更多

image-20220327180850758

用户登入之后, 使用auth模块记录用户登入状态,
auth.login(request, 用户对象) 就是相当于一次写session的操作.

用户登入的数据就记录在request中,通过判断request中的数据来做判断展示不同的菜单.
.user 用户对象数据, 默认展示用户名
.user.is_authenticated 是否用用户登入 返回 CallableBool(True) / CallableBool(False)
{% if request.user.is_authenticated %}
	用户名 更多
{% else %}
	注册  登入
{% endif %}
<ul class="nav navbar-nav navbar-right">
    <!-- 6.4 判断是有用户登入 is_authenticated会自动加括号调用函数 CallableBool(True)/ CallableBool(False)-->
    {% if request.user.is_authenticated %}
    <!-- 6.5 导航条链接4 显示用户名称 -->
    <li><a href="#">{{ request.user.username }}</a></li>
    <li class="dropdown">
        <!-- 6.6 导航条链接5 -->
        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
           aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
        <ul class="dropdown-menu">
            <!-- 6.7 修改密码链接 -->
            <li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
            <!-- 6.8 修改头像链接 -->
            <li><a href="#">修改头像</a></li>
            <!-- 6.9 后台管理链接 -->
            <li><a href="#">后台管理</a></li>
            <li role="separator" class="divider"></li>
            <!-- 6.10 退出登入链接 -->
            <li><a href="/logout/">退出登入</a></li>
        </ul>
    </li>
    {% else %}
    <!-- 6.11 注册链接 -->
    <li><a href="/register/">注册</a></li>
    <!-- 6.10 登入链接 -->
    <li><a href="/login/">登入</a></li>
    {% endif %}
</ul>

image-20220327181704398

* 清除session后刷新网页
没有用户登入的时候显示 注册  登录 

image-20220327181030471

9. 修改密码

点击 导航条右侧菜单 更多中的修改密码弹出一个模态框.
采用模态框设计一个表单来修改密码.
9.1 模态框

image-20220327191956720

改为a标签触发模态框, 
将button的 data-toggle='models' data-terget='.bs-example-modal-lg' 复制到a标签中, 

模态框div标签class中添加 .bs-example-modal-lg 属性, 不加a标签就无法触发模态框.
<!-- 触发模态框 -->
<li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<!-- 模态框代码 -->
<div class="modal fade .bs-example-modal-lg " tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel">
  <div class="modal-dialog modal-sm" role="document">
    <div class="modal-content">
      ...
    </div>
  </div>
</div>
点击修改密码的后触发模态框.

image-20220327192606847

8.2 模态框表单
模板框设置 data-backdrop="static" data-keyboard="false"
不能按esc退出模块框, 点模态框空白处不收起模态框

模态框设置两个按键 data-dismiss="modal" 关闭模态框
class="modal-footer" 模态框页尾 

模态框只能通过关闭按钮收起.
<!--7.修改密码模态框-->
<div class="modal fade bs-example-modal-lg"  data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
    <div class="modal-dialog modal-lg" role="document">
        <!--模态框内容-->
        <div class="modal-content" id="id_model">
            <h1 class="text-center" style="color: #030303">修改密码</h1>
            <div class="row">
                <div class="col-md-8 col-md-offset-2">
                    <!--7.1表单-->
                    <form action="" id="model_form">
                        <!--7.2 csrf校验-->
                        {% csrf_token %}
                        <div class="form-group">
                            <label for="id_username">名称</label>
                            <input type="text" name="username" id="id_username" value="{{ request.user.username }}"
                                   disabled class="form-control">
                        </div>

                        <div class="form-group">
                            <label for="id_password">旧密码</label>
                            <input type="password" name="old_password" id="id_password" class="form-control">
                        </div>

                        <div class="form-group">
                            <label for="new_password">新密码</label>
                            <input type="password" name="new_password" id="new_password" class="form-control">
                        </div>

                        <div class="form-group">
                            <label for="confirm_password">确认密码</label>
                            <input type="password" name="confirm_password" id="confirm_password"
                                   class="form-control">
                        </div>


                        <!--7.3 按钮-->
                        <div class="modal-footer">
                            <div class="col-md-8">
                                <button type="button" class="btn-primary btn-block" id="btn_model" style="opacity: 0.5">修改</button>
                            </div>
                            <div class="col-md-4">
                                <button type="button" class="btn-danger btn-block" data-dismiss="modal" style="opacity: 0.5">关闭</button>
                            </div>
                        </div>

                    </form>
                </div>
            </div>

        </div>
    </div>
</div>
9.3 模态框&表单样式
/* 后代选择器 选中模态框中的input框 无边款并且透明 */
#id_model input {
    border: none;
    background-color: transparent;
    color: #364046;
}

/* 模态框背景 */
#id_model {
    background-image: url({% static 'img/set_password.jpg' %});
    /*  不缩放  */
    background-size: cover;
    /*  不平铺  */
    background-repeat: no-repeat;
    /* 字体颜色 */
    color: #364046;
    font-size: 22px;
    /* 相对定位 */
    position: relative;
    top: 100px;
}


/* 二次弹框样式 */
.sweetAlert {
    width: 22em;
    color: #6b1583;
    background-color: transparent;
    /* 相对定位 */
    position: relative;
    top: 300px;
    left: 250px;
}

GIF 2022-3-28 8-29-00

9.4 ajax提交表单数据
<script>
    // 8. 为模态框绑定点击事件
    $('#btn_model').on('click', function () {
        // 8.1 获取表单的数据 --> [{}, ] 数组套自定义对象
        let ModelForm = $('#model_form').serializeArray()

        // 8.2 定义一个自定义对象
        let Model_obj = new Object()

        // 8.3 each 遍历数组
        $.each(ModelForm, function (index, obj) {
            // 8.4 为自定义对象创建属性
            Model_obj[obj.name] = obj.value
        })

        // console.log(Model_obj)

        // 8.5 ajax提交数据
        $.ajax({
            // 8.6 提交地址
            url: '/set_password/',
            // 8.7 提交方式
            type: 'post',
            // 8.8 提交的数据
            data: Model_obj,
            // 8.9 回调函数
            success: function (args) {
                // 8.9.1 判断响应状态码
                if (args.code === 200) {
                    // 8.9.2 修改成功跳转网页
                    window.location.href = args.url
                } else {
                    // 8.9.3 展示错误新
                    swal({
                        title: args.error_msg,
                        type: 'error',
                        customClass: 'sweetAlert',
                        timer: 2000,
                        // 不需要 手动点击 ok
                        showConfirmButton: false,
                    })
                    // 8.9.4 添加属性, 禁止模态框下面的页面操作
                    $('body').css("pointer-events", "none")
                    // 8.9.5 3秒后解除禁止模态框下面的页面操作
                    setTimeout(function () {
                        $('body').css("pointer-events", "")
                    }, 3000);
                }

            }
        })
    })
</script>

GIF 2022-3-28 7-01-22

9.5 设置密码路由层
表单数据向set_password提交.
    # 5. 设置密码
    url(r'^set_password', views.set_password),
9.6 修改密码视图函数
先对新密码的两次输入进行校验, 如果不一致返回错误提示信息.

再操作数据库对旧密码进行比对, 如果真正把新输入的密码加密写入数据库, 清除session中的数据跳转到登入页面.
旧密码错误放回错误信息.
# 6. 修改密码
# 6.0 导入用户登入装饰器
@login_required
def set_password(request):
    # 6.1 判断请求方式
    if request.is_ajax():
        # 6.2 获取用户提交的数据
        post_obj = request.POST
        # print(post_obj)

        # 6.3 判断 新密码 确认密码 是否一致
        new_password = post_obj.get('new_password')
        confirm_password = post_obj.get('confirm_password')
        print(new_password, confirm_password)
        if new_password == confirm_password:
            # 6.4 获取用户名和旧密码
            old_password = post_obj.get('old_password')
            # 6.5 校验用户输入的密码是否正确 --> 返回布尔值
            is_right = request.user.check_password(old_password)
            # 6.6 判断检验结果
            if is_right:
                # 6.7 修改密码
                request.user.set_password(new_password)
                # 6.8 将数据保存
                request.user.save()
                # 6.9 清除session数据
                request.session.flush()

                # 6.10 组织成功修改密码之后返回的信息  跳转到登入页面
                back_dict = {'code': 200, 'url': '/login/'}
            else:
                # 6.11 组织用户密码错误放回的信息
                back_dict = {'code': 400, 'error_msg': '旧密码输入错误!'}

        else:
            # 6.12 组织新密码两个输入不一致 返回的错误信息
            back_dict = {'code': 400, 'error_msg': '确认密码不一致'}

        print(back_dict)
        return JsonResponse(back_dict)

    else:
        return HttpResponse('非法访问!')
错误提示使用二次弹框展示.

GIF 2022-3-28 8-39-20

正常修改密码后之间跳转到登入页面

10. 退出登入

点击之后清除session中的数据, 跳转到登入界面.
10.1 路由层
    # 6. 退出登入
    url(r'^logout/', views.logout)
10.2 视图层
# 7. 退出登入(加上登入装饰器)
@login_required
def logout(request):
    # 7.1 清除登入状态
    auth.logout(request)
    # 7.2 跳转到登入界面
    return redirect('/login/')

11. 后台管理

admin后台中 Django提供一个可视化界面操作模型表, 可以进行数据的增删改查操作, 
需要在admin.py中先注册需要操作的表.
11.1 创建超级用户
* 创建一个超级用户.
PS F:\synchro\Project\BBS> python3.6 manage.py createsuperuser
Username: admin  
Email address: admin@qq.com
Password: root1111  # 不显示的
Password (again): root1111  # 不显示的
Superuser created successfully.
11.2 登入后台管理
登入后台: http://127.0.0.1:8000/admin/

image-20220328091624282

登入后展示的页面

image-20220328091505544

11.3 注册模型表
后台管理模型表需要在admin.py 中注册.

admin.site.register()
from django.contrib import admin

# 0. 导入模型层
from app01 import models
# Register your models here.

# 1. 注册用户表
admin.site.register(models.UserInfo)
刷新后台管理的页面, 展示注册的表, 表名后面默认都是加s的.
(表的名称有点小差异但是数据是对的)

可视化操作界面

image-20220328093507997

点击User表, 查看表中的数据.

image-20220328093744319

在映射表的类中添加 自定义admin中展示的表名 代码
# 0.7 自定义admin中展示的表名  Meta元类
class Meta:
    # verbose_name = '用户表'  # 用户表 加s
    verbose_name_plural = '用户表'  # 用户表 不加s

image-20220328095432275

注册所有的表
from django.contrib import admin

# 0. 导入模型层
from app01 import models

# Register your models here.

# 1. 注册用户表
admin.site.register(models.UserInfo)

# 2. 注册博客表(站点表)
admin.site.register(models.Blog)

# 3. 注册文章表
admin.site.register(models.Article)

# 4. 注册文章表与标签表的 第三张表
admin.site.register(models.ArticleToTag)

# 5. 注册标签表
admin.site.register(models.Tag)

# 6. 注册分类表
admin.site.register(models.Sort)

# 7. 注册点赞点踩表
admin.site.register(models.UpAndDown)

# 8. 注册评论表
admin.site.register(models.Comment)
为所有的表设置自定义名称
# 0.7 自定义admin中展示的表名  Meta元类
class Meta:
    verbose_name_plural = 'UserInfo(用户表)'  # 用户表 不加s

# 1.5 自定义admin中展示的表名
class Meta:
    verbose_name_plural = 'Blog(个人站点表)'
    
# 7.3 自定义admin中展示的表名
class Meta:
	verbose_name_plural = 'ArticleToTag(文章表多对多标签表的第三张表)'
    
# 3.4 自定义admin中展示的表名
class Meta:
	verbose_name_plural = 'Tag(标签表)'
    
# 4.4 自定义admin中展示的表名
class Meta:
  	verbose_name_plural = 'Sort(分类表)'
    
# 5.5 自定义admin中展示的表名
class Meta:
  	verbose_name_plural = 'UpAndDown(点赞点踩表)'
    
# 6.7 自定义admin中展示的表名
class Meta:
   	verbose_name_plural = 'Comment(评论表)'

image-20220328101602505

11.4 admin后台创建url
admin 会给注册的模型表自动增加四条url
urls.py 中的第一条路由 url(r'^admin/', admin.site.urls) 中创建的.
                             引用名/表名
http://127.0.0.1:8000/admin/app01/article/             
http://127.0.0.1:8000/admin/app01/article/add/         
http://127.0.0.1:8000/admin/app01/article/主键/set/    
http://127.0.0.1:8000/admin/app01/article/主键/delete/ 
11.5 添加数据
1. 文章表添加数据
点击文章表 --> 点击添加文章按键

image-20220328101833283

在表中可以直接编辑数据.
     点击外键字段可以跳转到外键对应的表中去.

image-20220328104007627

先点击关联博客表的外键 --> 添加站点

两个站点:
名称  站点签名       站点样式
kid   努力学习        kid.css
qq    qq的个人博客    qq.css

image-20220328104657171

选择站点 --> 展示的是id对应的数据对象.

image-20220328104825062

在映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回站点名称.
# 1.6 打印query_set对 返回站点名称
def __str__(self):
    return self.site_name

image-20220328105206481

在创建一个 个人站点的数据

创建分类 --> 分类的外键绑定 个人站点
创建四个分类:
分类名称         绑定的站点
kid的第一个分类    kid
kid的第二个分类    kid

qq的第一个分类     qq
qq的第二个分类     qq

image-20220328105730874

展示的也外键id对应的数据对象

image-20220328110233412

映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回分类的类名.
    # 4.5 在打印query_set对象的时候 返回分类的类名
    def __str__(self):
        return self.name

image-20220328110602394

添加文章 --> 绑定站点 设置分类
kid 站点第一个分类的文章

image-20220328111251746

kid 站点第二个分类的文章

image-20220328111543758

qq 站点第一个分类的文章

image-20220328111816220

qq 站点第二个分类的文章

image-20220328111939522

添加了四片文章, 打印的数据外键绑定的主键对应的数据对象

在这里插入图片描述

映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回文章的标题.
    # 2.13 在打印query_set对象的时候 返回文章的标题.
    def __str__(self):
        return self.title

image-20220328112333238

2. 用表添加数据

image-20220328113353950

用户表绑定站点

image-20220328113453497

在点保存的时候会报错

image-20220328113509899

image-20220328113904673

手机号码字段中:
null=True 设置的是 数据库该字段可以为空
在admin后台中操作就不允许为空了, 需要添加
blank=True 设置的是 admin后台中操作可以为空
    # 0.3 手机号码  在写入数据库的时候运行不写
    phone = models.BigIntegerField(null=True, blank=True, verbose_name='手机号码')
设置blank=True, 不需要执行数据库迁移命令
qqq账户绑定qq站点

image-20220328121014193

3. 标签表添加数据
标签表绑定站点

image-20220328114323916

kid站点第一个标签绑定 kid站点

image-20220328114359210

kid站点第二个标签绑定 kid站点

image-20220328114756274

qq站点第一个标签绑定 qq站点

image-20220328114843338

qq站点第二个标签绑定 qq站点

image-20220328115000623g)]

展示的是数据对象

image-20220328115033358

映射类中添加 __str__ 方法. 在打印query_set对象的时候, 标签的名称.
    # 3.5 在打印query_set对象的时候展示 标签的名称
    def __str__(self):
        return self.name

image-20220328115257528

4. 第三张表添加数据
文章与标签表多对多的第三张表添加数据

image-20220328115809155

多对多的关系, 一篇文章可以绑定多个标签. (这里就不绑定了)
kid发布的第一篇文章 Python基础 绑定 kid站点第一个标签

image-20220328120221523

kid发布的第二篇文章 Typora使用 绑定 kid站点第二个标签

image-20220328120001653

qq发布的第一篇文章 MySQL介绍 绑定 qq站点第一个标签

image-20220328120443529

qq发布的第二篇文章 web开发介绍 绑定 qq站点第二个标签

展示的是query_set 对象
第三张表中展示的 文章的id  标签的id 没有可展示名称.

image-20220328120557306

映射类中添加 __str__ 方法. 对象.外键 子查询, 查到是外键对应的query_set对象
打印该对象的时候又触发该类中设置的__str__ 方法.
    # 7.4 打印query_set self.外键 
    def __str__(self):
        return f'{self.article} -- {self.tag}'

image-20220328121643097

12. 上传文件路径

12.1 配置路径
网站所使用的静态文件都默认存放在static文件中, 然后在配置文件中开发路径.
用户上传的静态文件一般存放在media文件夹中.
在settings.py 中配置 MEDIA_ROOT = os.path(BASE_DIR, '目录')
改配置可以让用户上传的所有文件都固定到某一个指定的文件夹中, 
名字可以自定义, 一般使用 media.

该目录在用户上传文件的时候在项目目录下自动创建.
# settings.py 
# 5. 配置用户上传文件存放位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

在这里插入图片描述

头像字段中设置了存头像的路径, 该路径会在media目录中创建.
将avatar中的头像文件之间复制到media下的avatar中, 将原来的avatar目录删除.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7KuGGpT-1648624153233)(C:\Users\13600\AppData\Roaming\Typora\typora-user-images\image-20220328222750869.png)]

12.2 开发静态文件访问
在网页地址栏中直接输入url访问对应的静态文件.
# 在urls.py 中导入配置文件
# 7.导入serve 模块
from django.views.static import serve
# 8. 导入上传文件的配置
from BBS import settings

    # 9. 上传文件的路由匹配 结尾不能加/
    url(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
直接访问 http://127.0.0.1:8000/media/avatar/%E5%A4%B4%E5%83%8F1.png 

image-20220328224758608

12.3 开放源码
# 6. 配置用户上传文件存放位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'app01')
# 7.导入serve 模块
from django.views.static import serve
# 8. 导入上传文件的配置
from BBS import settings

url(r'^app01/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
访问源代码, 这是一个危险的操作.

image-20220328230023082

13. 主页布局

2 8 2 布局
在左右两边菜单栏添加 六个标题表单.
<div class="container-fluid">
    <!--8.页面布局 2 8 2-->
    <div class="row">
        <!--8.1 右侧菜单栏-->
        <div class="col-md-2">

            <div class="panel panel-primary">
                <div class="panel-heading">title1</div>
                <div class="panel-body">
                    内容1
                </div>
            </div>

            <div class="panel panel-info">
                <div class="panel-heading">title2</div>
                <div class="panel-body">
                    内容2
                </div>
            </div>

            <div class="panel panel-default">
                <div class="panel-heading">title3</div>
                <div class="panel-body">
                    内容3
                </div>
            </div>

        </div>
        <!--8.2 内容 -->
        <div class="col-md-8"></div>

        <!--8.3 左侧菜单栏-->
        <div class="col-md-2">

            <div class="panel panel-primary">
                <div class="panel-heading">title1</div>
                <div class="panel-body">
                    内容1
                </div>
            </div>

            <div class="panel panel-info">
                <div class="panel-heading">title2</div>
                <div class="panel-body">
                    内容2
                </div>
            </div>

            <div class="panel panel-default">
                <div class="panel-heading">title3</div>
                <div class="panel-body">
                    内容3
                </div>
            </div>

        </div>
    </div>
</div>

image-20220328085204927

14. 展示所有文章

在视图函数中获取数据中的文章表.
前端媒体对象列表展示文章.
文章过多需要制作分页器.
14.1 分页器模板
在项目应用下创建utils文件夹.
在utils目录下创建pages.py文件
将分页器模板复制到pages.py文件中
class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
        """
        封装分页相关数据
        :param current_page: 当前页
        :param all_count:    数据库中的数据总条数
        :param per_page_num: 每页显示的数据条数
        :param pager_count:  最多显示的页码个数
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1

        if current_page < 1:
            current_page = 1

        self.current_page = current_page

        self.all_count = all_count
        self.per_page_num = per_page_num

        # 总页码
        all_pager, tmp = divmod(all_count, per_page_num)
        if tmp:
            all_pager += 1
        self.all_pager = all_pager

        self.pager_count = pager_count
        self.pager_count_half = int((pager_count - 1) / 2)

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page_num

    @property
    def end(self):
        return self.current_page * self.per_page_num

    def page_html(self):
        # 如果总页码 < 11个:
        if self.all_pager <= self.pager_count:
            pager_start = 1
            pager_end = self.all_pager + 1
        # 总页码  > 11
        else:
            # 当前页如果<=页面上最多显示11/2个页码
            if self.current_page <= self.pager_count_half:
                pager_start = 1
                pager_end = self.pager_count + 1

            # 当前页大于5
            else:
                # 页码翻到最后
                if (self.current_page + self.pager_count_half) > self.all_pager:
                    pager_end = self.all_pager + 1
                    pager_start = self.all_pager - self.pager_count + 1
                else:
                    pager_start = self.current_page - self.pager_count_half
                    pager_end = self.current_page + self.pager_count_half + 1

        page_html_list = []
        # 添加前面的nav和ul标签
        page_html_list.append('''
                    <nav aria-label='Page navigation>'
                    <ul class='pagination'>
                ''')
        first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
        else:
            prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)

        page_html_list.append(prev_page)

        for i in range(pager_start, pager_end):
            if i == self.current_page:
                temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
            else:
                temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
            page_html_list.append(temp)

        if self.current_page >= self.all_pager:
            next_page = '<li class="disabled"><a href="#">下一页</a></li>'
        else:
            next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
        page_html_list.append(next_page)

        last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
        page_html_list.append(last_page)
        # 尾部添加标签
        page_html_list.append('''
                                           </nav>
                                           </ul>
                                       ''')
        return ''.join(page_html_list)
14.2 分页获取文章
获取所有文章数据后 使用分页器进行分页处理.
# 5. 主页
def home(request):
    # 5.1 导入分页器模板模块
    from app01.utils.pages import Pagination
    # 5.2 获取所有数据
    all_queryset_obj = models.Article.objects.all()
    # 5.3 计算总数
    all_queryset_sum = all_queryset_obj.count()

    # 5.4 获取当前的页码  获取的是字符串
    page_num = request.GET.get('page', 1)

    # 5.5 实例化对象
    page_obj = Pagination(
        current_page=page_num,  # 自动转换类型
        all_count=all_queryset_sum,
        per_page_num=2,
        pager_count=10,

    )
    """ 
    current_page: 当前页               必须的 自动转换类型
    all_count:    数据库中的数据总条数  必须的 
    per_page_num: 每页显示的数据条数    默认10
    pager_count:  最多显示的页码个数    默认10
    """
    # 5.6 切片操作
    page_queryset = all_queryset_obj[page_obj.start: page_obj.end]
    # 5.7 返回主页面
    return render(request, 'home.html', locals())
14.3 媒体对象展示文章

image-20220330113904283

<!--8.2 文章内容  使用媒体对象展示-->
<div class="col-md-8">

    <!--分页器分页之后的数据 一个个取出来-->
    {% for queryset_obj in page_queryset %}

    <!--媒体对象列表 展示文章-->
    <div class="media">
        
        <h4 class="media-heading">
            <a href="#">
                {{ queryset_obj.title }}</h4>
            </a>
        <div class="media-left media-middle">
            <a href="#">
                <img class="media-object" src="{% static 'img/default.png' %}" alt="..." width="80">
            </a>
        </div>
        <div class="media-body">
            {{ queryset_obj.desc }}
        </div>
        <br>
        <div>      <!--博客表 通过外键子查询拿到站点名称-->
            <span><a href="/{{ queryset_obj.blog.site_name}}/">{{ queryset_obj.blog.userinfo.username }}&nbsp;</a></span>
            <!--文章发布时间-->
            <span>发布于 {{ queryset_obj.create_time|date:'Y-m-d' }}&nbsp;</span>
            <span class="glyphicon glyphicon-comment">评论({{ queryset_obj.comment_num }})&nbsp;</span>
            <span class="glyphicon glyphicon-thumbs-up">点赞({{ queryset_obj.up_num }})&nbsp;</span>
            <span class="glyphicon glyphicon-thumbs-down">点踩({{ queryset_obj.down_num }})&nbsp;</span>

        </div>
        <hr>
    </div>
    <nav aria-label="Page navigation"></nav>
    {% endfor %}
    {# 分页器 #}
    {{ page_obj.page_html|safe }}
</div>

GIF 2022-3-30 11-36-48

15. 个人站点

15.1 路由层
以站点的名称访问对应的站点.
    # 11. 个人站点匹配路由 \w+ 配 数字大小字母字符
    url(r'(?P<site_name>\w+)', views.get_site, name='site_name')
15.2 视图层
# 8. 获取个人站点   接收有名分组匹配的数据
@login_required
def get_site(request, site_name):
    # 8.1 判断访问的站点是否存在
    blog_obj = models.Blog.objects.filter(site_name=site_name)
    # 8.2 用户存在 获取该用户的所有文章封装的 query_set对象
    if blog_obj:
        # 8.3 用户存在 
        article_query_set = models.Article.objects.filter(blog=blog_obj).all()
        # print(article_query_set)
        # 8.4 返回用户的所有文章
        return render(request, 'site.html', locals())
    else:
        # 8.5 个人站点不存在返回 404 页面
        return render(request, 'error.html')
15.3 404页面
当访问的站点不存在返回404页面, 复制博客园的404页面代码.

image-20220329002131146

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <link rel="icon" href="//common.cnblogs.com/favicon.ico" type="image/x-icon" />
    <title>400_无效的请求 - BBS博客</title>
    <style type='text/css'>
        body {
            margin: 8% auto 0;
            max-width: 550px;
            min-height: 200px;
            padding: 10px;
            font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
            font-size: 14px;
        }

        p {
            color: #555;
            margin: 10px 10px;
        }

        img {
            border: 0px;
        }

        .d {
            color: #404040;
        }
    </style>
</head>
<body>
        <a href='https://www.cnblogs.com/'><img src='//common.cnblogs.com/images/logo_small.gif' alt='' /></a>
    <p style='color:red'>400 Bad Request: 无效的请求。抱歉,给您带来麻烦了!</p>
    <p class='d'>麻烦您发邮件至 13600@qq.com向我们反馈。</p>
    <p><a href='/home/'>返回网站首页</a></p>
</body>
</html>
15.4 图片防盗技术知识补充
服务器对外开发了访问的路径, 可能存在盗用的情况, 别人开发一个项目使用你的服务器数据,
为了避免别的网站直接通过本网站的url访问资源, 服务器做了简单的防盗设置.
在后台中查看访问服务的信息:
请求头中有Referer专门记录请求来自于那个网站.

解决方法:
将对方的资源下载到自己的服务器
修改Referer的参数
通过博客园的访问服务器的资源正常显示

image-20220329004517289

在浏览器中输入: http://127.0.0.1:8000/%E5%93%88%E5%93%88
盗用图片资源被拒绝访问.

image-20220329004224845

将资源下载到本地

image-20220329005724718

<a href='/home/'><img src='/static/img/logo_small.jpg' alt='' /></a>

20220329010603

15.5 导航条
个人站点的导航条
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>个人站点</title>
        <!--0. 动态获取静态文件路径 -->
        {% load static %}
        <!--1. 导入 jQuery js 文件-->
        <script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
        <!--2. 导入 bootstrap css 文件-->
        <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
        <!--3. 导入 bootstrap js 文件-->
        <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
        <!--4. 导入 sweetalert css 文件-->
        <link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">
        <!--5. 导入 sweetalert js 文件-->
        <script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.min.js' %}"></script>
        <!-- 导入个人样式 -->
        <link rel="stylesheet" href="/media/css/{{ blog_obj.site_theme }}/">
    </head>
    <body>
        <!--6. 导入 导航条-->
        <nav class="navbar navbar-inverse">
            <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>
                    <!--6.1 图标 展示当前用户-->
                    <a class="navbar-brand" href="#">{{ request.user.username }}</a>
                </div>

                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav">
                        <!--6.2 导航条链接1 -->
                        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
                        <!--6.2 导航条链接2 -->
                        <li><a href="#">文章</a></li>
                        <li class="dropdown">
                            <!--6.3 导航条链接3 -->
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                               aria-expanded="false">更多 <span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <li><a href="#">Action</a></li>
                                <li><a href="#">Another action</a></li>
                                <li><a href="#">Something else here</a></li>
                                <li role="separator" class="divider"></li>
                                <li><a href="#">Separated link</a></li>
                                <li role="separator" class="divider"></li>
                                <li><a href="#">One more separated link</a></li>
                            </ul>
                        </li>
                    </ul>
                    <form class="navbar-form navbar-left">
                        <div class="form-group">
                            <input type="text" class="form-control" placeholder="Search">
                        </div>
                        <button type="submit" class="btn btn-default">Submit</button>
                    </form>

                    <ul class="nav navbar-nav navbar-right">
                        <!-- 6.4 判断是有用户登入 is_authenticated会自动加括号调用函数 CallableBool(True)/ CallableBool(False)-->
                        {% if request.user.is_authenticated %}
                        <!-- 6.5 导航条链接4 显示用户名称 -->
                        <li><a href="#">{{ request.user.username }}</a></li>
                        <li class="dropdown">
                            <!-- 6.6 导航条链接5 -->
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                               aria-haspopup="true" aria-expanded="false">更多 <span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <!-- 6.7 修改密码链接 -->
                                <li><a href="" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
                                <!-- 6.8 修改头像链接 -->
                                <li><a href="#">修改头像</a></li>
                                <!-- 6.9 后台管理链接 -->
                                <li><a href="#">后台管理</a></li>
                                <li role="separator" class="divider"></li>
                                <!-- 6.10 退出登入链接 -->
                                <li><a href="/logout/">退出登入</a></li>
                            </ul>
                        </li>
                        {% else %}
                        <!-- 6.11 注册链接 -->
                        <li><a href="/register/">注册</a></li>
                        <!-- 6.10 登入链接 -->
                        <li><a href="/login/">登入</a></li>
                        {% endif %}
                    </ul>
                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>
    </body>
</html>

image-20220330121555296

15.6 布局
3 - 9
左侧展示三个标题表单 右边展示文章
<!--9.页面布局 3 9 -->
<div class="container-fluid">
    <div class="row">
        <!--8.1 右侧菜单栏-->
        <div class="col-md-3">
            <!-- 右侧菜单栏 第一个表单 -->
            <div class="panel panel-primary">
                <div class="panel-heading">分类</div>
                <div class="panel-body">
                    <!--表单内容-->
                </div>
            </div>
            <!-- 右侧菜单栏 第二个表单 -->
            <div class="panel panel-info">
                <div class="panel-heading">标签</div>
                <div class="panel-body">
                    <!--表单内容-->
                </div>
            </div>
            <!-- 右侧菜单栏 第三个表单 -->
            <div class="panel panel-default">
                <div class="panel-heading">时间归档</div>
                <div class="panel-body">
                    <!--表单内容-->
                </div>
            </div>

        </div>

        <!--8.2 文章内容  使用媒体对象展示-->
        <div class="col-md-9">

            <!--分页器-->
            {% for queryset_obj in  article_query_set %}

            <!--媒体对象列表-->
            <div class="media">

                <h4 class="media-heading"><a href="">{{ queryset_obj.title }}</a></h4>
                <div class="media-left media-middle">
                    <a href="#">
                        <img class="media-object" src="/media/{{ queryset_obj.blog.userinfo.avatar }}" alt="..."
                             width="80">
                    </a>
                </div>
                <div class="media-body">
                    {{ queryset_obj.desc }}
                </div>
                <br>
                <div class="pull-right">
                    <p>
                        <!--博客表 反向解析两次拿到用户名-->
                        <span>pasted&nbsp;&nbsp;@&nbsp;</span>
                        <span><a href="">{{ queryset_obj.blog.userinfo.username }}&nbsp;</a></span>
                        <!--文章发布时间-->
                        <span>{{ queryset_obj.create_time|date:'Y-m-d' }}&nbsp;</span>
                        <span class="glyphicon glyphicon-comment">评论({{ queryset_obj.comment_num }})&nbsp;</span>
                        <span class="glyphicon glyphicon-thumbs-up">点赞({{ queryset_obj.up_num }})&nbsp;</span>
                        <span class="glyphicon glyphicon-thumbs-down">点踩({{ queryset_obj.down_num }})&nbsp;</span>
                        <span><a href="">编辑</a>&nbsp;</span>
                    </p>
                </div>
                <br>
                <hr>
            </div>
            <nav aria-label="Page navigation"></nav>
            {% endfor %}
            {# 利用自动分页器 #}
            {{ page_obj.page_html|safe }}
        </div>
    </div>
</div>

15.7 站点样式模拟
在madie目录下创建css目录, 
创建kid.css  qq.css 文件
/* kid.css  */
a {
    color: #aaff00;
}
/* qq.css */
a {
    color: #dd5579;
}
进入站点, 前端获取用户的css样式文件地址.
# kid.css 个人站点的样式文件名称
print(blog_obj.site_theme) 
<!-- 导入个人样式 -->
<link rel="stylesheet" href="/media/css/{{ blog_obj.site_theme }}/">

image-20220330141334844

image-20220330141312564

16. 个人站点左侧栏

个人站点左侧栏的三个表单中分类按 分类, 标签, 时间, 进行归档.
1. 先获取当前用户的所有文章.
2. 文章按 分类, 标签, 时间使用ORM语句镜像分组并统计.
    # 8.2 判断访问的站点是否存在
    blog_obj = models.Blog.objects.filter(site_name=site_name).first()

    # 8.3 存在返回个人站点的数据对象
    if blog_obj:
        # 8.3.1 用户存在 获取该用户的所有文章封装的 query_set对象
        article_query_set = models.Article.objects.filter(blog=blog_obj)
在视图函数的顶部导入聚合函数.
# 8.3.2 时导入聚合函数
from django.db.models import Max, Min, Sum, Count, Avg
16.1 分类表单
第一个表单展示当前用户的所有分类, 和每个分类下的所有文章.
前端需要 (分类的类名的id, 分类的类名, 和统计的数量.) 

文章表 一对一 分类表, 文章表正向查询分类表.
文章的quest_set对象, 按分类的id分组, 统计每组的个数(统计主键 pk), 表名__字段 跨表查询获取需要的数据.
sort_list = article_query_set.annotate(
    count_num=Count('sort__id')).values('sort__pk', 'sort__name', 'count_num')
<QuerySet [
{'sort__pk': 1, 'sort__name': 'kid站点第一个分类', 'count_num': 1},
{'sort__pk': 2, 'sort__name': 'kid站点第二个分类', 'count_num': 1}]>
<!-- 右侧菜单栏 第一个表单 -->
<div class="panel panel-primary">
    <div class="panel-heading">分类</div>
    <div class="panel-body">
        {% for sort_obj in sort_list %}
        <p>
            <a href="/{{ site_name }}/sort/{{sort_obj.sort__pk}}">
                {{ sort_obj.sort__name }}({{ sort_obj.count_num }})
            </a>
        </p>
        {% endfor %}
    </div>
</div>
16.2 标签表单
第二个表单展示当前用户的所有标签, 和每个标签下的所有文章.
前端需要 (标签的id, 标签名称, 和统计的数量.) 

文章表 多对多 标签表 文章表反向查询标签表.
文章的quest_set对象, 按标签的id分组, 统计每组的个数(统计主键 pk), 表名__字段 跨表查询获取需要的数据.
tag_list = article_query_set.annotate(
    count_num=Count('tag__id')).values('tag__pk', 'tag__name', 'count_num')
<QuerySet [{'tag__pk': 1, 'tag__name': 'kid站点第一个标签', 'count_num': 1}, {'tag__pk': 4, 'tag__name': 'kid站点第二个标签', 'count_num': 1}]>
<!-- 右侧菜单栏 第二个表单 -->
<div class="panel panel-info">
    <div class="panel-heading">标签</div>
    <div class="panel-body">
        {% for tag_obj in tag_list %}
        <p>
            <a href="/{{ site_name }}/tag/{{ tag_obj.tag__pk }}/">
                {{ tag_obj.tag__name }}({{ tag_obj.count_num }})
            </a>
        </p>
        {% endfor %}
    </div>
</div>
16.3 时间归档表单
在创建文章表的时候, create_timez字段的时间格式是 年月日
需要的是按年月进行分组, 再进行统计.

Django提供一个函数, 将一个时间时间过滤成年月的格式.
导入TruncMonth函数
from django.db.models.functions import TruncMonth

使用格式:
包含时间字段query_set对象.annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values('month', 'count_num')

同一种表中分组, 直接查找表的字段.
前端需要 (年月分组的名称, 和统计的数量.) 
先按时间字段分组, 分组之后拿到 年月的格式, 再按年月再次分组, 统计分组之后该类中书籍id的数量.
year_month_list = article_query_set.annotate(month=TruncMonth('create_time')).values(
    'month').annotate(count_num=Count('pk')).values('month', 'count_num')
<QuerySet [{'month': datetime.date(2022, 3, 1), 'count_num': 2}]>
<!-- 右侧菜单栏 第三个表单 -->
<div class="panel panel-default">
    <div class="panel-heading">时间归档</div>
    <div class="panel-body">
        {% for year_month_obj in year_month_list %}
        <!-- 手动拼接格式 年-月 -->
        <p><a href="/{{ site_name }}/archiving/{{ year_month_obj.month|date:"Y-m" }}/">{{ year_month_obj.month|date:"Y年m月" }}({{ year_month_obj.count_num }})</a></p>
        {% endfor %}
    </div>
</div>
16.4完整归档代码
# 8.0 登入装饰器
@login_required
# 8.1 个人站点 有名分组匹配的数据  site_name接收访问站点的名称 **kwargs 另外两个参数 eg: tag tag_id
def get_site(request, site_name, **kwargs):
    # 8.2 判断访问的站点是否存在
    blog_obj = models.Blog.objects.filter(site_name=site_name).first()

    # 8.3 存在返回个人站点的数据对象
    if blog_obj:

        # print(blog_obj.site_theme)  # kid.css 个人站点的样式文件名称

        # 8.3.1 用户存在 获取该用户的所有文章封装的 query_set对象
        article_query_set = models.Article.objects.filter(blog=blog_obj)

        # 8.3.3  导入OMR聚合函数
        # 8.3.4  查询当前用户的所有分类, 和分类下的文章数 使用聚合函数以类名分组 获取每个分组下文章的总数 统计主键 __id可以省略

        sort_list = article_query_set.annotate(
            count_num=Count('sort__id')).values('sort__pk', 'sort__name', 'count_num')

        # 8.3.5  查询当前用户站点的所有标签及标签下的文章总数
        tag_list = article_query_set.annotate(
            count_num=Count('tag__id')).values('tag__pk', 'tag__name', 'count_num')

        # 8.3.6 导入 模块
        # 8.3.7 按年月分类, 统计分类下的文章总数
        year_month_list = article_query_set.annotate(month=TruncMonth('create_time')).values(
            'month').annotate(count_num=Count('pk')).values('month', 'count_num')

        # 8.3.8 返回用户的所有文章
        return render(request, 'site.html', locals())
    else:
        # 8.4 个人站点不存在返回 404 页面
        return render(request, 'error.html')

image-20220330140204778

16.5 侧边栏过滤

image-20220330142711956

在点击表单中标签, 查看对应的文章.

url的格式:
http://127.0.0.1:8000/站点名称/sort/类型的id/
http://127.0.0.1:8000/站点名称/tag/标签的id/
http://127.0.0.1:8000/站点名称/archiving/年-月/

第一个路径为 站点的名称, 
第二个路径为 sort|tag|archiving 其中一个
第三个路径为 需要获取的数据依据
在视图函数中对用户的所有文章按照指定的归档信息进行二次过滤, 
在路由中设置有名分组,分别接收: 站点的名称, 访问的归档类型, 该归档下某个类型.
    # 12.6 个人站点左侧栏过滤

    # 单独写
    # url('^(?P<site_name>\w+)/sort/(\d+)/', views.get_site),
    # url('^(?P<site_name>\w+)/tag/(\d+)/', views.get_site),
    # url('^(?P<site_name>\w+)/archiving/(\w+)/', views.get_site)

    # 三合一 condition条件  archiving归档 
    url(r'^(?P<site_name>\w+)/(?P<condition>sort|tag|archiving)/(?P<param>.*)/', views.get_site),
site_name接收访问站点的名称 **kwargs 另外两个参数 eg: tag tag_id
kwargs的数据
{'condition': 'sort', param: '1'}
{'condition': 'tag', param: '1'}
{'condition': 'archiving', param: '2022-03'}
# .1 文章对象 按分类 二次过滤  文章表去查分类表 正向查询 只展示 分类表id=param 的所有数据
if kwargs.get('condition') == 'sort':
    article_query_set = article_query_set.filter(sort_id=kwargs.get('param'))
# .2 文章对象 按标签 二次过滤  文章表去查标签表  反向查询 只展示 标签表id=param 的所有数据
elif kwargs.get('condition') == 'tag':
    article_query_set = article_query_set.filter(tag__id=kwargs.get('param'))
# .3 文章对象 按年月 二次过滤  双下方法查询
elif kwargs.get('condition') == 'archiving':
    year, month = kwargs.get('param').split('-')  # 年-月 按-切分, 解压赋值给两个比变量
    article_query_set = article_query_set.filter(create_time__year=year, create_time__month=month)
# 8.0 登入装饰器
@login_required
# 8.1 个人站点 有名分组匹配的数据  site_name接收访问站点的名称 **kwargs 另外两个参数 eg: tag tag_id
def get_site(request, site_name, **kwargs):
    # 8.2 判断访问的站点是否存在
    blog_obj = models.Blog.objects.filter(site_name=site_name).first()

    # 8.3 存在返回个人站点的数据对象
    if blog_obj:

        # print(blog_obj.site_theme)  # kid.css 个人站点的样式文件名称

        # 8.3.1 用户存在 获取该用户的所有文章封装的 query_set对象
        article_query_set = models.Article.objects.filter(blog=blog_obj)

        # 8.3.2  导入OMR聚合函数
        # 8.3.3  查询当前用户的所有分类, 和分类下的文章数 使用聚合函数以类名分组 获取每个分组下文章的总数 统计主键 __id可以省略

        sort_list = article_query_set.annotate(
            count_num=Count('sort__id')).values('sort__pk', 'sort__name', 'count_num')

        # 8.3.4  查询当前用户站点的所有标签及标签下的文章总数
        tag_list = article_query_set.annotate(
            count_num=Count('tag__id')).values('tag__pk', 'tag__name', 'count_num')

        # 8.3.5 导入 模块
        # 8.3.6 按年月分类, 统计分类下的文章总数
        year_month_list = article_query_set.annotate(month=TruncMonth('create_time')).values(
            'month').annotate(count_num=Count('pk')).values('month', 'count_num')

        # 8.3.7 判断是否访问站点表单的数据,
        if kwargs:

            # .1 文章对象按分类(id=param )二次过滤  文章表去查分类表 正向查询 
            if kwargs.get('condition') == 'sort':
                article_query_set = article_query_set.filter(sort_id=kwargs.get('param'))

            # .2 文章对象按标签(d=param )二次过滤  文章表去查标签表  反向查询 
            elif kwargs.get('condition') == 'tag':
                article_query_set = article_query_set.filter(tag__id=kwargs.get('param'))

            # .3 文章对象 按年月 二次过滤  双下方法查询
            elif kwargs.get('condition') == 'archiving':
                year, month = kwargs.get('param').split('-')  # 年-月 按-切分, 解压赋值给两个比变量
                article_query_set = article_query_set.filter(create_time__year=year, create_time__month=month)

        # 8.3.8 返回用户的所有文章
        return render(request, 'site.html', locals())
    else:
        # 8.4 个人站点不存在返回 404 页面
        return render(request, 'error.html')

GIF 2022-3-30 14-55-54

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值