模板

personalBlog采用典型的博客布局,左侧三分之二为主体,显示文章列表、正文;右侧三分之一为边栏,显示分为类列表、社交链接等。现在的工作是将HTML文件加工为模板,并创建对应的表单类,在模板中渲染。

并非所有的页面都需要添加边栏,所以我们不能把它放到基模板中。为了避免重复和易于维护,我们把边栏部分的代码放到了局部模板_sidebar.html中。除了基模板base.html和存储宏的macros.html模板,personalBlog程序的博客前台使用的模板如下所示:

index.html 主页;

about.html 关于页面;

_sidebar.html 边栏;

category.html 分类页面;

post.html 文章页面;

login.html 登录页面;

400.html;

404.html;

500.html;

personalBlog中将会用到400错误响应,表示无效请求,所以我们添加了对应的错误页面模板,在前面介绍工厂函数时我们已经编写了对应的错误处理函数。

博客后台使用的模板如下所示:

manager_category.html 分类管理页面;

new_category.html 新建分类页面;

edit_category.html 编辑分类页面;

manage_post.html 文章管理页面;

new_post.html 新建文章页面;

edit_post.html 编辑文章页面;

settings.html 博客设置页面;

manage_comment.html 评论管理页面。

这些模板根据类别分别放到了templates目录下的auth、admin、blog和errors子文件夹中,只有基模板在templates跟目录内。基模板中定义了程序的基本样式,包括导航栏和页脚,如下所示。

personalblog/templates/base.html: 基模板

<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<meta charset="utf-8">
<meta name="viepoint" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock title %} - PersonalBlog</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/%s.min.css' % request.cookies.get('theme', 'perfect_blue')) }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" type="text/css">
{% endblock head %}
</head>
<body>
{% block nav %}
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">PersonalBlog</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01"
aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button> <div class="collapse navbar-collapse" id="navbarColor01">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>a>
</li>
</ul>
</div>
</div>
</nav>
{% endblock nav %}
<main class="container">
{% block content %}{% endblock content %}
{% block footer %}
<footer>
</footer>
{% endblock footer %}
</main> {% 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='jsj/bootstrap.min.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/script.js') }}"></script>
{{ moment.include_moment(local_js=url_for('static', filename='js/moment-with-locales.min.js')) }}
{% endblock %}
</body>
</html>

除了基本的HTML结构,我们还在基模板中加载了Favicon、自定义CSS、JavaScript文件,以及Bootstrap、Moment.js所需的资源文件,并创建了一些块用于在字模板中继承。

Bootstrap默认的样式足够没关,但也许你已经感到厌倦了。Bootswatch(https://bootswatch.com/)以及StartBootstrap(https://startbootstrap.com/)等网站上提供了许多免费的Bootstrap主题文件,你可以为自己的程序选择一个。你需要下载对应的CSS文件,保存到static/css目录下,替换Bootstrap的CSS文件(bootstrap.min.css),清楚缓存并重新加载页面即可看到新的样式。

基模板中的一些代码我们会在下面详细介绍,其他模板的实现我们则会在实现具体的功能时介绍。

1、模板上下文

在基模板的导航栏以及博客主页中需要使用博客的标题、副标题等存储在管理员对象上的数据,为了避免在每个视图函数中渲染模板时传入这些数据,我们在模板上下文函数中像模板上下文添加了管理员对象变量(admin)。另外,在多个页面中都包含的边栏中包含分类列表,我们也把分类数据传入到模板上下文中,如下所示。

personalBlog/__init__.py: 处理模板上下文

 
def create_app(config_name = None):
if config_name is None:
config_name = os.getenv('FLASK_CONFIG', 'development') app = Flask('personalBlog')
app.config.from_object(config[config_name]) register_logging(app) # 注册日志处理器
register_extensions(app) # 注册扩展(扩展初始化)
register_blueprints(app) # 注册蓝本
register_commands(app) # 注册自定义shell命令
register_errors(app) # 注册错误处理函数
register_shell_context(app) # 注册错误处理函数
register_template_context(app) # 注册模板上下文处理函数 return app def register_template_context(app):
@app.context_processor
def make_template_context():
admin = Admin.query.first()
categories = Category.query.order_by(Category.name).all()
return dict(admin=admin, categories=categories)

获取分类记录时,我们使用order_by()对记录进行排序,传入的规则是分类模型的name字段,这会对分类按字母顺序排列。在边栏模板(_sidebar.html)中,我们迭代categories变量,渲染分类列表,如下所示:

personalBlog/templates/blog/_sidebar.html: 边栏局部模板

{% if categories %}
<div class="card mb-3">
<div class="card-header">Categories</div>
<ul class="list-group list-group-flush">
{% for category in categories %}
<li class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
<a href="{{ url_for('blog.show_category', category_id=category.id) }}">
{{ category.name }}
</a>
<span class="badge badge-primary badge-pill">{{ category.posts|length }}</span>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
除了分类的名称,我们还在每一个分类的右侧显示了与分类对应的文章总数,总数通过对分类对象的posts关系属性添加length过滤器获取。分类连接指向的blog.show_category视图我们将在后面介绍。
 
在基模板(base.html)和主页模板(index.html)中,我们可以直接使用传入的admin对象获取博客的标题和副标题。以主页模板为例:
personalBlog/templates/blog/index.html:
 
{% extends 'base.html' %}
{% from 'bootstrap/ pagination.html' import render_pager %} {% block title %}Home{% endblock %} {% block content %}
<div class="page-header">
<h1 class="display-3">{{ admin.blog_title|default('Blog Title') }}</h1>
<h4 class="text-muted">&nbsp;{{ admin.blog_sub_title|default('Blog Subtitle' }}</h4>
</div>
<div class="row">
<div class="col-sm-8">
{% include 'blog/_posts.html' %}
{% if posts %}
<div class="page-footer">{{ render_page(pagination }}</div>
{% endif %}
</div>
<div class="col-sm-4 sidebar">
{% include 'blog/_sidebar.html' %}
</div>
</div>
{% endblock %}
2、渲染导航链接

导航栏上的按钮应该再对应的页面显示激活状态。举例来说,当用户单击导航栏上的“关于”按钮打开关于页面时,“关于”按钮应该高亮显示。Bootstrap为导航链接提供了一个active类来显示激活状态,我们需要为当前页面对应的按钮添加active类。

这个功能可以通过判断请求的端点来实现,对request对象调用endpoint属性即可获得当前的请求端点。如果当前的端点与导航链接指向的端点相同,就为它添加active类,显示激活样式,如下所示:

<li {% if request.endpoint == 'blog.index' %}class="active"{% endif %}><a href="{{ url_for('blog.index') }}">Home</a>
</li>

有些教程中会使用endswith()方法来比较端点结尾。但是蓝本拥有独立的端点命名空间,即“<蓝本命>.<端点名>”,不同的端点可能会拥有相同的结尾,比如blog.index和auth.index,这时使用endswith()会导致判断错误,所以最妥善的做法是比较完整的端点值。

每个导航按钮的代码都基本相同,后面我们还会添加更多的导航链接。如果把这部分代码放到宏里,然后正在需要的地方根据指定的参数调用,就可以让模板更加整洁易读了。下面是用于渲染导航链接的nav_link()宏:

{% macro nav_link(endpoint, text) -%}
<li class="nav-item {% if request.endpoint
and request.endpoint == endpoint %}active{% endif %}">
<a class="nav-link" href="{{ url_for(endpoint, **kwargs) }}">{{ text }}</a>
</li>
{%- endmacro %}

nav_link()宏接收完整的端点值和按钮文本作为参数,返回完整的导航链接。因为错误页面没有端点值,当渲染错误页面的导航栏时,链接会出现request.endpoint为None的错误。为了避免这个错误,需要在nav_link()宏的if判断中额外添加一个判断条件,确保端点不为None。

借助nav_link宏,渲染导航链接的代码会变得非常简单:

{% from 'macros.html' import nav_link %}
...
<ul class="navbar-nav mr-auto">
{{ nav_link('index', 'Home') }}
{{ nav_link('about', 'About') }}
</ul>
...

不过在personalBlog的模板中我们并没有使用这个nav_link()宏,因为Bootstrap-Flask提供了一个更加完善的render_nav_item()宏,它的用法和我们创建的nav_link()宏基本相同。这个宏可以在模板中通过bootstrap/nav.html路径导入,它支持的常用参数如下所示:

3、Flash消息分类

我们目前的Flash消息应用了Bootstrap的alert-info样式,单一的样式使消息的类别和等级难以区分,更合适的做法是为不同类别的消息应用不同的样式。比如,当用户访问出错时显示一个黄色的警告消息;而不同的提示消息则使用蓝色的默认样式。bootstrap为提醒消息(Alert)提供了8种基本的样式类,即alert-primary、alert-secondary、alert-success、alert-danger、alert-warning、alert-light、alert-dark,如下所示:

要开启消息分类,我们首先要在消息渲染函数get_flashed_messages()中将with_categories参数设为True。这时会把消息迭代为一个类似于“(分类,消息)”的元祖,我们使用消息分类字符来构建样式类,如下所示:

personalBlog/templates/base.html: 渲染分类消息

<main class="container">
{% for message in get_flashed_messages(with_categories=True) %}
<div class="alert alert-{{ message[0] }}" role="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{ message[1] }}
</div>
{% endfor %} {% block content %}{% endblock content %}
{% block footer %}
<footer>
</footer>
{% endblock footer %}
</main>

样式类通过“alert-{{ message[0] }}”形式构建,所以在调用flash()函数时,消息的类别作为第二个参数传入(primary、secondary、success、danger、warning、light、dark中的一个)。比如,下面的消息使用success分类,在渲染时会使用alert-success样式类:

flash(u'发表成功!', 'success')

如果你不想使用Bootstrap,或是想添加一个自定义分类,可以通过在css文件中添加新的消息样式的css类实现。比如下面的CSS类实现了一个自定义消息样式类alert-matrix:

.alert-matrix{
color: #66ff66;
background-color: #000000;
border-color: #ebccd1;
}

在调用flash()函数时,则使用“matrix”作为分类:

flash(u'发表成功!', 'matrix')

flask实战-个人博客-模板 --的更多相关文章

  1. flask实战-个人博客-虚拟环境、项目结构

    个人博客 博客是典型的CMS(Content Management system,内容管理系统),通常由两部分组成:一部分是博客前台,用来展示开放给所有用户的博客内容:另一部分是博客后台,这部分内容仅 ...

  2. flask实战-个人博客-使用蓝本模块化程序

    使用蓝本模块化程序 实例化flask提供的blueprint类就创建一个蓝本实例.像程序实例一样,我们可以为蓝本实例注册路由.错误处理函数.上下文处理函数,请求处理函数,甚至是单独的静态文件文件夹和模 ...

  3. flask实战-个人博客-编写博客前台

    编写博客前台 博客前台需要开放给所有用户,这里包括显示文章列表.博客信息.文章内容和评论等功能功能. 分页显示文章列表 为了在主页显示文章列表,我们要先在渲染主页模板的index视图的数据库中获取所有 ...

  4. flask实战-个人博客-电子邮件支持

    电子邮件支持 因为博客要支持评论,所以我们需要在文章有了新评论后发邮件通知管理员.而且,当管理员回复了读者的评论后,也需要发送邮件提醒读者. 为了方便读者使用示例程序,personalBlog中仍然使 ...

  5. flask实战-个人博客-表单

    表单 下面我们来编写所有表单类,personalBlog中主要包含下面这些表单: 登录表单: 文章表单: 评论表单: 博客设置表单: 这里仅介绍登录表单.文章表单.分类表单和评论表单,其他的表单在实现 ...

  6. flask实战-个人博客-数据库-生成虚拟数据 --

    3.生成虚拟数据 为了方便编写程序前台和后台功能,我们在创建数据库模型后就编写生成虚拟数据的函数. 1)管理员 用于生成虚拟管理员信息的fake_admin()函数如下所示: personalBlog ...

  7. flask实战-个人博客-程序骨架、创建数据库模型、临接列表关系 --

    编写程序骨架 personalBlog的功能主要分为三部分:博客前台.用户认证.博客后台,其中包含的功能点如下图所示: 数据库 personalBlog一共需要使用四张表,分别存储管理员(Admin) ...

  8. flask实战-个人博客-视图函数

    视图函数 在上面我们创建了所有必须的模型类.模板文件和表单类.经过程序规划和设计后,我们可以创建大部分视图函数.这些视图函数暂时没有实现具体功能,仅渲染对应的模板,或是重定向到其他视图.以blog蓝本 ...

  9. flask实战-个人博客-使用工厂函数创建程序实例 --

    使用工厂函数创建程序实例 使用蓝本还有一个重要的好处,那就是允许使用工厂函数来创建程序实例.在OOP(Object-Oriented Programming,面向对象编程)中,工厂(factory)是 ...

随机推荐

  1. [LeetCode] 104. Maximum Depth of Binary Tree 二叉树的最大深度

    Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the long ...

  2. nginx ubantu 安装步骤

    Ubuntu14.04默认安装的是Nginx 1.4.6 如果已经安装,请先卸载sudo apt-get remove nginx最新的稳定版Nginx 1.6.0在ubuntuupdates ppa ...

  3. java web开发入门十一(idea maven mybatis自动代码生成)基于intellig idea

    6.idea maven mybatis逆向工程(代码生成器) 1.配置pom.xml 在plugins标签下添加mybatis-generator-maven-plugin <plugin&g ...

  4. logback&log4j异步日志配置

    logback 原始配置 配置 appender, 控制文件的滚动方式,日志的输出格式. <appender name="method-time-appender" clas ...

  5. LeetCode题库整理(自学整理)

    1. Two Sum 两数之和       来源:力扣(LeetCode) 题目:给定一个整数数组和一个目标值,找出数组中和为目标值的两个数.你可以假设每个输入只对应一种答案,且同样的元素不能被重复利 ...

  6. shell 中 贪婪匹配 和 非贪婪匹配

    举个栗子: v=jfedu.shiyiwen.com echo ${v%.*} 一个%为非贪婪匹配,即匹配最短结果.%从右到左进行非贪婪匹配,匹配什么呢? 匹配已 .*的字符. 那么当然是匹配到了.c ...

  7. rust下获取本机IP

    又拾起了rust语言, 想写一点东西玩一玩, 但是发现连一个获取本机IP地址的库都没有, 还得挽起袖子自己撸. https://crates.io/crates/local_ipaddress 没有用 ...

  8. oracle批量新增更新数据

    本博客介绍一下Oracle批量新增数据和更新数据的sql写法,业务场景是这样的,往一张关联表里批量新增更新数据,然后,下面介绍一下批量新增和更新的写法: 批量新增数据 对于批量新增数据,介绍两种方法 ...

  9. Security实现登录安全控制

    1:在pom.xml中添加依赖 <!-- 身份验证 --> <dependency> <groupId>org.springframework.security&l ...

  10. 【C++】STL各容器的实现,时间复杂度,适用情况分析

    一.vector 1.概述 动态数组,在内存中具有连续的储存空间,在堆上分配内存,支持快速随机访问,在中间插入和删除慢,但在末尾插入和删除快 2.特点 1)拥有一段连续的内存空间,并且起始地址不变,因 ...