个人博客Demo: link.
GitHub项目完整链接:link
回顾上一节主要讲了以下个方面内容:
3.1.1 基模板 base.html
- 基模板定义了博客的基本样式,导航栏和页脚,其他页面都要继承自基模板
- 除了在基模板中定义基本html结构,我们还在基模板中加载css,javascript文件以及bootstrap,moment.js所需的资源文件,并创建一些块block用于子模板的继承,修改与追加
- 基模板中还涉及到用户登录和未登录时用current_user.is_authenticated显示不同的组件
- HTML代码中涉及许多bootstrap样式,可参考菜鸟教程的bootstrap4教程:link
- 先贴上代码,再进行进一步阐述:
{% from 'bootstrap/nav.html' import render_nav_item %}
<!DOCTYPE html>
<html lang="en">
<head>
{% block head%}
<meta charset="UTF-8">
<title>{% block title %}{% endblock title %} - {{ admin.blog_title|default('MyBlog')}}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css')}}" type="text/css">
<link rel="stylesheet" href="{{url_for('static', filename='css/style.css')}}" type="text/css">
{% endblock head%}
</head>
<body>
{% block nav%}
<div class="row">
<div class="col-sm-12">
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<div class=" container">
<a class="navbar-brand" href="{{url_for('blog.index')}}">{{ admin.blog_title|default('Blog') }}</a>
<ul class="nav navbar-nav navbar-right">
<li class="nav-item dropdown " >
<a href="#" class="nav-link dropdown-toggle "role="button"
data-toggle="dropdown" >文章分类<span class="caret"></span></a>
<div class="dropdown-menu " >
{% for category in categories%}
<a class="dropdown-item"
href="{{ url_for('blog.show_category',category_id=category.id)}}">{{ category.name }}</a>
{% endfor %}
</div>
</li>
{% if current_user.is_authenticated %}
<li class="nav-item dropdown ">
<a href="#" class="nav-link dropdown_toggle" data-toggle="dropdown" role="button">
新建<span class="caret"></span>
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{{ url_for('admin.new_post')}}">文章</a>
<a class="dropdown-item" href="{{ url_for('admin.new_category')}}">分类</a>
</div>
</li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button">
管理<span class="caret"></span>
{% if unread_comments %}
<span class="badge badge-success">new</span>
{% endif %}
</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{{ url_for('admin.manage_post')}}">文章</a>
<a class="dropdown-item" href="{{ url_for('admin.manage_category')}}">分类</a>
<a class="dropdown-item" href="{{ url_for('admin.manage_comments')}}">评论
{% if unread_comments %}
<span class="badge badge-success">{{ unread_comments }}</span>
{% endif %}
</a>
</div>
</li>
{{ render_nav_item('admin.settings', '设置')}}
{% endif %}
</ul>
</div>
</nav>
</div>
</div>
{% endblock nav %}
<main class="container">
{% for message in get_flashed_messages(with_categories=True) %}
<!--alert:信息提示框-->
<div class="alert alert-{{ message[0] }}" role="alert">
<!--设置消息提示框关闭按钮-->
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message[1] }}
</div>
{% endfor %}
{% block content %}
{% endblock content%}
</main>
{% block footer %}
<div class="row">
<div class="container">
<hr>
<a class="text-dark" href="#" title="created by HJ"><small>© 2019 HJ</small></a> -
<a class="text-dark" href="https://github.com/theminimize" title="HJ's GitHub count"><small>GitHub</small></a> -
<a class="text-dark" href="https://blog.csdn.net/one_Salted_FishGG" title="HJ's CSDN count"><small>CSDN</small></a>
{% if current_user.is_authenticated %}
<a class="text-dark float-right " href="{{url_for('login.logout', next=request.full_path)}}"><small>注销</small></a>
{% else %}
<a class=" text-dark float-right" href="{{url_for('login.login', next=request.full_path)}}"><small>登录</small></a>
{% endif %}
</div>
</div>
{% endblock footer %}
{% block scripts %}
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.2.1.slim.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/popper.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/script.js') }}"></script>
<!-- 引入Moment.js库,用于渲染时间 -->
{{ moment.include_moment(local_js=url_for('static', filename='js/moment-with-locales.min.js')) }}
{% endblock %}
</body>
</html>
- {% block head%}块里面主要定义网页标题{% block title %}和CSS资源位置,JS资源文件{% scripts %}独立在最下面的原因是方便追加新的js资源
- {% block nav %}定义导航栏代码,用户未登录或游客看到的导航栏包含博客标题以及文章分类下拉菜单。用户登录后在导航栏追加显示新建,管理和设置三个下拉菜单。设置下拉菜单采用bootstrap-flask提供的快速渲染样式render_nav_item进行快速渲染。
- {% block nav%}块下方< div >表示消息闪现位置
- {% block content %}块空白,方便子模板继承添加内容
- {% block footer %}页脚添加上友情链接和登录/注销按钮,登录注销按钮附加 next参数保存上衣页面完整链接,用于返回上一页
3.1.2 文章列表显示局部模板 _post.html
- 局部模板能够很方便的插入到其他模板之中,由于文章列表显示在博客主页以及文章分类页都能用到,因此将其独立成局部模板,减少代码重复
- 代码如下:
{% if posts %}
<ul class="list-group">
{% for post in posts %}
<li class="list-group-item border border-0" id="list-group-padding">
<h2><a href="{{ url_for('.show_post', post_id=post.id)}}" class="text-dark">{{ post.title }}</a></h2>
<p >{{ post.body|truncate|striptags }}
<a href="{{ url_for('.show_post', post_id=post.id)}}"><small>Read More</small></a>
</p>
<!--<p><small> {{ post.timestamp }}</small></p>-->
<p class="text-muted"><small>{{ moment(post.timestamp).format('LL') }}</small></p>
</li>
{% endfor %}
</ul>
{% else %}
<!-- 没有文章时,则tip提示无文章 -->
<div class="tip">
<h5>这里一篇文章也没有...</h5>
{% if current_user.is_authenticated %}
<a href="{{ url_for('admin.new_post') }}">马上写一篇...</a>
{% endif %}
</div>
{% endif %}
- {% if posts %}用于判断是否有文章,有则通过for循环迭代列表显示文章,没有则tip提示无文章
- 将文章标题和read more按钮都定向到文章详情页面,文章摘要通过{{ post.body| truncate| striptags}}实现,truncate用于裁剪字符串, striptags用于清除html标签,因为富文本编辑器ckeditor采用html标记文本
3.1.3 博客主页index和分类详情页category
- 博客主页和分类详情页都插入了局部模板_post.html因此放在一起,同时将与视图函数一起讲,代码都比较少
- index.html代码如下:
{% extends 'base.html' %}
{% from 'bootstrap/pagination.html' import render_pagination%}
{% block title %}主页{% endblock title%}
{% block content %}
<div class="row">
<div class="container ">
{% include 'blog/_post.html'%}
{% if posts %}
{{ render_pagination(pagination) }}
{% endif %}
</div>
</div>
{% endblock content%}
- index模板继承base.html基模板,{% block title %}更改标题,在{% content %}块内用{% include xxx%}插入局部模板
- 通过bootstrap提供的快熟渲染样式,渲染分页标签{{ render_pagination(pagination) }},渲染分页标签需要传入pagination分页对象,由于视图函数控制模板渲染(render_template),因此需要在视图函数中定义分页对象并传入模板
- bluemaps/blog.py下index视图函数代码如下:
from flask import Blueprint, request, current_app, render_template
from Blog.models import Post
blog_bm = Blueprint('blog', __name__)
@blog_bm.route('/')
def index():
page = request.args.get('page', 1, type=int) # 从查询字符串获取当前页数
per_page = current_app.config['BLOG_POST_PER_PAGE'] # 每页文章数
pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=per_page) # 分页对象
posts = pagination.items # 当前页数的记录列表
return render_template('blog/index.html', pagination=pagination, posts=posts)
- 为了实现分页,查询执行参数order_by()后加入pagiante()方法,它接收两个主要参数用来决定把记录分成几页(per_page)以及返回哪一页的记录(即查询字符串获取的当前页数参数page)
- 对分页对象pagination调用items属性会以列表形式返回对应页数的记录,然后在模板中for循环迭代显示列表中记录即可
- 分类文章页的视图函数和模板代码类似,不在赘述
- 分类文章页category.html的代码如下:
{% extends 'base.html' %}
{% from 'bootstrap/pagination.html' import render_pagination %}
{% block title %}{{ category.name }}{% endblock title %}
{% block content %}
<div class="page-header">
<h1>{{ category.name }} : {{ posts|length}} 篇文章</h1>
</div>
{% include 'blog/_post.html' %}
{{ render_pagination(pagination) }}
{% endblock content %}
- blog.py 分类文章页视图函数代码如下:
@blog_my.route('/category/<int:category_id>', methods=['GET', 'POST']) # <int:xxx>为url转换器,将id值转换为整型
def show_category(category_id):
category = Category.query.get_or_404(category_id)
page = request.args.get('page', 1, type=int)
per_page = current_app.config['MYBLOG_POST_PER_PAGE']
pagination = Post.query.with_parent(category).order_by(Post.timestamp.desc()).paginate(page, per_page=per_page)
posts = pagination.items
return render_template('blog/category.html', category=category, pagination=pagination, posts=posts)
- 视图函数需要传入指定分类id值(category_id)用于显示指定分类记录,获取方式为:模板中重定向到show_category视图时,传入的参数,如category_id = category.id,而模板中的category对象又是从哪来的,从for循环迭代对象categories中获取,categories又在工厂函数注册的模板上下文中定义
- 如基模板中导航栏块中for循环迭代categories:
- 以及__init__.py文件中对模板上下文的注册包含categories对象:
# 模板上下文
def register_template_context(app):
@app.context_processor
def make_template_context():
admin = Admin.query.first()
categories = Category.query.order_by(Category.name).all()
posts = Post.query.order_by(Post.timestamp).all()
if current_user.is_authenticated:
unread_comments = Comment.query.filter_by(reviewed=False).count()
else:
unread_comments = None
return dict(
admin=admin,
categories=categories,
posts=posts,
unread_comments=unread_comments
)
3.1.4 小结
- 视图函数与模板之间参数传递,渲染的先后关系需要弄清楚,HTML页面结构还需多联系
- 后面将继续介绍前台页面中文章详情页和登录页面的实现