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. 导航条右侧菜单
在用户登入的时候能显示 用户名 与 更多
用户登入之后, 使用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>
* 清除session后刷新网页
没有用户登入的时候显示 注册 与 登录
9. 修改密码
点击 导航条右侧菜单 更多中的修改密码弹出一个模态框.
采用模态框设计一个表单来修改密码.
9.1 模态框
改为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>
点击修改密码的后触发模态框.
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;
}
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>
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('非法访问!')
错误提示使用二次弹框展示.
正常修改密码后之间跳转到登入页面
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/
登入后展示的页面
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的.
(表的名称有点小差异但是数据是对的)
可视化操作界面
点击User表, 查看表中的数据.
在映射表的类中添加 自定义admin中展示的表名 代码
# 0.7 自定义admin中展示的表名 Meta元类
class Meta:
# verbose_name = '用户表' # 用户表 加s
verbose_name_plural = '用户表' # 用户表 不加s
注册所有的表
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(评论表)'
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. 文章表添加数据
点击文章表 --> 点击添加文章按键
在表中可以直接编辑数据.
点击外键字段可以跳转到外键对应的表中去.
先点击关联博客表的外键 --> 添加站点
两个站点:
名称 站点签名 站点样式
kid 努力学习 kid.css
qq qq的个人博客 qq.css
选择站点 --> 展示的是id对应的数据对象.
在映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回站点名称.
# 1.6 打印query_set对 返回站点名称
def __str__(self):
return self.site_name
在创建一个 个人站点的数据
创建分类 --> 分类的外键绑定 个人站点
创建四个分类:
分类名称 绑定的站点
kid的第一个分类 kid
kid的第二个分类 kid
qq的第一个分类 qq
qq的第二个分类 qq
展示的也外键id对应的数据对象
映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回分类的类名.
# 4.5 在打印query_set对象的时候 返回分类的类名
def __str__(self):
return self.name
添加文章 --> 绑定站点 设置分类
kid 站点第一个分类的文章
kid 站点第二个分类的文章
qq 站点第一个分类的文章
qq 站点第二个分类的文章
添加了四片文章, 打印的数据外键绑定的主键对应的数据对象
映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回文章的标题.
# 2.13 在打印query_set对象的时候 返回文章的标题.
def __str__(self):
return self.title
2. 用表添加数据
用户表绑定站点
在点保存的时候会报错
手机号码字段中:
null=True 设置的是 数据库该字段可以为空
在admin后台中操作就不允许为空了, 需要添加
blank=True 设置的是 admin后台中操作可以为空
# 0.3 手机号码 在写入数据库的时候运行不写
phone = models.BigIntegerField(null=True, blank=True, verbose_name='手机号码')
设置blank=True, 不需要执行数据库迁移命令
qqq账户绑定qq站点
3. 标签表添加数据
标签表绑定站点
kid站点第一个标签绑定 kid站点
kid站点第二个标签绑定 kid站点
qq站点第一个标签绑定 qq站点
qq站点第二个标签绑定 qq站点
g)]
展示的是数据对象
映射类中添加 __str__ 方法. 在打印query_set对象的时候, 标签的名称.
# 3.5 在打印query_set对象的时候展示 标签的名称
def __str__(self):
return self.name
4. 第三张表添加数据
文章与标签表多对多的第三张表添加数据
多对多的关系, 一篇文章可以绑定多个标签. (这里就不绑定了)
kid发布的第一篇文章 Python基础 绑定 kid站点第一个标签
kid发布的第二篇文章 Typora使用 绑定 kid站点第二个标签
qq发布的第一篇文章 MySQL介绍 绑定 qq站点第一个标签
qq发布的第二篇文章 web开发介绍 绑定 qq站点第二个标签
展示的是query_set 对象
第三张表中展示的 文章的id 与 标签的id 没有可展示名称.
映射类中添加 __str__ 方法. 对象.外键 子查询, 查到是外键对应的query_set对象
打印该对象的时候又触发该类中设置的__str__ 方法.
# 7.4 打印query_set self.外键
def __str__(self):
return f'{self.article} -- {self.tag}'
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目录删除.
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
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})
访问源代码, 这是一个危险的操作.
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>
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 媒体对象展示文章
<!--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 }} </a></span>
<!--文章发布时间-->
<span>发布于 {{ queryset_obj.create_time|date:'Y-m-d' }} </span>
<span class="glyphicon glyphicon-comment">评论({{ queryset_obj.comment_num }}) </span>
<span class="glyphicon glyphicon-thumbs-up">点赞({{ queryset_obj.up_num }}) </span>
<span class="glyphicon glyphicon-thumbs-down">点踩({{ queryset_obj.down_num }}) </span>
</div>
<hr>
</div>
<nav aria-label="Page navigation"></nav>
{% endfor %}
{# 分页器 #}
{{ page_obj.page_html|safe }}
</div>
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页面代码.
<!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的参数
通过博客园的访问服务器的资源正常显示
在浏览器中输入: http://127.0.0.1:8000/%E5%93%88%E5%93%88
盗用图片资源被拒绝访问.
将资源下载到本地
<a href='/home/'><img src='/static/img/logo_small.jpg' alt='' /></a>
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>
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 @ </span>
<span><a href="">{{ queryset_obj.blog.userinfo.username }} </a></span>
<!--文章发布时间-->
<span>{{ queryset_obj.create_time|date:'Y-m-d' }} </span>
<span class="glyphicon glyphicon-comment">评论({{ queryset_obj.comment_num }}) </span>
<span class="glyphicon glyphicon-thumbs-up">点赞({{ queryset_obj.up_num }}) </span>
<span class="glyphicon glyphicon-thumbs-down">点踩({{ queryset_obj.down_num }}) </span>
<span><a href="">编辑</a> </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 }}/">
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')
16.5 侧边栏过滤
在点击表单中标签, 查看对应的文章.
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')