7. 主页
7.1 路由层
url(r'^home/', views.home),
7.2 视图层
def home(request):
return render(request, 'home.html')
7.3 主页导航条
主页站点东西太多先一步步的完成
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
{% load static %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<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>
<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">
{% 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>
</div>
</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">
{% 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="/logout/">退出登入</a></li>
</ul>
</li>
{% else %}
<li><a href="/register/">注册</a></li>
<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" 模态框页尾
模态框只能通过关闭按钮收起.
<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">
<form action="" id="model_form">
{% 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>
<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 模态框&表单样式
#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>
$('#btn_model').on('click', function () {
let ModelForm = $('#model_form').serializeArray()
let Model_obj = new Object()
$.each(ModelForm, function (index, obj) {
Model_obj[obj.name] = obj.value
})
$.ajax({
url: '/set_password/',
type: 'post',
data: Model_obj,
success: function (args) {
if (args.code === 200) {
window.location.href = args.url
} else {
swal({
title: args.error_msg,
type: 'error',
customClass: 'sweetAlert',
timer: 2000,
showConfirmButton: false,
})
$('body').css("pointer-events", "none")
setTimeout(function () {
$('body').css("pointer-events", "")
}, 3000);
}
}
})
})
</script>
9.5 设置密码路由层
表单数据向set_password提交.
url(r'^set_password', views.set_password),
9.6 修改密码视图函数
先对新密码的两次输入进行校验, 如果不一致返回错误提示信息.
再操作数据库对旧密码进行比对, 如果真正把新输入的密码加密写入数据库, 清除session中的数据跳转到登入页面.
旧密码错误放回错误信息.
@login_required
def set_password(request):
if request.is_ajax():
post_obj = request.POST
new_password = post_obj.get('new_password')
confirm_password = post_obj.get('confirm_password')
print(new_password, confirm_password)
if new_password == confirm_password:
old_password = post_obj.get('old_password')
is_right = request.user.check_password(old_password)
if is_right:
request.user.set_password(new_password)
request.user.save()
request.session.flush()
back_dict = {'code': 200, 'url': '/login/'}
else:
back_dict = {'code': 400, 'error_msg': '旧密码输入错误!'}
else:
back_dict = {'code': 400, 'error_msg': '确认密码不一致'}
print(back_dict)
return JsonResponse(back_dict)
else:
return HttpResponse('非法访问!')
错误提示使用二次弹框展示.
正常修改密码后之间跳转到登入页面
10. 退出登入
点击之后清除session中的数据, 跳转到登入界面.
10.1 路由层
url(r'^logout/', views.logout)
10.2 视图层
@login_required
def logout(request):
auth.logout(request)
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
from app01 import models
admin.site.register(models.UserInfo)
刷新后台管理的页面, 展示注册的表, 表名后面默认都是加s的.
(表的名称有点小差异但是数据是对的)
可视化操作界面
点击User表, 查看表中的数据.
在映射表的类中添加 自定义admin中展示的表名 代码
class Meta:
verbose_name_plural = '用户表'
注册所有的表
from django.contrib import admin
from app01 import models
admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Article)
admin.site.register(models.ArticleToTag)
admin.site.register(models.Tag)
admin.site.register(models.Sort)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
为所有的表设置自定义名称
class Meta:
verbose_name_plural = 'UserInfo(用户表)'
class Meta:
verbose_name_plural = 'Blog(个人站点表)'
class Meta:
verbose_name_plural = 'ArticleToTag(文章表多对多标签表的第三张表)'
class Meta:
verbose_name_plural = 'Tag(标签表)'
class Meta:
verbose_name_plural = 'Sort(分类表)'
class Meta:
verbose_name_plural = 'UpAndDown(点赞点踩表)'
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对象的时候, 返回站点名称.
def __str__(self):
return self.site_name
在创建一个 个人站点的数据
创建分类 --> 分类的外键绑定 个人站点
创建四个分类:
分类名称 绑定的站点
kid的第一个分类 kid
kid的第二个分类 kid
qq的第一个分类 qq
qq的第二个分类 qq
展示的也外键id对应的数据对象
映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回分类的类名.
def __str__(self):
return self.name
添加文章 --> 绑定站点 设置分类
kid 站点第一个分类的文章
kid 站点第二个分类的文章
qq 站点第一个分类的文章
qq 站点第二个分类的文章
添加了四片文章, 打印的数据外键绑定的主键对应的数据对象
映射类中添加 __str__ 方法. 在打印query_set对象的时候, 返回文章的标题.
def __str__(self):
return self.title
2. 用表添加数据
用户表绑定站点
在点保存的时候会报错
手机号码字段中:
null=True 设置的是 数据库该字段可以为空
在admin后台中操作就不允许为空了, 需要添加
blank=True 设置的是 admin后台中操作可以为空
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对象的时候, 标签的名称.
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__ 方法.
def __str__(self):
return f'{self.article} -- {self.tag}'
12. 上传文件路径
12.1 配置路径
网站所使用的静态文件都默认存放在static文件中, 然后在配置文件中开发路径.
用户上传的静态文件一般存放在media文件夹中.
在settings.py 中配置 MEDIA_ROOT = os.path(BASE_DIR, '目录')
改配置可以让用户上传的所有文件都固定到某一个指定的文件夹中,
名字可以自定义, 一般使用 media.
该目录在用户上传文件的时候在项目目录下自动创建.
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
在这里插入图片描述
头像字段中设置了存头像的路径, 该路径会在media目录中创建.
将avatar中的头像文件之间复制到media下的avatar中, 将原来的avatar目录删除.
12.2 开发静态文件访问
在网页地址栏中直接输入url访问对应的静态文件.
from django.views.static import serve
from BBS import settings
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 开放源码
MEDIA_ROOT = os.path.join(BASE_DIR, 'app01')
from django.views.static import serve
from BBS import settings
url(r'^app01/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
访问源代码, 这是一个危险的操作.
13. 主页布局
2 8 2 布局
在左右两边菜单栏添加 六个标题表单.
<div class="container-fluid">
<div class="row">
<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 class="col-md-8"></div>
<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):
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
else:
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
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 = []
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 分页获取文章
获取所有文章数据后 使用分页器进行分页处理.
def home(request):
from app01.utils.pages import Pagination
all_queryset_obj = models.Article.objects.all()
all_queryset_sum = all_queryset_obj.count()
page_num = request.GET.get('page', 1)
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
"""
page_queryset = all_queryset_obj[page_obj.start: page_obj.end]
return render(request, 'home.html', locals())
14.3 媒体对象展示文章
<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 路由层
以站点的名称访问对应的站点.
url(r'(?P<site_name>\w+)', views.get_site, name='site_name')
15.2 视图层
@login_required
def get_site(request, site_name):
blog_obj = models.Blog.objects.filter(site_name=site_name)
if blog_obj:
article_query_set = models.Article.objects.filter(blog=blog_obj).all()
return render(request, 'site.html', locals())
else:
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>
{% load static %}
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'bootstrap-sweetalert-master/dist/sweetalert.css' %}">
<script src="{% static 'bootstrap-sweetalert-master/dist/sweetalert.min.js' %}"></script>
<link rel="stylesheet" href="/media/css/{{ blog_obj.site_theme }}/">
</head>
<body>
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<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="#">{{ request.user.username }}</a>
</div>
<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">
{% 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="/logout/">退出登入</a></li>
</ul>
</li>
{% else %}
<li><a href="/register/">注册</a></li>
<li><a href="/login/">登入</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
</body>
</html>
15.6 布局
3 - 9
左侧展示三个标题表单 右边展示文章
<div class="container-fluid">
<div class="row">
<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>
<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 文件
a {
color: #aaff00;
}
a {
color: #dd5579;
}
进入站点, 前端获取用户的css样式文件地址.
print(blog_obj.site_theme)
<link rel="stylesheet" href="/media/css/{{ blog_obj.site_theme }}/">
16. 个人站点左侧栏
个人站点左侧栏的三个表单中分类按 分类, 标签, 时间, 进行归档.
1. 先获取当前用户的所有文章.
2. 文章按 分类, 标签, 时间使用ORM语句镜像分组并统计.
blog_obj = models.Blog.objects.filter(site_name=site_name).first()
if blog_obj:
article_query_set = models.Article.objects.filter(blog=blog_obj)
在视图函数的顶部导入聚合函数.
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完整归档代码
@login_required
def get_site(request, site_name, **kwargs):
blog_obj = models.Blog.objects.filter(site_name=site_name).first()
if blog_obj:
article_query_set = models.Article.objects.filter(blog=blog_obj)
sort_list = article_query_set.annotate(
count_num=Count('sort__id')).values('sort__pk', 'sort__name', 'count_num')
tag_list = article_query_set.annotate(
count_num=Count('tag__id')).values('tag__pk', 'tag__name', 'count_num')
year_month_list = article_query_set.annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values('month', 'count_num')
return render(request, 'site.html', locals())
else:
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 其中一个
第三个路径为 需要获取的数据依据
在视图函数中对用户的所有文章按照指定的归档信息进行二次过滤,
在路由中设置有名分组,分别接收: 站点的名称, 访问的归档类型, 该归档下某个类型.
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'}
if kwargs.get('condition') == 'sort':
article_query_set = article_query_set.filter(sort_id=kwargs.get('param'))
elif kwargs.get('condition') == 'tag':
article_query_set = article_query_set.filter(tag__id=kwargs.get('param'))
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)
@login_required
def get_site(request, site_name, **kwargs):
blog_obj = models.Blog.objects.filter(site_name=site_name).first()
if blog_obj:
article_query_set = models.Article.objects.filter(blog=blog_obj)
sort_list = article_query_set.annotate(
count_num=Count('sort__id')).values('sort__pk', 'sort__name', 'count_num')
tag_list = article_query_set.annotate(
count_num=Count('tag__id')).values('tag__pk', 'tag__name', 'count_num')
year_month_list = article_query_set.annotate(month=TruncMonth('create_time')).values(
'month').annotate(count_num=Count('pk')).values('month', 'count_num')
if kwargs:
if kwargs.get('condition') == 'sort':
article_query_set = article_query_set.filter(sort_id=kwargs.get('param'))
elif kwargs.get('condition') == 'tag':
article_query_set = article_query_set.filter(tag__id=kwargs.get('param'))
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)
return render(request, 'site.html', locals())
else:
return render(request, 'error.html')
|