【六】个人站点搭建
【一】首页搭建
【路由接口】
home/
【引言】
- 访问网站后显示的主页面
【需求】
-
需要展示的效果
-
布局采用 2/8/2
-
两侧展示自定义个性化内容
- 如广告展示等
-
中间部分展示每一个人的所有文章内容
-
【1】功能实现 - 前端展示页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 本地 链接 引入方法 -->
<!-- Websource 文件夹 拷贝到当前文件夹下即可使用 -->
<!-- jQuery 文件(先导入) -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<!-- Bootstrap 的 JS 文件 (动画效果需要jQuery) -->
<script src="{% static 'plugins/Bootstrap/js/bootstrap.min.js' %}"></script>
<!-- Bootstrap 的 CSS 样式文件 -->
<link rel="stylesheet" href="{% static 'plugins/Bootstrap/css/bootstrap.min.css' %}">
<!-- bootstrap-sweetalert(弹框) 的 CSS 文件 -->
<link rel="stylesheet" href="{% static 'plugins/bootstrap-sweetalert/dist/sweetalert.css' %}">
<!-- bootstrap-sweetalert(弹框) 的 JS 文件 -->
<script src="{% static 'plugins/bootstrap-sweetalert/dist/sweetalert.js' %}"></script>
<!-- 以下为 css样式书写区 -->
<style>
</style>
</head>
<body>
{#导航条#}
<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="#">博客系统</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="#">BBS <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">提交</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">更多操作 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/log_out/">退出登录</a></li>
</ul>
<!-- Large modal 修改密码模态框 -->
<!-- Large modal -->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h1 class="text-center">修改密码</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" disabled value="{{ request.user.username }}"
class="form-control">
</div>
<div class="form-group">
<label for="">原密码</label>
<input type="password" id="id_old_password" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="id_new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="id_confirm_password" class="form-control">
</div>
<button type="button" class="btn btn-default pull-right"
data-dismiss="modal">
取消修改
</button>
<button class="btn btn-danger center-block pull-right"
style="margin-bottom: 30px;margin-right: 10px" id="id_edit">
确认修改
</button>
<span style="color: red" id="id_pwd_error"></span>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="/register/">注册</a></li>
<li><a href="/login/">登陆</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{# 导航条内部 #}
<div class="container-fluid">
{# 左侧面板 #}
<div class="col-md-2">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">美女在线发牌!</h3>
</div>
<div class="panel-body">
即时互动,体验绝佳!享受美女荷官在线发牌!
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">环球娱乐发钱了</h3>
</div>
<div class="panel-body">
尊贵会员,环球娱乐城惊喜派发中,快来领取您的奖励!
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">重金求子!</h3>
</div>
<div class="panel-body">
寻觅天使宝贝,重金助您实现心愿!一旦录取,重金奖赏1000万
</div>
</div>
</div>
<div class="col-md-8">
{# 文章展示 #}
<ul class="media-list">
{% for article_obj in page_queryset %}
<li class="media">
<h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
<img class="media-object" src="/Source/{{ article_obj.blog.userinfo.avatar }}" alt="..."
style="height: 50px;width: 50px;margin-left: 20px">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div>
<br>
<div>
<span><a href="/{{ article_obj.blog.userinfo.username }}/">{{ article_obj.blog.userinfo.username }} </a></span>
<span>发布于</span>
<span>{{ article_obj.create_time|date:"Y-m-d" }} </span>
<span><span class="glyphicon glyphicon-comment"></span> 评论 {{ article_obj.comment_num }} </span>
<span><span class="glyphicon glyphicon-thumbs-up"></span> 点赞 {{ article_obj.up_num }} </span>
</div>
</li>
<hr>
{% endfor %}
</ul>
</div>
<div class="col-md-2">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">美女在线发牌!</h3>
</div>
<div class="panel-body">
即时互动,体验绝佳!享受美女荷官在线发牌!
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">环球娱乐发钱了</h3>
</div>
<div class="panel-body">
尊贵会员,环球娱乐城惊喜派发中,快来领取您的奖励!
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">重金求子!</h3>
</div>
<div class="panel-body">
寻觅天使宝贝,重金助您实现心愿!一旦录取,重金奖赏1000万
</div>
</div>
</div>
</div>
<script>
$('#id_edit').click(function () {
$.ajax({
url: "/set_password/",
type: "post",
data: {
"old_password": $("#id_old_password").val(),
"new_password": $("#id_new_password").val(),
"confirm_password": $("#id_confirm_password").val(),
"csrfmiddlewaretoken": "{{ csrf_token }}",
},
success: function (args) {
if (args.code === 1000) {
// 刷新页面
alert(args.message);
window.location.reload();
} else {
$("#id_pwd_error").text(args.message)
}
}
})
})
</script>
</body>
</html>
-
延续自登录功能继续扩展内容
-
登录/修改密码等功能已经将外部边框搭建起来
-
下面只需要搭建内部的内容
-
内部内容采用 2/8/2 分配
- 左右两侧
- 展示广告/自定义内容
- 中间
- 显示文章及相关信息
- 左右两侧
-
整体布局
<div class="container-fluid"> <div class="col-md-2"></div> <div class="col-md-8"></div> <div class="col-md-2"></div> </div>
-
两侧设计
-
采用官方文档中的面板-带标题的面板
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Panel title</h3> </div> <div class="panel-body"> Panel content </div> </div>
-
修改后
<div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">美女在线发牌!</h3> </div> <div class="panel-body"> 即时互动,体验绝佳!享受美女荷官在线发牌! </div> </div> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">环球娱乐发钱了</h3> </div> <div class="panel-body"> 尊贵会员,环球娱乐城惊喜派发中,快来领取您的奖励! </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">重金求子!</h3> </div> <div class="panel-body"> 寻觅天使宝贝,重金助您实现心愿!一旦录取,重金奖赏1000万 </div> </div>
-
-
中间主体内容部分
-
采用官方模版,媒体对象
<ul class="media-list"> <li class="media"> <div class="media-left"> <a href="#"> <img class="media-object" src="..." alt="..."> </a> </div> <div class="media-body"> <h4 class="media-heading">Media heading</h4> ... </div> </li> </ul>
-
-
自定制
{# 文章展示 #}
<ul class="media-list">
{% for article_obj in page_queryset %}
<li class="media">
<h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
<img class="media-object" src="/Source/{{ article_obj.blog.userinfo.avatar }}" alt="..."
style="height: 50px;width: 50px;margin-left: 20px">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div>
<br>
<div>
<span><a href="/{{ article_obj.blog.userinfo.username }}/">{{ article_obj.blog.userinfo.username }} </a></span>
<span>发布于</span>
<span>{{ article_obj.create_time|date:"Y-m-d" }} </span>
<span><span class="glyphicon glyphicon-comment"></span> 评论 {{ article_obj.comment_num }} </span>
<span><span class="glyphicon glyphicon-thumbs-up"></span> 点赞 {{ article_obj.up_num }} </span>
</div>
</li>
<hr>
{% endfor %}
</ul>
- 自定制页面分析
- 样式构成
- 头上显示文章标题
- 标题下面
- 左侧是用户头像
- 右侧是用户文章的简介
- 样式构成
- 我们之前的图像文件是固定存放在某个文件夹是静态文件
- 但是无法通过链接直接访问
- 导致我们的用户头像无法加载
- 这里使用到了一个解决办法
- media配置 + 开始后端文件夹指定资源
- 文章主题的下方是
- 用户名(可访问个人站点)
- 发布日期
- 评论数
- 点赞数
【2】功能实现 - 后端提供数据
- 分页器
- 接口
# 首页接口
def home(request):
# 查询网站上所有的数据,并展示到页面上 ---分页器做分页
article_queryset = models.Article.objects.all()
# 分页器
current_page = request.GET.get("page", 1)
all_count = article_queryset.count()
page_obj = Pagination(current_page=current_page, all_count=all_count, per_page_num=10)
page_queryset = article_queryset[page_obj.start:page_obj.end]
return render(request, 'home.html', locals())
-
使用了分页器 - 防止页数过多
-
查询到文章表里面的所有文章
- 返回给前端调用
【补充】media配置开设指定文件资源接口
【一】引言
-
网址所使用的静态文件默认都放在 static 文件夹下
-
用户上传的静态文件也应该单独放在某个文件夹下
-
该配置可以让用户上传的所有文件都固定存放在某一个文件夹下
-
并且可以通过网络连接访问访问到指定资源
【二】settings配置文件配置
# media 配置 --- 配置用户上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'Source')
Source
为自定义的用户上传的文件存储静态文件文件夹
【三】开放指定资源访问接口
- 特别提醒
- 开放指定资源的访问接口一定要考虑好要开放那些文件
- 一旦开放错了,可能会导致源码泄露
- 造成不可估量的损失
- 在路由中开放接口
# 开设后端指定文件夹资源
# re_path 写法
re_path(r'^Source/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
# path 写法
path('Source/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
Source
为自定义文件夹名称- 以上为固定写法,只需要更改路由中的路由头即可
【四】前端访问指定资源
<img class="media-object" src="/Source/{{ article_obj.blog.userinfo.avatar }}" alt="..."
style="height: 50px;width: 50px;margin-left: 20px">
img
标签通过链接,访问指定资源位置- 使用到了模版语法获取后端传入的图片数据
【二】个人站点搭建(基于首页跳转)
【路由接口】
<str:username>/
【引言】
- 跳转到的个人首页
【二】需求
-
需要展示的效果
- 布局采用 3/9
-
左侧展示
- 文章分类
- 文章标签
- 文章归档(日期归档)
-
右侧显示个人的所有文章
【三】功能实现 - 前端展示页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 本地 链接 引入方法 -->
<!-- Websource 文件夹 拷贝到当前文件夹下即可使用 -->
<!-- jQuery 文件(先导入) -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<!-- Bootstrap 的 JS 文件 (动画效果需要jQuery) -->
<script src="{% static 'plugins/Bootstrap/js/bootstrap.min.js' %}"></script>
<!-- Bootstrap 的 CSS 样式文件 -->
<link rel="stylesheet" href="{% static 'plugins/Bootstrap/css/bootstrap.min.css' %}">
<!-- bootstrap-sweetalert(弹框) 的 CSS 文件 -->
<link rel="stylesheet" href="{% static 'plugins/bootstrap-sweetalert/dist/sweetalert.css' %}">
<!-- bootstrap-sweetalert(弹框) 的 JS 文件 -->
<script src="{% static 'plugins/bootstrap-sweetalert/dist/sweetalert.js' %}"></script>
<!-- 以下为 css样式书写区 -->
<link rel="stylesheet" href="/Source/css/{{ blog.site_theme }}">
<style>
</style>
</head>
<body>
{#导航条#}
<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="#">{{ blog.site_title }}</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="#">BBS <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">提交</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">更多操作 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/log_out/">退出登录</a></li>
</ul>
<!-- Large modal 修改密码模态框 -->
<!-- Large modal -->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h1 class="text-center">修改密码</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" disabled value="{{ request.user.username }}"
class="form-control">
</div>
<div class="form-group">
<label for="">原密码</label>
<input type="password" id="id_old_password" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="id_new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="id_confirm_password" class="form-control">
</div>
<button type="button" class="btn btn-default pull-right"
data-dismiss="modal">
取消修改
</button>
<button class="btn btn-danger center-block pull-right"
style="margin-bottom: 30px;margin-right: 10px" id="id_edit">
确认修改
</button>
<span style="color: red" id="id_pwd_error"></span>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="/register/">注册</a></li>
<li><a href="/login/">登陆</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{#导航条内部#}
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">文章分类</h3>
</div>
<div class="panel-body">
{% for category in category_list %}
<p><a href="/{{ username }}/category/{{ category.pk }}/">{{ category.name }}
({{ category.category_num }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">文章标签</h3>
</div>
<div class="panel-body">
{% for tag in tag_list %}
<p><a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }} ({{ tag.category_num }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">日期归档</h3>
</div>
<div class="panel-body">
{% for date in date_list %}
<p><a href="/{{ username }}/archive/{{ date.month|date:"Y-m" }}/">{{ date.month|date:"Y年m月" }}
({{ date.count_num }})</a></p>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-9">
{# 文章展示 #}
<ul class="media-list">
{% for article_obj in article_list %}
<li class="media">
<h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
<img class="media-object" src="/Source/{{ article_obj.blog.userinfo.avatar }}" alt="..."
style="height: 50px;width: 50px;margin-left: 20px">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div>
<div class="pull-right">
<span>posted</span>
<span>{{ article_obj.create_time|date:"Y-m-d" }} </span>
<span>{{ article_obj.blog.userinfo.username }} </span>
<span><span
class="glyphicon glyphicon-comment"></span> 评论 {{ article_obj.comment_num }} </span>
<span><span class="glyphicon glyphicon-thumbs-up"></span> 点赞 {{ article_obj.up_num }} </span>
<span><a href="">编辑</a></span>
</div>
</li>
<hr>
{% endfor %}
</ul>
</div>
</div>
</div>
</body>
</html>
-
外部延续首页页面导航栏
- 可以用 块 包起来
- 然后继承页面
- 后续可能会优化,目前就先这样,先把功能实现再选择是否优化
-
内部采用 3/9 排版
<div class="container-fluid">
<div class="col-md-3"></div>
<div class="col-md-9"></div>
</div>
-
左侧部分展示
- 文章分类
- 文章标签
- 日期归档
<div class="panel panel-info"> <div class="panel-heading"> <h3 class="panel-title">文章分类</h3> </div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.pk }}/">{{ category.name }} ({{ category.category_num }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">文章标签</h3> </div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.pk }}/">{{ tag.name }} ({{ tag.category_num }})</a></p> {% endfor %} </div> </div> <div class="panel panel-warning"> <div class="panel-heading"> <h3 class="panel-title">日期归档</h3> </div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.month|date:"Y-m" }}/">{{ date.month|date:"Y年m月" }} ({{ date.count_num }})</a></p> {% endfor %} </div> </div>
-
右侧部分展示用户所有的文章内容
{# 文章展示 #} <ul class="media-list"> {% for article_obj in article_list %} <li class="media"> <h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4> <div class="media-left"> <a href="#"> <img class="media-object" src="/Source/{{ article_obj.blog.userinfo.avatar }}" alt="..." style="height: 50px;width: 50px;margin-left: 20px"> </a> </div> <div class="media-body"> {{ article_obj.desc }} </div> <div class="pull-right"> <span>posted</span> <span>{{ article_obj.create_time|date:"Y-m-d" }} </span> <span>{{ article_obj.blog.userinfo.username }} </span> <span><span class="glyphicon glyphicon-comment"></span> 评论 {{ article_obj.comment_num }} </span> <span><span class="glyphicon glyphicon-thumbs-up"></span> 点赞 {{ article_obj.up_num }} </span> <span><a href="">编辑</a></span> </div> </li> <hr> {% endfor %} </ul>
【404】页面
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 本地 链接 引入方法 -->
<!-- Websource 文件夹 拷贝到当前文件夹下即可使用 -->
<!-- jQuery 文件(先导入) -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<!-- Bootstrap 的 JS 文件 (动画效果需要jQuery) -->
<script src="{% static 'plugins/Bootstrap/js/bootstrap.min.js' %}"></script>
<!-- Bootstrap 的 CSS 样式文件 -->
<link rel="stylesheet" href="{% static 'plugins/Bootstrap/css/bootstrap.min.css' %}">
<!-- bootstrap-sweetalert(弹框) 的 CSS 文件 -->
<link rel="stylesheet" href="{% static 'plugins/bootstrap-sweetalert/dist/sweetalert.css' %}">
<!-- bootstrap-sweetalert(弹框) 的 JS 文件 -->
<script src="{% static 'plugins/bootstrap-sweetalert/dist/sweetalert.js' %}"></script>
<!-- 以下为 css样式书写区 -->
<!-- 以下为 css样式书写区 -->
<style>
</style>
</head>
<body>
<div class="error-container text-center">
<div class="error-panel server-error error-404">
<a href="/home/"><img src="https://static.hdslb.com/error/very_sorry.png"></a>
<p style="font-size: 28px">抱歉,您访问的资源不存在</p>
<br>
<p style="font-size: 28px">请确认您输入的网址是否正确</p>
<br>
<a class="change-img-btn btn btn-primary" href="/home/" style="font-size: 28px">返回网站首页</a>
</div>
<div class="error-split" id="up">
</div>
<div class="error-manga">
<img src="{% static 'img/error.png' %}" style="margin-top: 50px">
</div>
</div>
</body>
</html>
【四】功能实现 - 后端提供数据
【1】路由
# 侧边栏筛选页面搭建 ---- 指向同一个路由
# # (1) 分类筛选 http://127.0.0.1:8000/dream/category/1
# # re_path(r'^(?P<username>\w+)/category/(\d+)/', views.site),
# path('<str:username>/category/<int:category_id>/', views.site),
# # (2) 标签筛选 http://127.0.0.1:8000/dream/tag/1
# path('<str:username>/tag/<int:category_id>/', views.site),
# # (3) 日期筛选 http://127.0.0.1:8000/dream/archive/2023-06
# path('<str:username>/archive/<str:date>/', views.site),
# 侧边栏筛选页面搭建 ---- 指向同一个路由(合并路由)
re_path(r"^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/", views.site),
# 开设后端指定文件夹资源
re_path(r'^Source/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
- 路由接口一共有两部分
- 一部分是用来开放外部访问资源的接口
- 一部分是根据用户选择的不同跳转到不同接口的功能
- 这里使用到了多个URL公用一个是视图函数
from common.my_page import Pagination
# 日期分组归档
from django.db.models.functions import TruncMonth
from book import models
from django.shortcuts import render, HttpResponse, redirect
# 个人站点接口
def site(request, username, **kwargs):
'''
:param request:
:param username:
:param kwargs: 如果该参数有值,则代表我们需要对 article_list 做额外的筛选操作
:return:
'''
# 校验当前用户名对应的个人站点是否存在
user_obj = models.UserInfo.objects.filter(username=username).first()
# 用户如果不存在,返回 404 页面
if not user_obj:
return render(request, 'error.html', locals())
# 用户存在,展示个人站点
blog = user_obj.blog
article_list = models.Article.objects.filter(blog=blog)
if kwargs:
# print(kwargs) # {'condition': 'tag', 'param': '1'}
condition = kwargs.get('condition')
param = kwargs.get('param')
# 判断筛选条件
if condition == "category":
article_list = article_list.filter(category_id=param)
elif condition == "tag":
# 跨表查询
article_list = article_list.filter(tags__id=param)
elif condition == "archive":
year, month = param.split('-') # 2023-06 > [2023,06]
article_list = article_list.filter(create_time__year=year, create_time__month=month)
else:
return render(request, 'error.html')
# 查询当前用户所有的分类及分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(category_num=Count('article__pk')).values('name',
'category_num',
'pk')
# 查询当前用户所有的标签及标签下的文章数
tag_list = models.CategoryTag.objects.filter(blog=blog).annotate(category_num=Count('article__pk')).values('name',
'category_num',
'pk')
# 按照年月统计所有的文章 - 年月归档
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth("create_time")).values(
"month").annotate(count_num=Count('pk')).values('month', 'count_num')
return render(request, 'site.html', locals())
-
主逻辑
- 参数方面
- 一个是
username
- 用来接收当前登录的用户名
- 一个是
**kwargs
- 用来接收需要访问的功能
- 比如分类筛选
category
- 比如标签筛选
category
- 比如日期筛选
category
- 比如分类筛选
- 用来接收需要访问的功能
- 而我们想要呈现 的效果是这样的
http://127.0.0.1:8000/dream/category/1
http://127.0.0.1:8000/dream/tag/1
http://127.0.0.1:8000/dream/archive/2023-06
- 一个是
- 参数方面
-
首先根据传入进来的用户名在用户信息表中比对校验是否存在当前用户
- 如果不存在当前用户,则直接转到 404 页面
-
用户存在,根据用户对象拿到用户的站点名称
blog = user_obj.blog
-
根据用户的站点名称,在文章表中过滤出属于当前站点的文章
article_list = models.Article.objects.filter(blog=blog)
-
当链接后面没有跟指定路径的时候
- 进行文章的分类查询和文章数查询
# 查询当前用户所有的分类及分类下的文章数 category_list = models.Category.objects.filter(blog=blog).annotate(category_num=Count('article__pk')).values('name','category_num','pk')
- 查询当前用户的所有标签以及标签下的文章
# 查询当前用户所有的标签及标签下的文章数 tag_list = models.CategoryTag.objects.filter(blog=blog).annotate(category_num=Count('article__pk')).values('name','category_num','pk')
- 按照年月统计所有文章 - 日期归档
# 按照年月统计所有的文章 - 年月归档 date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth("create_time")).values("month").annotate(count_num=Count('pk')).values('month', 'count_num')
-
当链接后面存在指定路径的时候
- 取到路径参数和用户的文章id
if kwargs: # print(kwargs) # {'condition': 'tag', 'param': '1'} condition = kwargs.get('condition') param = kwargs.get('param')
- 根据路径的不同,划分不同的功能模块
- 根据文章的分类,筛选出当前分类下的所有文章
# 判断筛选条件 if condition == "category": article_list = article_list.filter(category_id=param)
- 根据标签的分类,筛选出当前标签下的所有文章
elif condition == "tag": # 跨表查询 article_list = article_list.filter(tags__id=param)
- 根据用户选择的时间(年月)删选出符合时间的文章
elif condition == "archive": year, month = param.split('-') # 2023-06 > [2023,06] article_list = article_list.filter(create_time__year=year, create_time__month=month)
-
返回前端页面展示内容
【补充】图片防盗链
图片防盗链介绍
- 图片防盗链(Hotlink Protection)是一种针对恶意盗链行为的保护措施
- 用于防止其他网站直接链接到您服务器上的图片资源。
- 例如
- 如果您在自己的网站上使用了许多精心制作的图片
- 您可能不希望其他网站通过在其网页中引用您的图片 URL 来显示您的图片,并占用您的服务器带宽。
图片防盗链措施目的
-
希望节约带宽:
- 当其他网站直接链接您的图片时
- 他们会消耗您服务器的带宽资源
- 增加您的服务器负载。
-
保护版权:
- 如果您的图片受版权保护
- 您可能不希望其他网站直接在自己的页面上展示您的图片
- 而没有经过您的允许。
常见解决方案
-
通过服务器配置文件实现防盗链:
- 在您的 Web 服务器配置文件(如 Apache 的 .htaccess)中
- 添加一些规则来允许只有特定来源(referer)的请求能够访问您的图片资源。
- 这样,如果来源不符合规则,服务器会返回一个替代的图像或错误信息。
- 您可以根据您的需求设置多个referer规则
- 以控制哪些来源是允许的。
- 在您的 Web 服务器配置文件(如 Apache 的 .htaccess)中
-
使用防盗链工具或插件:
- 一些网站托管平台或内容管理系统提供防盗链功能的插件或工具。
- 这些工具可以帮助您轻松地配置和管理防盗链设置,而无需手动编辑服务器配置文件。
-
CDN(内容分发网络)防盗链:
- 如果您使用了CDN服务,您可以配置CDN防盗链选项。
- 这将使CDN在处理请求时校验referer,并拒绝非法盗链请求。
- 需要注意的是,图片防盗链并非绝对可靠的解决方案
- 因为有些恶意盗链者可能使用各种方法来规避防护措施。
- 此外,某些合法用户可能由于网络配置问题而出现无法正常查看您的图片的情况。
- 因此,在实施图片防盗链前,建议您仔细权衡利弊,并进行充分测试,以确保它不会对您的合法用户造成任何不便。
- 另外,如果您的图片受到严格版权保护,您还可以考虑使用其他技术措施
- 如数字水印或登录验证等,以进一步保护您的版权和图片资源的安全性。
【补充】Django中路由分发的本质
【一】路由分发机制
- 在Django中,admin 路由分发的本质是将管理员后台管理功能与应用程序正常的 URL 分发机制进行了分离。
- admin 路由分发可以让开发者方便地创建、配置和管理应用程序的后台管理界面。
【二】关键部分
-
admin.site.register(model):
-
通过调用
admin.site.register()
方法,将应用程序中的模型(Model)注册到管理员后台。 -
这样,Django 就知道要为这些模型提供哪些默认的管理功能
- 如列表显示、搜索、过滤、添加、编辑、删除等。
-
-
admin.autodiscover():
-
该方法被用于自动发现应用程序中的
admin.py
文件,以便导入其中的admin
实例。 -
在 Django 的设置文件中,通常会包含这样一行代码:
-
admin.autodiscover()
。 -
这会触发 Django 在每个应用程序中查找
admin.py
文件 -
并将其导入到管理员后台。
-
-
-
admin.site.urls:
- 在项目的 URL 配置中,通常会包含一个路由模式,即将
^admin/
路径匹配到admin.site.urls
。 - 这个 URL 模式实际上将所有以
admin/
开头的请求都分发给了管理员后台的视图函数。 - Django 的
admin.site.urls
内部使用了一些 URL 配置和视图处理来渲染和处理后台管理界面的相关请求。
- 在项目的 URL 配置中,通常会包含一个路由模式,即将
- 通过以上的机制,Django 的 admin 路由分发可以将管理员后台的 URL 请求与应用程序正常的 URL 分发机制进行隔离,并提供一套功能强大、易于配置和扩展的后台管理界面。
- 开发者可以在
admin.py
文件中自定义注册的模型的显示方式、过滤器、搜索字段、编辑表单等各个方面,以满足具体业务需求。
【三】案例演示
path('admin/', ([],None,None)),
-
admin路由分发的本质是使用
include
函数将admin.site.urls
包含进项目的URL配置中。 -
具体来说
admin.site.urls
实际上是一个AdminSite
类的实例- 其中定义了一个名为
urls
的装饰器。
- 其中定义了一个名为
- 这个装饰器返回了一个三元组
- 包含了后台管理界面的URL配置、名称和命名空间。
-
当我们在项目的URL配置中使用
include('admin.urls')
或者path('admin/', admin.site.urls)
时- Django会将后台管理界面的URL配置追加到主URL配置中
- 从而使得以
admin/
开头的URL都可以被匹配。
-
而
AdminSite
类中的urls
装饰器的具体实现是通过定义一个property
属性- 返回了
self.get_urls(), 'admin', self.name
。 - 这里的
self.get_urls()
实际上调用了AdminSite
类的get_urls
方法 - 用于获取后台管理界面的URL配置列表。
- 返回了
-
通过这种方式,我们可以在项目的URL配置中套娃无限制地添加多层级的URL,构建复杂的 URL 分发结构。
- 比如,可以在
admin/
路径下再嵌套其他路径和视图函数,形成多层级的后台管理界面。
- 比如,可以在
-
以下是一个完整且稍微复杂一点的案例演示:
from django.contrib import admin
from django.urls import include, path
from django.http import HttpResponse
def index(request):
return HttpResponse("This is the index page.")
urlpatterns = [
# 主页
path('', index, name='home'),
# 后台管理界面
path('admin/', admin.site.urls),
# 自定义的二级路径
path('custom/', include([
# 二级路径下的一级路径
path('level1/', include([
# 二级路径下的一级路径下的二级路径
path('level2/', include([
# 二级路径下的一级路径下的二级路径下的三级路径
path('level3/', index, name='custom-level3'),
])),
])),
])),
]
- 在上述代码中,我们定义了一个带有自定义路径的URL配置,包括了主页和后台管理界面。
- 在自定义路径的部分,我们通过
include
函数进行嵌套,形成了多层级的URL结构。
- 在自定义路径的部分,我们通过
- 其中
- 后台管理界面被包含在
admin/
路径下,而自定义路径则为custom/
。 custom/
路径下又包含了多层级的路径,最终指向了一个名为custom-level3
的视图函数。
- 后台管理界面被包含在
- 通过这样的URL配置
- 我们可以实现非常灵活的URL分发,并构建符合我们项目需求的后台管理界面和其他自定义功能页面。
【补充】多个URL公用一个是视图函数
- 我们可以根据路由地址后面参数的不同跳转到指定的功能函数里面
- 我们也可以在函数内根据关键位置传参,跳转到指定路由
# 侧边栏筛选页面搭建 ---- 指向同一个路由
# # (1) 分类筛选 http://127.0.0.1:8000/dream/category/1
# # re_path(r'^(?P<username>\w+)/category/(\d+)/', views.site),
# path('<str:username>/category/<int:category_id>/', views.site),
# # (2) 标签筛选 http://127.0.0.1:8000/dream/tag/1
# path('<str:username>/tag/<int:category_id>/', views.site),
# # (3) 日期筛选 http://127.0.0.1:8000/dream/archive/2023-06
# path('<str:username>/archive/<str:date>/', views.site),
-
但是使用上面的方法的话,代码会冗余很多,
- 在多个路由使用同一个视图函数时
- 我们需要考虑到优化问题
-
优化后的代码
# 侧边栏筛选页面搭建 ---- 指向同一个路由(合并路由)
re_path(r"^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<param>.*)/", views.site),
- 后端根据前端的路由做出指定的功能划分
def site(request, username, **kwargs):
'''
:param request:
:param username:
:param kwargs: 如果该参数有值,则代表我们需要对 article_list 做额外的筛选操作
:return:
'''
# 校验当前用户名对应的个人站点是否存在
user_obj = models.UserInfo.objects.filter(username=username).first()
# 用户如果不存在,返回 404 页面
if not user_obj:
return render(request, 'error.html', locals())
# 用户存在,展示个人站点
blog = user_obj.blog
article_list = models.Article.objects.filter(blog=blog)
if kwargs:
# print(kwargs) # {'condition': 'tag', 'param': '1'}
condition = kwargs.get('condition')
param = kwargs.get('param')
# 判断筛选条件
if condition == "category":
article_list = article_list.filter(category_id=param)
elif condition == "tag":
# 跨表查询
article_list = article_list.filter(tags__id=param)
elif condition == "archive":
year, month = param.split('-') # 2023-06 > [2023,06]
article_list = article_list.filter(create_time__year=year, create_time__month=month)
else:
return render(request, 'error.html')
# 查询当前用户所有的分类及分类下的文章数
category_list = models.Category.objects.filter(blog=blog).annotate(category_num=Count('article__pk')).values('name',
'category_num',
'pk')
# 查询当前用户所有的标签及标签下的文章数
tag_list = models.CategoryTag.objects.filter(blog=blog).annotate(category_num=Count('article__pk')).values('name',
'category_num',
'pk')
# 按照年月统计所有的文章 - 年月归档
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth("create_time")).values(
"month").annotate(count_num=Count('pk')).values('month', 'count_num')
return render(request, 'site.html', locals())
【补充】个人站点使用自定义首页样式
原理还是依赖于暴漏出去的文件资源接口
- 使用的时候只需要根据当前用户名引入自己的css/js文件即可
<link rel="stylesheet" href="/Source/css/{{ blog.site_theme }}">
【补充】路由冲突问题
-
在某些情况下,我们可能需要用到正则表达式去匹配我们的路由
-
但是由于这种方法第一个参数是正则表达式
- 所以当路由特别多的时候,可能会出现被顶替的情况
-
解决方式
- 修改正则表达式
- 调整路由位置
- 推荐根据功能的不同划分出不同的接口 api
- 采用路由分发的方式,将不同的功能接口引入各自的文件夹中进行路由接口开放
【补充】根据年月对文章进行分组
【一】官方文档的参考写法
Django官网提供的 orm 语法
# django官网提供的一个orm语法
from django.db.models.functions import TruncMonth
-官方提供
from django.db.models.functions import TruncMonth
Sales.objects
.annotate(month=TruncMonth('timestamp')) # Truncate to month and add to select list
.values('month') # Group By month
.annotate(c=Count('id')) # Select the count of the grouping
.values('month', 'c') # (might be redundant, haven't tested) select month and countSales就是指models里面的模型类
-
from django.db.models.functions import TruncMonth
:导入TruncMonth
函数,该函数用于将日期时间字段截断到月份,并将其添加到选择列表中。 -
Sales.objects
:Sales
是一个模型类,通过使用objects
属性可以获取该模型类对应的查询集对象,可以对其进行查询和操作。 -
annotate(month=TruncMonth('timestamp'))
:使用annotate
方法扩展查询集,在结果中添加一个名为month
的字段,并使用TruncMonth
函数对timestamp
字段进行截断。 -
.values('month')
:将结果按照month
字段进行分组。 -
.annotate(c=Count('id'))
:对分组后的结果再次使用annotate
方法,在结果中添加一个名为c
的字段,该字段表示每个分组中id
字段的计数。 -
.values('month', 'c')
:指定返回结果中的字段,包括month
和c
字段。
【二】简单的示例
# 按照年月统计所有的文章
date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values("month").annotate(count_num=Count("pk")).values_list('month','count_num')
# 先filter(blog=blog)查找到当前用户的所有文章
# annotate(month=TruncMonth('create_time')) 以创建时间的月分组
# 第二个annotate前的values("month")是分组条件
【三】我的项目中的演示
year, month = param.split('-') # 2023-06 > [2023,06]
article_list = article_list.filter(create_time__year=year, create_time__month=month)
【四】扩展
【Trunc】
Trunc
:- 这是一个用于日期时间字段截断的函数类。
- 它可以用于按照指定的单位(如年、月、周等)来截断日期时间字段。
Trunc
函数可以根据具体需求选择不同的截断单位- 例如
TruncYear
(按年截断) TruncMonth
(按月截断)TruncDay
(按天截断)等。
- 例如
【Extract】
Extract
:- 这个函数类用于从日期时间字段中提取指定部分(如年、月、日等)。
- 可以使用具体的
Extract
函数- 如
ExtractYear
(提取年份) ExtractMonth
(提取月份)ExtractDay
(提取天数)等。
- 如
【1】TruncMonth
- 当使用
TruncMonth
函数时- 它可以用于按照月份截断日期时间字段。
- 下面是一个使用
TruncMonth
函数的示例:
- 假设我们有一个模型类
Transaction
- 其中包含一个
date
字段,表示交易发生的日期。
- 其中包含一个
- 我们可以使用
TruncMonth
函数来按月份截断日期- 并计算每个月的交易总额。
from django.db.models import Sum
from django.db.models.functions import TruncMonth
from myapp.models import Transaction
# 查询每个月的交易总额
monthly_totals = Transaction.objects.annotate(
month=TruncMonth('date')
).values('month').annotate(
total_amount=Sum('amount')
).order_by('month')
for entry in monthly_totals:
month = entry['month']
total_amount = entry['total_amount']
print(f"{month.strftime('%Y-%m')}: {total_amount}")
- 在上面的示例中,首先使用
annotate
方法和TruncMonth('date')
来创建一个新的字段month
- 该字段对应于每个交易的月份。
- 然后使用
values
和annotate
来分组并计算每个月的交易总额。 - 最后使用
order_by
对结果进行排序。
【2】TruncYear
-
TruncYear
是Django ORM中的一个函数- 用于按照年份截断日期时间字段。
- 下面是对
TruncYear
函数的详细解释和使用案例:
-
TruncYear
函数可以将日期时间字段按照年份进行截断- 得到该年的开始日期。
-
这个函数可以用于统计某一年的数据
- 或者按年份进行分组和聚合操作。
-
下面是一个使用
TruncYear
函数的示例: -
假设我们有一个模型类
Transaction
- 其中包含一个名为
transaction_date
的日期字段 - 表示交易发生的日期。
- 我们希望按年份统计每年的交易总额。
- 其中包含一个名为
from django.db.models import Sum
from django.db.models.functions import TruncYear
from myapp.models import Transaction
# 查询每年的交易总额
yearly_totals = Transaction.objects.annotate(
year=TruncYear('transaction_date')
).values('year').annotate(
total_amount=Sum('amount')
).order_by('year')
for entry in yearly_totals:
year = entry['year']
total_amount = entry['total_amount']
print(f"{year.year}: {total_amount}")
- 在上面的示例中,我们首先使用
annotate
方法和TruncYear('transaction_date')
函数创建了一个新字段year
- 该字段对应于每个交易的年份。
- 然后使用
values
和annotate
对年份进行分组并计算每年的交易总额。 - 最后使用
order_by
对结果按照年份进行排序。 - 请注意,由于
TruncYear
函数将日期字段按照年份截断,所以在使用时需要根据自己的模型和字段名称进行相应的调整,以确保代码的正确执行。
【3】TruncDay
-
TruncDay
是Django ORM中的一个函数- 用于按照天数截断日期时间字段。
-
下面是对
TruncDay
函数的详细解释和使用案例:TruncDay
函数可以将日期时间字段按照天数进行截断- 得到该天的开始时间。
- 这个函数可以用于按天统计数据、按天进行分组和聚合操作
- 或者筛选某个特定的日期范围内的数据。
-
下面是一个使用
TruncDay
函数的示例:- 假设我们有一个模型类
Sales
,其中包含一个名为sale_date
的日期字段 - 表示销售发生的日期。
- 我们希望按天统计每天的销售总额。
- 假设我们有一个模型类
from django.db.models import Sum
from django.db.models.functions import TruncDay
from myapp.models import Sales
# 查询每天的销售总额
daily_totals = Sales.objects.annotate(
day=TruncDay('sale_date')
).values('day').annotate(
total_amount=Sum('amount')
).order_by('day')
for entry in daily_totals:
day = entry['day']
total_amount = entry['total_amount']
print(f"{day.date()}: {total_amount}")
- 在上面的示例中
- 我们首先使用
annotate
方法和TruncDay('sale_date')
函数创建了一个新字段day
- 该字段对应于每个销售的日期。
- 然后使用
values
和annotate
对日期进行分组并计算每天的销售总额。 - 最后使用
order_by
对结果按照日期进行排序。
- 我们首先使用
- 请注意
- 由于
TruncDay
函数将日期字段按照天数截断 - 所以在使用时需要根据自己的模型和字段名称进行相应的调整,以确保代码的正确执行。
- 由于
【补充】时间出错问题解决
TIME_ZONE = 'Asia/Shanghai'
和USE_TZ = False
是Django项目设置中的两个相关选项- 用于指定项目的时区和是否使用时区。
【一】TIME_ZONE = 'Asia/Shanghai'
- 这个设置用于指定项目所在的时区。
- 在这个例子中,时区被设置为'Asia/Shanghai'
- 表示项目位于上海时区。
- 通过设置正确的时区
- 可以确保项目在处理日期时间相关的操作时
- 能够正确地转换和显示时间。
【二】USE_TZ = False
- 这个设置表示项目是否使用时区感知功能。
- 当
USE_TZ
设置为False
时- 意味着项目不会处理时区感知的日期时间。
- 在这种情况下
- 日期时间字段将被假设为处于与
TIME_ZONE
相同的本地时区 - 并且在存储到数据库之前会被转换为该时区。
- 日期时间字段将被假设为处于与
- 同样
- 在从数据库中读取日期时间字段时
- 它们将被假定为处于本地时区
- 并将其转换为项目设置的时区。
【三】使用场景
- 项目的需求仅涉及本地时间,没有跨时区的操作需求。
- 数据库中存储的日期时间字段已经是处于项目所在的时区。
- 开发人员希望简化日期时间处理并避免时区转换的复杂性。
【四】条件
- 如果项目需要处理跨时区操作,例如在多个时区之间进行时间转换或在不同时区中显示日期时间等,应将
USE_TZ
设置为True
。 - 如果数据库中存储的日期时间字段是使用UTC(协调世界时)保存的,并且项目需要在不同时区之间处理日期时间,也应将
USE_TZ
设置为True
。
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/17571588.html