网站建设开头,成都网站建设公司,暴富建站 网址,公司网页制作流程图一、入门系列#xff1a; Flask入门系列(一)–Hello World 项目开发中#xff0c;经常要写一些小系统来辅助#xff0c;比如监控系统#xff0c;配置系统等等。用传统的Java写#xff0c;太笨重了#xff0c;连PHP都嫌麻烦。一直在寻找一个轻量级的后台框架#xff0c;学…一、入门系列 Flask入门系列(一)–Hello World 项目开发中经常要写一些小系统来辅助比如监控系统配置系统等等。用传统的Java写太笨重了连PHP都嫌麻烦。一直在寻找一个轻量级的后台框架学习成本低维护简单。发现Flask后我立马被它的轻巧所吸引它充分发挥了Python语言的优雅和轻便连Django这样强大的框架在它面前都觉得繁琐。可以说简单就是美。这里我们不讨论到底哪个框架语言更好只是从简单这个角度出发Flask绝对是佼佼者。这一系列文章就会给大家展示Flask的轻巧之美。 系列文章 Flask入门系列(一)–Hello WorldFlask入门系列(二)–路由Flask入门系列(三)–模板Flask入门系列(四)–请求响应及会话Flask入门系列(五)–错误处理及消息闪现Flask入门系列(六)–数据库集成Hello World 程序员的经典学习方法从Hello World开始。不要忘了先安装python, pip然后运行”pip install Flask”环境就装好了。当然本人还是强烈建议使用virtualenv来安装环境。细节就不多说了让我们写个Hello World吧 1 2 3 4 5 6 7 8 9 from flask import Flask app Flask(__name__) app.route(/) def index(): return h1Hello World/h1 if __name__ __main__: app.run() 一个Web应用的代码就写完了对就是这么简单保存为”hello.py”打开控制台到该文件目录下运行 $ python hello.py 看到”* Running on http://127.0.0.1:5000/ (Press CTRLC to quit)”字样后就说明服务器启动完成。打开你的浏览器访问”http://127.0.0.1:5000/”一个硕大的”Hello World”映入眼帘:)。 简单解释下这段代码 首先引入了Flask包并创建一个Web应用的实例”app” 1 2 from flask import Flask app Flask(__name__) 这里给的实例名称就是这个python模块名。 定义路由规则 1 app.route(/) 这个函数级别的注解指明了当地址是根路径时就调用下面的函数。可以定义多个路由规则会在下篇文章里详细介绍。说的高大上些这里就是MVC中的Contoller。 处理请求 1 2 def index(): return h1Hello World/h1 当请求的地址符合路由规则时就会进入该函数。可以说这里是MVC的Model层。你可以在里面获取请求的request对象返回的内容就是response。本例中的response就是大标题”Hello World”。 启动Web服务器 1 2 if __name__ __main__: app.run() 当本文件为程序入口也就是用python命令直接执行本文件时就会通过”app.run()”启动Web服务器。如果不是程序入口那么该文件就是一个模块。Web服务器会默认监听本地的5000端口但不支持远程访问。如果你想支持远程需要在”run()”方法传入”host0.0.0.0″想改变监听端口的话传入”port端口号”你还可以设置调试模式。具体例子如下 1 2 if __name__ __main__: app.run(host0.0.0.0, port8888, debugTrue) 注意Flask自带的Web服务器主要还是给开发人员调试用的在生产环境中你最好是通过WSGI将Flask工程部署到类似Apache或Nginx的服务器上。 本例中的代码可以在这里下载。 Flask入门系列(二)–路由 上一篇中我们用Flask写了一个Hello World程序让大家领略到了Flask的简洁轻便。从这篇开始我们将对Flask框架的各功能作更详细的介绍我们首先从路由(Route)开始。 系列文章 Flask入门系列(一)–Hello WorldFlask入门系列(二)–路由Flask入门系列(三)–模板Flask入门系列(四)–请求响应及会话Flask入门系列(五)–错误处理及消息闪现Flask入门系列(六)–数据库集成路由 从Hello World中我们了解到URL的路由可以直接写在其要执行的函数上。有人会质疑这样不是把Model和Controller绑在一起了吗的确如果你想灵活的配置Model和Controller这样是不方便但是对于轻量级系统来说灵活配置意义不大反而写在一块更利于维护。Flask路由规则都是基于Werkzeug的路由模块的它还提供了很多强大的功能。 带参数的路由 让我们在上一篇Hello World的基础上加上下面的函数。并运行程序。 1 2 3 app.route(/hello/name) def hello(name): return Hello %s % name 当你在浏览器的地址栏中输入”http://localhost:5000/hello/man”你将在页面上看到”Hello man”的字样。URL路径中”/hello/”后面的参数被作为”hello()”函数的”name”参数传了进来。 你还可以在URL参数前添加转换器来转换参数类型我们再来加个函数 1 2 3 app.route(/user/int:user_id) def get_user(user_id): return User ID: %d % user_id 试下访问”http://localhost:5000/user/man”你会看到404错误。但是试下”http://localhost:5000/user/123″页面上就会有”User ID: 123″显示出来。参数类型转换器”int:”帮你控制好了传入参数的类型只能是整形。目前支持的参数类型转换器有 类型转换器 作用 缺省 字符型但不能有斜杠 int: 整型 float: 浮点型 path: 字符型可有斜杠 另外大家有没有注意到Flask自带的Web服务器支持热部署。当你修改好文件并保存后Web服务器自动部署完毕你无需重新运行程序。 多URL的路由 一个函数上可以设施多个URL路由规则 1 2 3 4 5 6 7 app.route(/) app.route(/hello) app.route(/hello/name) def hello(nameNone): if name is None: name World return Hello %s % name 这个例子接受三种URL规则”/”和”/hello”都不带参数函数参数”name”值将为空页面显示”Hello World””/hello/“带参数页面会显示参数”name”的值效果与上面第一个例子相同。 HTTP请求方法设置 HTTP请求方法常用的有Get, Post, Put, Delete。不熟悉的朋友们可以去度娘查下。Flask路由规则也可以设置请求方法。 1 2 3 4 5 6 7 8 from flask import request app.route(/login, methods[GET, POST]) def login(): if request.method POST: return This is a POST request else: return This is a GET request 当你请求地址”http://localhost:5000/login””GET”和”POST”请求会返回不同的内容其他请求方法则会返回405错误。有没有觉得用Flask来实现Restful风格很方便啊 URL构建方法 Flask提供了”url_for()”方法来快速获取及构建URL方法的第一个参数指向函数名加过”app.route”注解的函数后续的参数对应于要构建的URL变量。下面是几个例子 1 2 3 4 url_for(login) # 返回/login url_for(login, id1) # 将id作为URL参数返回/login?id1 url_for(hello, nameman) # 适配hello函数的name参数返回/hello/man url_for(static, filenamestyle.css) # 静态文件地址返回/static/style.css 静态文件位置 一个Web应用的静态文件包括了JS, CSS, 图片等Flask的风格是将所有静态文件放在”static”子目录下。并且在代码或模板下篇会介绍中使用”url_for(‘static’)”来获取静态文件目录。上小节中第四个的例子就是通过”url_for()”函数获取”static”目录下的指定文件。如果你想改变这个静态目录的位置你可以在创建应用时指定”static_folder”参数。 1 app Flask(__name__, static_folderfiles) 本例中的代码可以在这里下载。 Flask入门系列(三)–模板 在第一篇中我们讲到了Flask中的Controller和Model但是一个完整的MVC没有View怎么行前端代码如果都靠后台拼接而成就太麻烦了。本篇我们就介绍下Flask中的View即模板。 系列文章 Flask入门系列(一)–Hello WorldFlask入门系列(二)–路由Flask入门系列(三)–模板Flask入门系列(四)–请求响应及会话Flask入门系列(五)–错误处理及消息闪现Flask入门系列(六)–数据库集成模板 Flask的模板功能是基于Jinja2模板引擎实现的。让我们来实现一个例子吧。创建一个新的Flask运行文件你应该不会忘了怎么写吧代码如下 1 2 3 4 5 6 7 8 9 10 11 12 from flask import Flask from flask import render_template app Flask(__name__) app.route(/hello) app.route(/hello/name) def hello(nameNone): return render_template(hello.html, namename) if __name__ __main__: app.run(host0.0.0.0, debugTrue) 这段代码同上一篇的多URL路由的例子非常相似区别就是”hello()”函数并不是直接返回字符串而是调用了”render_template()”方法来渲染模板。方法的第一个参数”hello.html”指向你想渲染的模板名称第二个参数”name”是你要传到模板去的变量变量可以传多个。 那么这个模板”hello.html”在哪儿呢变量参数又该怎么用呢别急接下来我们创建模板文件。在当前目录下创建一个子目录”templates”注意一定要使用这个名字。然后在”templates”目录下创建文件”hello.html”内容如下 1 2 3 4 5 6 7 !doctype html titleHello Sample/title {% if name %} h1Hello {{ name }}!/h1 {% else %} h1Hello World!/h1 {% endif %} 这段代码是不是很像HTML接触过其他模板引擎的朋友们肯定立马秒懂了这段代码。它就是一个HTML模板根据”name”变量的值显示不同的内容。变量或表达式由”{{ }}”修饰而控制语句由”{% %}”修饰其他的代码就是我们常见的HTML。 让我们打开浏览器输入”http://localhost:5000/hello/man”页面上即显示大标题”Hello man!”。我们再看下页面源代码 1 2 3 4 !doctype html titleHello from Flask/title h1Hello man!/h1 果然模板代码进入了”Hello {{ name }}!”分支而且变量”{{ name }}”被替换为了”man”。Jinja2的模板引擎还有更多强大的功能包括for循环过滤器等。模板里也可以直接访问内置对象如request, session等。对于Jinja2的细节感兴趣的朋友们可以自己去查查。 模板继承 一般我们的网站虽然页面多但是很多部分是重用的比如页首页脚导航栏之类的。对于每个页面都要写这些代码很麻烦。Flask的Jinja2模板支持模板继承功能省去了这些重复代码。让我们基于上面的例子在”templates”目录下创建一个名为”layout.html”的模板 1 2 3 4 5 6 7 !doctype html titleHello Sample/title link relstylesheet typetext/css href{{ url_for(static, filenamestyle.css) }} div classpage {% block body %} {% endblock %} /div 再修改之前的”hello.html”把原来的代码定义在”block body”中并在代码一开始”继承”上面的”layout.html” 1 2 3 4 5 6 7 8 {% extends layout.html %} {% block body %} {% if name %} h1Hello {{ name }}!/h1 {% else %} h1Hello World!/h1 {% endif %} {% endblock %} 打开浏览器再看下”http://localhost:5000/hello/man”页面的源码。 1 2 3 4 5 6 7 8 !doctype html titleHello Sample/title link relstylesheet typetext/css href/static/style.css div classpage h1Hello man!/h1 /div 你会发现虽然”render_template()”加载了”hello.html”模板但是”layout.html”的内容也一起被加载了。而且”hello.html”中的内容被放置在”layout.html”中”{% block body %}”的位置上。形象的说就是”hello.html”继承了”layout.html”。 HTML自动转义 我们看下下面的代码 1 2 3 app.route(/) def index(): return divHello %s/div % emFlask/em 打开页面你会看到”Hello Flask”字样而且”Flask”是斜体的因为我们加了”em”标签。但有时我们并不想让这些HTML标签自动转义特别是传递表单参数时很容易导致HTML注入的漏洞。我们把上面的代码改下引入”Markup”类 1 2 3 4 5 6 7 from flask import Flask, Markup app Flask(__name__) app.route(/) def index(): return Markup(divHello %s/div) % emFlask/em 再次打开页面”em”标签显示在页面上了。Markup还有很多方法比如”escape()”呈现HTML标签, “striptags()”去除HTML标签。这里就不一一列举了。 我们会在Flask进阶系列里对模板功能作更详细的介绍。 本文中的示例代码可以在这里下载。 Flask入门系列(四)–请求响应及会话 一个完整的HTTP请求包括了客户端的请求Request服务器端的响应Response会话Session等。一个基本的Web框架一定会提供内建的对象来访问这些信息Flask当然也不例外。我们来看看在Flask中该怎么使用这些内建对象。 系列文章 Flask入门系列(一)–Hello WorldFlask入门系列(二)–路由Flask入门系列(三)–模板Flask入门系列(四)–请求响应及会话Flask入门系列(五)–错误处理及消息闪现Flask入门系列(六)–数据库集成Flask内建对象 Flask提供的内建对象常用的有request, session, g通过request你还可以获取cookie对象。这些对象不但可以在请求函数中使用在模板中也可以使用。 请求对象request 引入flask包中的request对象就可以直接在请求函数中直接使用该对象了。让我们改进下第二篇中的login方法 1 2 3 4 5 6 7 8 9 10 11 from flask import request app.route(/login, methods[POST, GET]) def login(): if request.method POST: if request.form[user] admin: return Admin login successfully! else: return No such user! title request.args.get(title, Default) return render_template(login.html, titletitle) 在第三篇的templates目录下添加”login.html”文件 1 2 3 4 5 6 7 {% extends layout.html %} {% block body %} form namelogin action/login methodpost Hello {{ title }}, please login by: input typetext nameuser / /form {% endblock %} 执行上面的例子结果我就不多描述了。简单解释下request中”method”变量可以获取当前请求的方法即”GET”, “POST”, “DELETE”, “PUT”等”form”变量是一个字典可以获取Post请求表单中的内容在上例中如果提交的表单中不存在”user”项则会返回一个”KeyError”你可以不捕获页面会返回400错误想避免抛出这”KeyError”你可以用request.form.get(“user”)来替代。而”request.args.get()”方法则可以获取Get请求URL中的参数该函数的第二个参数是默认值当URL参数不存在时则返回默认值。request的详细使用可参阅Flask的官方API文档。 会话对象session 会话可以用来保存当前请求的一些状态以便于在请求之前共享信息。我们将上面的python代码改动下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from flask import request, session app.route(/login, methods[POST, GET]) def login(): if request.method POST: if request.form[user] admin: session[user] request.form[user] return Admin login successfully! else: return No such user! if user in session: return Hello %s! % session[user] else: title request.args.get(title, Default) return render_template(login.html, titletitle) app.secret_key 123456 你可以看到”admin”登陆成功后再打开”login”页面就不会出现表单了。session对象的操作就跟一个字典一样。特别提醒使用session时一定要设置一个密钥”app.secret_key”如上例。不然你会得到一个运行时错误内容大致是”RuntimeError: the session is unavailable because no secret key was set”。密钥要尽量复杂最好使用一个随机数这样不会有重复上面的例子不是一个好密钥。 我们顺便写个登出的方法估计我不放例子大家也都猜到怎么写就是清除字典里的键值 1 2 3 4 5 6 from flask import request, session, redirect, url_for app.route(/logout) def logout(): session.pop(user, None) return redirect(url_for(login)) 关于”redirect”方法我们会在下一篇介绍。 构建响应 在之前的例子中请求的响应我们都是直接返回字符串内容或者通过模板来构建响应内容然后返回。其实我们也可以先构建响应对象设置一些参数比如响应头后再将其返回。修改下上例中的Get请求部分 1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import request, session, make_response app.route(/login, methods[POST, GET]) def login(): if request.method POST: ... if user in session: ... else: title request.args.get(title, Default) response make_response(render_template(login.html, titletitle), 200) response.headers[key] value return response 打开浏览器调试在Get请求用户未登录状态下你会看到响应头中有一个”key”项。”make_response”方法就是用来构建response对象的第二个参数代表响应状态码缺省就是200。response对象的详细使用可参阅Flask的官方API文档。 Cookie的使用 提到了Session当然也要介绍Cookie喽毕竟没有CookieSession就根本没法用不知道为什么查查去。Flask中使用Cookie也很简单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from flask import request, session, make_response import time app.route(/login, methods[POST, GET]) def login(): response None if request.method POST: if request.form[user] admin: session[user] request.form[user] response make_response(Admin login successfully!) response.set_cookie(login_time, time.strftime(%Y-%m-%d %H:%M:%S)) ... else: if user in session: login_time request.cookies.get(login_time) response make_response(Hello %s, you logged in on %s % (session[user], login_time)) ... return response 例子越来越长了这次我们引入了”time”模块来获取当前系统时间。我们在返回响应时通过”response.set_cookie()”函数来设置Cookie项之后这个项值会被保存在浏览器中。这个函数的第三个参数max_age可以设置该Cookie项的有效期单位是秒不设的话在浏览器关闭后该Cookie项即失效。 在请求中”request.cookies”对象就是一个保存了浏览器Cookie的字典使用其”get()”函数就可以获取相应的键值。 全局对象g “flask.g”是Flask一个全局对象这里有点容易让人误解其实”g”的作用范围就在一个请求也就是一个线程里它不能在多个请求间共享。你可以在”g”对象里保存任何你想保存的内容。一个最常用的例子就是在进入请求前保存数据库连接。这个我们会在介绍数据库集成时讲到。 本例中的代码可以在这里下载。 Flask入门系列(五)–错误处理及消息闪现 本篇将补充一些Flask的基本功能包括错误处理URL重定向日志功能还有一个很有趣的消息闪现功能。 系列文章 Flask入门系列(一)–Hello WorldFlask入门系列(二)–路由Flask入门系列(三)–模板Flask入门系列(四)–请求响应及会话Flask入门系列(五)–错误处理及消息闪现Flask入门系列(六)–数据库集成错误处理 使用”abort()”函数可以直接退出请求返回错误代码 1 2 3 4 5 from flask import abort app.route(/error) def error(): abort(404) 上例会显示浏览器的404错误页面。有时候我们想要在遇到特定错误代码时做些事情或者重写错误页面可以用下面的方法 1 2 3 app.errorhandler(404) def page_not_found(error): return render_template(404.html), 404 此时当再次遇到404错误时即会调用”page_not_found()”函数其返回”404.html”的模板页。第二个参数代表错误代码。 不过在实际开发过程中我们并不会经常使用”abort()”来退出常用的错误处理方法一般都是异常的抛出或捕获。装饰器”app.errorhandler()”除了可以注册错误代码外还可以注册指定的异常类型。让我们来自定义一个异常 1 2 3 4 5 6 7 8 9 10 11 12 13 class InvalidUsage(Exception): status_code 400 def __init__(self, message, status_code400): Exception.__init__(self) self.message message self.status_code status_code app.errorhandler(InvalidUsage) def invalid_usage(error): response make_response(error.message) response.status_code error.status_code return response 我们在上面的代码中定义了一个异常”InvalidUsage”同时我们通过装饰器”app.errorhandler()”修饰了函数”invalid_usage()”装饰器中注册了我们刚定义的异常类。这也就意味着一但遇到”InvalidUsage”异常被抛出这个”invalid_usage()”函数就会被调用。写个路由试一试吧。 1 2 3 app.route(/exception) def exception(): raise InvalidUsage(No privilege to access the resource, status_code403) URL重定向 重定向”redirect()”函数的使用在上一篇logout的例子中已有出现。作用就是当客户端浏览某个网址时将其导向到另一个网址。常见的例子比如用户在未登录时浏览某个需授权的页面我们将其重定向到登录页要求其登录先。 1 2 3 4 5 6 7 8 from flask import session, redirect app.route(/) def index(): if user in session: return Hello %s! % session[user] else: return redirect(url_for(login), 302) “redirect()”的第二个参数时HTTP状态码可取的值有301, 302, 303, 305和307默认即302为什么没有304留给大家去思考。 日志 提到错误处理那一定要说到日志。Flask提供logger对象其是一个标准的Python Logger类。修改上例中的”exception()”函数 1 2 3 4 5 app.route(/exception) def exception(): app.logger.debug(Enter exception method) app.logger.error(403 error happened) raise InvalidUsage(No privilege to access the resource, status_code403) 执行后你会在控制台看到日志信息。在debug模式下日志会默认输出到标准错误stderr中。你可以添加FileHandler来使其输出到日志文件中去也可以修改日志的记录格式下面演示一个简单的日志配置代码 import logging from logging.handlers import TimedRotatingFileHandler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 server_log TimedRotatingFileHandler(server.log,D) server_log.setLevel(logging.DEBUG) server_log.setFormatter(logging.Formatter( %(asctime)s %(levelname)s: %(message)s )) error_log TimedRotatingFileHandler(error.log, D) error_log.setLevel(logging.ERROR) error_log.setFormatter(logging.Formatter( %(asctime)s: %(message)s [in %(pathname)s:%(lineno)d] )) app.logger.addHandler(server_log) app.logger.addHandler(error_log) 上例中我们在本地目录下创建了两个日志文件分别是”server.log”记录所有级别日志”error.log”只记录错误日志。我们分别给两个文件不同的内容格式。另外我们使用了”TimedRotatingFileHandler”并给了参数”D”这样日志每天会创建一个新的文件并将旧文件加日期后缀来归档。 注执行后会生成server.log和error.log俩文件访问正常或错误页面这俩均无内容不知道是本身相关代码有问题还是访问方式有问题 你还可以将错误信息发送邮件。更详细的日志使用可参阅Python logging官方文档。 消息闪现 “Flask Message”是一个很有意思的功能一般一个操作完成后我们都希望在页面上闪出一个消息告诉用户操作的结果。用户看完后这个消息就不复存在了。Flask提供的”flash”功能就是为了这个。我们还是拿用户登录来举例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from flask import render_template, request, session, url_for, redirect, flash app.route(/) def index(): if user in session: return render_template(hello.html, namesession[user]) else: return redirect(url_for(login), 302) app.route(/login, methods[POST, GET]) def login(): if request.method POST: session[user] request.form[user] flash(Login successfully!) return redirect(url_for(index)) else: return form namelogin action/login methodpost Username: input typetext nameuser / /form 上例中当用户登录成功后就用”flash()”函数闪出一个消息。让我们找回第三篇中的模板代码在”layout.html”加上消息显示的部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 !doctype html titleHello Sample/title link relstylesheet typetext/css href{{ url_for(static, filenamestyle.css) }} {% with messages get_flashed_messages() %} {% if messages %} ul classflash {% for message in messages %} li{{ message }}/li {% endfor %} /ul {% endif %} {% endwith %} div classpage {% block body %} {% endblock %} /div 上例中”get_flashed_messages()”函数就会获取我们在”login()”中通过”flash()”闪出的消息。从代码中我们可以看出闪出的消息可以有多个。模板”hello.html”不用改。运行下试试。登录成功后是不是出现了一条”Login successfully”文字再刷新下页面你会发现文字消失了。你可以通过CSS来控制这个消息的显示方式。 “flash()”方法的第二个参数是消息类型可选择的有”message”, “info”, “warning”, “error”。你可以在获取消息时同时获取消息类型还可以过滤特定的消息类型。只需设置”get_flashed_messages()”方法的”with_categories”和”category_filter”参数即可。比如Python部分可改为 1 2 3 4 5 6 7 8 app.route(/login, methods[POST, GET]) def login(): if request.method POST: session[user] request.form[user] flash(Login successfully!, message) flash(Login as user: %s. % request.form[user], info) return redirect(url_for(index)) ... layout模板部分可改为 1 2 3 4 5 6 7 8 9 10 11 ... {% with messages get_flashed_messages(with_categoriestrue, category_filter[message,error]) %} {% if messages %} ul classflash {% for category, message in messages %} li class{{ category }}{{ category }}: {{ message }}/li {% endfor %} /ul {% endif %} {% endwith %} ... 运行结果大家就自己试试吧。 本例中的代码可以在这里下载。 Flask入门系列(六)–数据库集成 转眼我们要进入本系列的最后一篇了。一个基本的Web应用功能其实已经讲完了现在就让我们引入数据库。简单起见我们就使用SQLite3作为例子。 系列文章 Flask入门系列(一)–Hello WorldFlask入门系列(二)–路由Flask入门系列(三)–模板Flask入门系列(四)–请求响应及会话Flask入门系列(五)–错误处理及消息闪现Flask入门系列(六)–数据库集成集成数据库 既然前几篇都用用户登录作为例子我们这篇就继续讲登录只是登录的信息会由数据库来验证。让我们先准备SQLite环境吧。 初始化数据库 怎么安装SQLite这里就不说了。我们先写个数据库表的初始化SQL保存在”init.sql”文件中 1 2 3 4 5 6 7 8 9 drop table if exists users; create table users ( id integer primary key autoincrement, name text not null, password text not null ); insert into users (name, password) values (visit, 111); insert into users (name, password) values (admin, 123); 运行sqlite3命令初始化数据库。我们的数据库文件就放在”db”子目录下的”user.db”文件中。 $ sqlite3 db/user.db init.sql 配置连接参数 创建配置文件config.py保存配置信息 1 2 3 4 #coding:utf8 DATABASE db/user.db # 数据库文件位置 DEBUG True # 调试模式 SECRET_KEY secret_key_1 # 会话密钥 在创建Flask应用时导入配置信息 1 2 3 4 5 from flask import Flask import config app Flask(__name__) app.config.from_object(config) 这里也可以用app.config.from_envvar(FLASK_SETTINGS, silentTrue)方法来导入配置信息此时程序会读取系统环境变量中FLASK_SETTINGS的值来获取配置文件路径并加载此文件。如果文件不存在该语句返回False。参数silentTrue表示忽略错误。 建立和释放数据库连接 这里要用到请求的上下文装饰器我们会在进阶系列的第一篇里详细介绍上下文。 1 2 3 4 5 6 7 8 9 app.before_request def before_request(): g.db sqlite3.connect(app.config[DATABASE]) app.teardown_request def teardown_request(exception): db getattr(g, db, None) if db is not None: db.close() 我们在before_request()里建立数据库连接它会在每次请求开始时被调用并在teardown_request()关闭它它会在每次请求关闭前被调用。 查询数据库 让我们取回上一篇登录部分的代码index()和logout()请求不用修改在login()请求中我们会查询数据库验证客户端输入的用户名和密码是否存在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 app.route(/login, methods[POST, GET]) def login(): if request.method POST: name request.form[user] passwd request.form[passwd] cursor g.db.execute(select * from users where name? and password?, [name, passwd]) if cursor.fetchone() is not None: session[user] name flash(Login successfully!) return redirect(url_for(index)) else: flash(No such user!, error) return redirect(url_for(login)) else: return render_template(login.html) 模板中加上login.html文件 1 2 3 4 5 6 7 8 {% extends layout.html %} {% block body %} form namelogin action/login methodpost Username: input typetext nameuser /br Password: input typepassword namepasswd /br input typesubmit valueSubmit / /form {% endblock %} 终于一个真正的登录验证写完了前几篇都是假的打开浏览器登录下吧。因为比较懒就不写CSS美化了受不了这粗糙界面的朋友们就自己调吧。 到目前为止Flask的基础功能已经介绍完了是否很想动手写个应用啦其实Flask还有更强大的高级功能之后会在进阶系列里介绍。 本例中的代码可以在这里下载。 二、模板引擎详解 Flask中Jinja2模板引擎详解(一)–控制语句和表达式 让我们开启Jinja2模板引擎之旅虽说标题是Flask中的Jinja2其实介绍的主要是Jinja2本身Flask是用来做例子的。如果对Flask不熟悉的朋友们建议将本博客的入门系列先看下。怎么不知道什么是模板引擎你可以将模板比作MVC模式中的View视图层而模板引擎就是用来将模板同业务代码分离并解析模板语言的程序。你可以耐心地看下本系列文章就能体会到什么是模板引擎了。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展回顾 我们在Flask入门系列第三篇中已经介绍了Jinja2模板的基本使用方式让我们先回顾下把其中的代码拿过来。 Flask Python代码 1 2 3 4 5 6 7 8 9 10 11 from flask import Flask,render_template app Flask(__name__) app.route(/hello) app.route(/hello/name) def hello(nameNone): return render_template(hello.html, namename) if __name__ __main__: app.run(host0.0.0.0, debugTrue) 模板代码 1 2 3 4 5 6 7 !doctype html titleHello Sample/title {% if name %} h1Hello {{ name }}!/h1 {% else %} h1Hello World!/h1 {% endif %} 我们了解到模板的表达式都是包含在分隔符”{{ }}”内的控制语句都是包含在分隔符”{% %}”内的另外模板也支持注释都是包含在分隔符”{# #}”内支持块注释。 表达式 表达式一般有这么几种 最常用的是变量由Flask渲染模板时传过来比如上例中的”name”也可以是任意一种Python基础类型比如字符串{{ “Hello” }}用引号括起或者数值列表元祖字典布尔值。直接显示基础类型没啥意义一般配合其他表达式一起用运算。包括算数运算如{{ 2 3 }}比较运算如{{ 2 1 }}逻辑运算如{{ False and True }}过滤器“|”和测试器“is”。这个在后面会介绍函数调用如{{ current_time() }}数组下标操作如{{ arr[1] }}“in”操作符如{{ 1 in [1,2,3] }}字符串连接符”~”作用同Python中的””一样如{{ “Hello ” ~ name ~ “!” }}“if”关键字如{{ ‘Hi, %s’ % name if name }}。这里的”if”不是条件控制语句。有没有觉得这里的表达式很像Python的语法呀 控制语句 Jinja2的控制语句主要就是条件控制语句if和循环控制语句for语法类似于Python。我们可以改动下上节的模板代码 1 2 3 4 5 6 7 {% if name and name admin %} h1This is admin console/h1 {% elif name %} h1Welcome {{ name }}!/h1 {% else %} h1Please login/h1 {% endif %} 上面是一个条件控制语句的例子注意if控制语句要用”{% endif %}”来结束。模板中无法像代码中一样靠缩进来判断代码块的结束。再来看个循环的例子我们先改下Python代码中的”hello”函数让其传两个列表进模板。 1 2 3 4 5 6 def hello(nameNone): return render_template(hello-1.html, namename, digits[1,2,3,4,5], users[{name:John}, {name:Tom, hidden:True}, {name:Lisa} {name:Bob}]) 然后在模板中加上 1 2 3 {% for digit in digits %} {{ digit }} {% endfor %} 是不是列表被显示出来了同if语句一样for控制语句要用”{% endfor %}”来结束。页面上每个元素之间会有空格如果你不希望有空格就要在”for”语句的最后和”endfor”语句的最前面各加上一个”-“号。如 1 2 3 {% for digit in digits -%} {{ digit }} {%- endfor %} 现在你可以看到数字”12345″被一起显示出来了。我们再来看个复杂的循环例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 dl {% for user in users if not user.hidden %} {% if loop.first %} divUser List:/div {% endif %} div class{{ loop.cycle(odd, even) }} dtUser No {{ loop.index }}:/dt dd{{ user.name }}/dd /div {% if loop.last %} divTotal Users: {{ loop.length }}/div {% endif %} {% else %} liNo users found/li {% endfor %} /dl 这里有三个知识点。首先for循环支持else语句当待遍历的列表”users”为空或者为None时就进入else语句。 其次在for语句后使用if关键字可以对循环中的项作过滤。本例中所有hidden属性为True的user都会被过滤掉。 另外for循环中可以访问Jinja2的循环内置变量。本例中我们会在第一项前显示标题最后一项后显示总数每一项显示序号。另外奇偶项的HTML div元素会有不同的class。如果我们加入下面的CSS style就能看到斑马线。 直接在html页面中写入下列内容即可。 不过也可单独写入一个css文件路不过需要引用才能生效 1 2 3 4 5 style typetext/css .odd { background-color: #BDF; } /style Jinja2的循环内置变量主要有以下几个 变量 内容 loop.index 循环迭代计数从1开始 loop.index0 循环迭代计数从0开始 loop.revindex 循环迭代倒序计数从len开始到1结束 loop.revindex0 循环迭代倒序计数从len1开始到0结束 loop.first 是否为循环的第一个元素 loop.last 是否为循环的最后一个元素 loop.length 循环序列中元素的个数 loop.cycle 在给定的序列中轮循如上例在”odd”和”even”两个值间轮循 loop.depth 当前循环在递归中的层级从1开始 loop.depth0 当前循环在递归中的层级从0开始 关于递归循环大家看看下面的例子我就不多介绍了 1 2 3 4 5 6 7 8 {% for item in [[1,2],[3,4,5]] recursive %} Depth: {{ loop.depth }} {% if item[0] %} {{ loop(item) }} {% else %} Number: {{ item }} ; {% endif %} {% endfor %} 另外如果你启用了”jinja2.ext.loopcontrols”扩展的话你还可以在循环中使用”{% break %}”和”{% continue %}”来控制循环执行。关于Jinja2的扩展我们会在本系列的第七篇和第八篇中介绍。 其他常用语句 忽略模板语法 有时候我们在页面上就是要显示”{{ }}”这样的符号怎么办Jinja2提供了”raw”语句来忽略所有模板语法。 1 2 3 4 5 6 7 {% raw %} ul {% for item in items %} li{{ item }}/li {% endfor %} /ul {% endraw %} 自动转义 我们将本文一开始的Flask代码”hello()”方法改动下 1 2 3 4 5 6 app.route(/hello) app.route(/hello/name) def hello(nameNone): if name is None: name emWorld/em return render_template(hello.html, namename) 此时访问”http://localhost:5000/hello”页面上会显示”Welcome emWorld/em!”也就是这个HTML标签”em”被自动转义了。正如我们曾经提到过的Flask会对”.html”, “.htm”, “.xml”, “.xhtml”这四种类型的模板文件开启HTML格式自动转义。这样也可以防止HTML语法注入。如果我们不想被转义怎么办 1 2 3 {% autoescape false %} h1Hello {{ name }}!/h1 {% endautoescape %} 将”autoescape”开关设为”false”即可反之设为”true”即开启自动转义。使用”autoescape”开关前要启用”jinja2.ext.autoescape”扩展在Flask框架中这个扩展默认已启用。 赋值 使用”set”关键字给变量赋值 1 {% set items [[1,2],[3,4,5]] %} 用法可以参考下面的with语句 with语句 类似于Python中的”with”关键字它可以限制with语句块内对象的作用域 1 2 3 4 5 {% with foo 1 %} {% set bar 2 %} {{ foo bar }} {% endwith %} {# foo and bar are not visible here #} 使用”with”关键字前要启用”jinja2.ext.with_”扩展在Flask框架中这个扩展默认已启用。 执行表达式 1 2 3 4 {% with arr [Sunny] %} {{ arr.append(Rainy) }} {{ arr }} {% endwith %} 看上面这段代码我们想执行列表的”append”操作这时使用”{{ arr.append(‘Rainy’) }}”页面会输出”None”换成”{% %}”来执行程序会报错因为这是个表达式不是语句。那怎么办我们可以启用”jinja2.ext.do”扩展。然后在模板中执行”do”语句即可 1 2 3 4 {% with arr [Sunny] %} {% do arr.append(Rainy) %} {{ arr }} {% endwith %} 默认jinja2没开启这个需要启用 在py文件中添加这个app.jinja_env.add_extension(jinja2.ext.do)表示启用jinja2的do扩展然后就能在html文件中使用上述语句了 本篇中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(二)–上下文环境 Flask每个请求都有生命周期在生命周期内请求有其上下文环境Request Context。我们在Flask进阶系列第一篇中有详细介绍。作为在请求中渲染的模板自然也在请求的生命周期内所以Flask应用中的模板可以使用到请求上下文中的环境变量及一些辅助函数。本文就会介绍下这些变量和函数。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展标准上下文变量和函数 请求对象request request对象可以用来获取请求的方法”request.method”表单”request.form”请求的参数”request.args”请求地址”request.url”等。它本身是一个字典。在模板中你一样可以获取这些内容只要用表达式符号”{{ }}”括起来即可。 1 p{{ request.url }}/p 在没有请求上下文的环境中这个对象不可用。 会话对象session session对象可以用来获取当前会话中保存的状态它本身是一个字典。在模板中你可以用表达式符号”{{ }}”来获取这个对象。 Flask代码如下别忘了设置会话密钥哦 注需要导入from flask import Flask,render_template,session 1 2 3 4 5 6 app.route(/) def index(): session[user] guest return render_template(hello.html) app.secret_key 123456 模板代码 1 pUser: {{ session.user }}/p 在没有请求上下文的环境中这个对象不可用。 全局对象g 全局变量g用来保存请求中会用到全局内容比如数据库连接。模板中也可以访问。 Flask代码 注需要导入from flask import Flask,render_template,g 1 2 3 4 app.route(/) def index(): g.db mysql return render_template(hello.html) 模板代码 1 pDB: {{ g.db }}/p g对象是保存在应用上下文环境中的也只在一个请求生命周期内有效。在没有应用上下文的环境中这个对象不可用。 Flask配置对象config 在Flask入门系列第六篇中我们曾介绍过如何将配置信息导入Flask应用中。导入的配置信息就保存在”app.config”对象中。这个配置对象在模板中也可以访问。 1 pHost: {{ config.DEBUG }}/p 结果返回Host:True表示的是开启了调试模式 “config”是全局对象离开了请求生命周期也可以访问。 url_for()函数 url_for()函数可以用来快速获取及构建URLFlask也将此函数引入到了模板中比如下面的代码就可以获取静态目录下的”style.css”文件。 1 link relstylesheet href{{ url_for(static, filenamestyle.css) }} 该函数是全局的离开了请求生命周期也可以调用。 get_flashed_messages()函数 get_flashed_messages()函数是用来获取消息闪现的。具体的示例我们在入门系列第五篇中已经讲过这里就不再赘述了。这也是一个全局可使用的函数。 自定义上下文变量和函数 自定义变量 除了Flask提供的标准上下文变量和函数我们还可以自己定义。下面我们就来先定义一个上下文变量在Flask应用代码中加入下面的函数 1 2 3 4 5 from flask import current_app app.context_processor def appinfo(): return dict(appnamecurrent_app.name) 函数返回的是一个字典里面有一个属性”appname”值为当前应用的名称。我们曾经介绍过这里的”current_app”对象是一个定义在应用上下文中的代理。函数用”app.context_processor”装饰器修饰它是一个上下文处理器它的作用是在模板被渲染前运行其所修饰的函数并将函数返回的字典导入到模板上下文环境中与模板上下文合并。然后在模板中”appname”就如同上节介绍的”request”, “session”一样成为了可访问的上下文对象。我们可以在模板中将其输出 1 pCurrent App is: {{ appname }}/p 自定义函数 同理我们可以自定义上下文函数只需将上例中返回字典的属性指向一个函数即可下面我们就来定义一个上下文函数来获取系统当前时间 1 2 3 4 5 6 7 import time app.context_processor def get_current_time(): def get_time(timeFormat%b %d, %Y - %H:%M:%S): return time.strftime(timeFormat) return dict(current_timeget_time) 我们可以试下在模板中将其输出 1 2 pCurrent Time is: {{ current_time() }}/p pCurrent Day is: {{ current_time(%Y-%m-%d) }}/p 上下文处理器可以修饰多个函数也就是我们可以定义多个上下文环境变量和函数。 本篇中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(三)–过滤器 我所了解的模板引擎大部分都会提供类似Jinja2过滤器的功能只不过叫法不同罢了。比如PHP Smarty中的Modifiers变量调节器或修饰器FreeMarker中的Build-ins内建函数连AngularJS这样的前端框架也提供了Filter过滤器。它们都是用来在变量被显示或使用前对其作转换处理的。可以把它认为是一种转换函数输入的参数就是其所修饰的变量返回的就是变量转换后的值。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展过滤器使用 回到我们第一篇开篇的例子我们在模板中对变量name作如下处理 1 h1Hello {{ name | upper }}!/h1 你会看到name的输出都变成大写了。这就是过滤器只需在待过滤的变量后面加上”|”符号再加上过滤器名称就可以对该变量作过滤转换。上面例子就是转换成全大写字母。过滤器可以连续使用 1 h1Hello {{ name | upper | truncate(3, True) }}!/h1 现在name变量不但被转换为大写而且当它的长度大于3后只显示前3个字符后面默认用”…”显示。过滤器”truncate”有3个参数第一个是字符截取长度第二个决定是否保留截取后的子串默认是False也就是当字符大于3后只显示”…”截取部分也不出现第三个是省略符号默认是”…”。 其实从例子中我们可以猜到过滤器本质上就是一个转换函数它的第一个参数就是待过滤的变量在模板中使用时可以省略去。如果它有第二个参数模板中就必须传进去。 内置过滤器 Builtin Filters Jinja2模板引擎提供了丰富的内置过滤器。这里介绍几个常用的。 字符串操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 {# 当变量未定义时显示默认字符串可以缩写为d #} p{{ name | default(No name, true) }}/p {# 单词首字母大写 #} p{{ hello | capitalize }}/p {# 单词全小写 #} p{{ XML | lower }}/p {# 去除字符串前后的空白字符 #} p{{ hello | trim }}/p {# 字符串反转返回olleh #} p{{ hello | reverse }}/p {# 格式化输出返回Number is 2 #} p{{ %s is %d | format(Number, 2) }}/p {# 关闭HTML自动转义 #} p{{ emname/em | safe }}/p {% autoescape false %} {# HTML转义即使autoescape关了也转义可以缩写为e #} p{{ emname/em | escape }}/p {% endautoescape %} 数值操作 1 2 3 4 5 6 7 8 {# 四舍五入取整返回13.0 #} p{{ 12.8888 | round }}/p {# 四舍五入向下截取到小数点后2位返回12.89 #} p{{ 12.8888 | round(2) }}/p {# 向下截取到小数点后2位返回12.88 #} p{{ 12.8888 | round(2, floor) }}/p {# 绝对值返回12 #} p{{ -12 | abs }}/p 列表操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 {# 取第一个元素 #} p{{ [1,2,3,4,5] | first }}/p {# 取最后一个元素 #} p{{ [1,2,3,4,5] | last }}/p {# 返回列表长度可以写为count #} p{{ [1,2,3,4,5] | length }}/p {# 列表求和 #} p{{ [1,2,3,4,5] | sum }}/p {# 列表排序默认为升序 #} p{{ [3,2,1,5,4] | sort }}/p {# 合并为字符串返回1 | 2 | 3 | 4 | 5 #} p{{ [1,2,3,4,5] | join( | ) }}/p {# 列表中所有元素都全大写。这里可以用upper,lower但capitalize无效 #} p{{ [tom,bob,ada] | upper }}/p 字典列表操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 {% set users[{name:Tom,gender:M,age:20}, {name:John,gender:M,age:18}, {name:Mary,gender:F,age:24}, {name:Bob,gender:M,age:31}, {name:Lisa,gender:F,age:19}] %} {# 按指定字段排序这里设reverse为true使其按降序排 #} ul {% for user in users | sort(attributeage, reversetrue) %} li{{ user.name }}, {{ user.age }}/li {% endfor %} /ul {# 列表分组每组是一个子列表组名就是分组项的值 #} ul {% for group in users|groupby(gender) %} li{{ group.grouper }}ul {% for user in group.list %} li{{ user.name }}/li {% endfor %}/ul/li {% endfor %} /ul {# 取字典中的某一项组成列表再将其连接起来 #} p{{ users | map(attributename) | join(, ) }}/p 更全的内置过滤器介绍可以从Jinja2的官方文档中找到。 Flask内置过滤器 Flask提供了一个内置过滤器”tojson”它的作用是将变量输出为JSON字符串。这个在配合Javascript使用时非常有用。我们延用上节字典列表操作中定义的”users”变量 1 2 3 4 script typetext/javascript var users {{ users | tojson | safe }}; console.log(users[0].name); /script 注意这里要避免HTML自动转义所以加上safe过滤器。 注暂不知道具体用法 语句块过滤 Jinja2还可以对整块的语句使用过滤器。 1 2 3 {% filter upper %} This is a Flask Jinja2 introduction. {% endfilter %} 不过上述这种场景不经常用到。 自定义过滤器 内置的过滤器不满足需求怎么办自己写呗。过滤器说白了就是一个函数嘛我们马上就来写一个。回到Flask应用代码中 注:这个很有用 1 2 def double_step_filter(l): return l[::2] 我们定义了一个”double_step_filter”函数返回输入列表的偶数位元素第0位第2位,..。怎么把它加到模板中当过滤器用呢Flask应用对象提供了”add_template_filter”方法来帮我们实现。我们加入下面的代码 1 app.add_template_filter(double_step_filter, double_step) 函数的第一个参数是过滤器函数第二个参数是过滤器名称。然后我们就可以愉快地在模板中使用这个叫”double_step”的过滤器了 1 2 {# 返回[1,3,5] #} p{{ [1,2,3,4,5] | double_step }}/p Flask还提供了添加过滤器的装饰器”template_filter”使用起来更简单。下面的代码就添加了一个取子列表的过滤器。装饰器的参数定义了该过滤器的名称”sub”。 1 2 3 app.template_filter(sub) def sub(l, start, end): return l[start:end] 我们在模板中可以这样使用它 1 2 {# 返回[2,3,4] #} p{{ [1,2,3,4,5] | sub(1,4) }}/p Flask添加过滤器的方法实际上是封装了对Jinja2环境变量的操作。上述添加”sub”过滤器的方法等同于下面的代码。 1 app.jinja_env.filters[sub] sub 我们在Flask应用中不建议直接访问Jinja2的环境变量。如果离开Flask环境直接使用Jinja2的话就可以通过”jinja2.Environment”来获取环境变量并添加过滤器。 本篇中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(四)–测试器 Jinja2中的测试器Test和过滤器非常相似区别是测试器总是返回一个布尔值它可以用来测试一个变量或者表达式你需要使用”is”关键字来进行测试。测试器一般都是跟着if控制语句一起使用的。下面我们就来深入了解下这个测试器。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展测试器使用 再次取回第一篇开篇的例子我们在模板中对变量name作如下判断 1 2 3 {% if name is lower %} h2{{ name }} are all lower case./h2 {% endif %} 当name变量中的字母都是小写时这段文字就会显示。这就是测试器在if语句中变量或表达式的后面加上is关键字再加上测试器名称就可以对该变量或表达式作测试并根据其测试结果的真或假来决定是否进入if语句块。测试器也可以有参数用括号括起。当其只有一个参数时可以省去括号。 1 2 3 {% if 6 is divisibleby 3 %} h2divisibleby test pass/h2 {% endif %} 上例中测试器”divisibleby”可以判断其所接收的变量是否可以被其参数整除。因为它只有一个参数我们就可以用空格来分隔测试器和其参数。上面的调用同”divisibleby(3)”效果一致。测试器也可以配合not关键字一起使用 1 2 3 {% if 6 is not divisibleby(4) %} h2not divisibleby test pass/h2 {% endif %} 显然测试器本质上也是一个函数它的第一个参数就是待测试的变量在模板中使用时可以省略去。如果它有第二个参数模板中就必须传进去。测试器函数返回的必须是一个布尔值这样才可以用来给if语句作判断。 内置测试器 Builtin Tests 同过滤器一样Jinja2模板引擎提供了丰富的内置测试器。这里介绍几个常用的。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 {# 检查变量是否被定义也可以用undefined检查是否未被定义 #} {% if name is defined %} pName is: {{ name }}/p {% endif %} {# 检查是否所有字符都是大写 #} {% if name is upper %} h2{{ name }} are all upper case./h2 {% endif %} {# 检查变量是否为空 #} {% if name is none %} h2Variable is none./h2 {% endif %} {# 检查变量是否为字符串也可以用number检查是否为数值 #} {% if name is string %} h2{{ name }} is a string./h2 {% endif %} {# 检查数值是否是偶数也可以用odd检查是否为奇数 #} {% if 2 is even %} h2Variable is an even number./h2 {% endif %} {# 检查变量是否可被迭代循环也可以用sequence检查是否是序列 #} {% if [1,2,3] is iterable %} h2Variable is iterable./h2 {% endif %} {# 检查变量是否是字典 #} {% if {name:test} is mapping %} h2Variable is dict./h2 {% endif %} 更全的内置测试器介绍可以从Jinja2的官方文档中找到。 自定义测试器 如果内置测试器不满足需求我们就来自己写一个。写法很类似于过滤器先在Flask应用代码中定义测试器函数然后通过”add_template_test”将其添加为模板测试器 1 2 3 4 import re def has_number(str): return re.match(r.*\d, str) app.add_template_test(has_number,contain_number) 我们定义了一个”has_number”函数用正则来判断输入参数是否包含数字。然后调用”app.add_template_test”方法第一个参数是测试器函数第二个是测试器名称。之后我们就可以在模板中使用”contain_number”测试器了 1 2 3 {% if name is contain_number %} h2{{ name }} contains number./h2 {% endif %} 同过滤器一样Flask提供了添加测试器的装饰器”template_test”。下面的代码就添加了一个判断字符串是否以某一子串结尾的测试器。装饰器的参数定义了该测试器的名称”end_with” 1 2 3 app.template_test(end_with) def end_with(str, suffix): return str.lower().endswith(suffix.lower()) 我们在模板中可以这样使用它 1 2 3 {% if name is end_with me %} h2{{ name }} ends with me./h2 {% endif %} Flask添加测试器的方法是封装了对Jinja2环境变量的操作。上述添加”end_with”测试器的方法等同于下面的代码。 1 app.jinja_env.tests[end_with] end_with 我们在Flask应用中不建议直接访问Jinja2的环境变量。如果离开Flask环境直接使用Jinja2的话就可以通过”jinja2.Environment”来获取环境变量并添加测试器。 本文中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(五)–全局函数 介绍完了过滤器和测试器接下来要讲的是Jinja2模板引擎的另一个辅助函数功能即全局函数Global Functions。如果说过滤器是一个变量转换函数测试器是一个返回布尔值的函数那全局函数就可以是任意函数。可以在任一场景使用没有输入和输出值的限制。本篇我们就来阐述下这个全局函数。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展全局函数使用 还是取出第一篇开篇的代码我们在模板中加入下面的代码 1 2 3 4 5 ul {% for num in range(10, 20, 2) %} liNumber is {{ num }}/li {% endfor %} /ul 页面上会显示”10,12,14,16,18″5个列表项。全局函数”range()”的作用同Python里的一样返回指定范围内的数值序列。三个参数分别是开始值结束值不包含间隔。如果只传两个参数那间隔默认为1如果只传1个参数那开始值默认为0。 由此可见全局函数如同其名字一样就是全局范围内可以被使用的函数。其同第二篇介绍的上下文环境中定义的函数不同没有请求生命周期的限制。 内置全局函数 演示几个常用的内置全局函数。 dict()函数方便生成字典型变量 1 2 3 4 {% set user dict(nameMike,age15) %} p{{ user | tojson | safe }}/p {# 显示 {age: 15, name: Mike} #} joiner()函数神奇的辅助函数。它可以初始化为一个分隔符然后第一次调用时返回空字符串以后再调用则返回分隔符。对分隔循环中的内容很有帮助 1 2 3 4 5 6 {% set sep joiner(|) %} {% for val in range(5) %} {{ sep() }} span{{ val }}/span {% endfor %} {# 显示 0 | 1 | 2 | 3 | 4 #} cycler()函数作用同第一篇介绍的循环内置变量”loop.cycle”类似在给定的序列中轮循 1 2 3 4 5 6 7 {% set cycle cycler(odd, even) %} ul {% for num in range(10, 20, 2) %} li class{{ cycle.next() }}Number is {{ num }}, next line is {{ cycle.current }} line./li {% endfor %} /ul 基于上一节的例子加上”cycler()”函数的使用你会发现列表项li的”class”在”odd”和”even”两个值间轮循。加入第一篇中的CSS style就可以看到斑马线了。 “cycler()”函数返回的对象可以做如下操作 next()返回当前值并往下一个值轮循reset()重置为第一个值current当前轮循到的值更全的内置全局函数介绍可以从Jinja2的官方文档中找到。 自定义全局函数 我们当然也可以写自己的全局函数方法同之前介绍的过滤器啦测试器啦都很类似。就是将Flask应用代码中定义的函数通过”add_template_global”将其传入模板即可 1 2 3 4 5 6 7 8 9 import re def accept_pattern(pattern_str): pattern re.compile(pattern_str, re.S) def search(content): return pattern.findall(content) return dict(searchsearch, current_patternpattern_str) app.add_template_global(accept_pattern, accept_pattern) 上例中的accept_pattern函数会先预编译一个正则然后返回的字典中包含一个查询函数”search”之后调用”search”函数就可以用编译好的正则来搜索内容了。”app.add_template_global”方法的第一个参数是自定义的全局函数第二个是全局函数名称。现在让我们在模板中使用”accept_pattern”全局函数 1 2 3 4 5 6 7 8 9 {% with pattern accept_pattern(li(.*?)/li) %} {% set founds pattern.search(liTom/liliBob/li) %} ul {% for item in founds %} liFound: {{ item }}/li {% endfor %} /ul pCurrent Pattern: {{ pattern.current_pattern }}/p {% endwith %} “Tom”和”Bob”被抽取出来了很牛掰的样子。你还可以根据需要在”accept_pattern”的返回字典里定义更多的方法。 Flask同样提供了添加全局函数的装饰器”template_global”以方便全局函数的添加。我们来用它将第二篇中取系统当前时间的函数”current_time”定义为全局函数。 1 2 3 4 import time app.template_global(end_with) def current_time(timeFormat%b %d, %Y - %H:%M:%S): return time.strftime(timeFormat) 同第二篇中的一样我们在模板中可以这样使用它 1 2 pCurrent Time is: {{ current_time() }}/p pCurrent Day is: {{ current_time(%Y-%m-%d) }}/p Flask添加全局函数的方法是封装了对Jinja2环境变量的操作。上述添加”current_time”全局函数的方法等同于下面的代码。 1 app.jinja_env.globals[current_time] current_time 我们在Flask应用中不建议直接访问Jinja2的环境变量。如果离开Flask环境直接使用Jinja2的话就可以通过”jinja2.Environment”来获取环境变量并添加全局函数。 本文中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(六)–块和宏 考虑到模板代码的重用Jinja2提供了块 (Block)和宏 (Macro)的功能。块功能有些类似于C语言中的宏原理就是代码替换而宏的功能有些类似于函数可以传入参数。本篇我们就来介绍下块和宏的用法。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展块 (Block) 在Flask入门系列第三篇介绍模板时我们提到了模板的继承。我们在子模板的开头定义了”{% extend ‘parent.html’ %}”语句来声明继承此后在子模板中由”{% block block_name %}”和”{% endblock %}”所包括的语句块将会替换父模板中同样由”{% block block_name %}”和”{% endblock %}”所包括的部分。 这就是块的功能模板语句的替换。这里要注意几个点 模板不支持多继承也就是子模板中定义的块不可能同时被两个父模板替换。模板中不能定义多个同名的块子模板和父模板都不行因为这样无法知道要替换哪一个部分的内容。另外我们建议在”endblock”关键字后也加上块名比如”{% endblock block_name %}”。虽然对程序没什么作用但是当有多个块嵌套时可读性好很多。 保留父模板块的内容 如果父模板中的块里有内容不想被子模板替换怎么办我们可以使用”super( )”方法。基于Flask入门系列第三篇的例子我们将父模板”layout.html”改为 1 2 3 4 5 6 7 8 9 10 11 12 13 !doctype html head {% block head %} link relstylesheet href{{ url_for(static, filenamestyle.css) }} title{% block title %}{% endblock %}/title {% endblock %} /head body div classpage {% block body %} {% endblock %} /div /body 并在子模板里加上”head”块和”title”块 1 2 3 4 5 6 7 {% block title %}Block Sample{% endblock %} {% block head %} {{ super() }} style typetext/css h1 { color: #336699; } /style {% endblock %} 父模板同子模板的”head”块中都有内容。运行后你可以看到父模板中的”head”块语句先被加载而后是子模板中的”head”块语句。这就得益于我们在子模板的”head”块中加上了表达式”{{ super( ) }}”。效果有点像Java中的”super( )”吧。 块内语句的作用域 默认情况下块内语句是无法访问块外作用域中的变量。比如我们在”layout.html”加上一个循环 1 2 3 {% for item in range(5) %} li{% block list %}{% endblock %}/li {% endfor %} 然后在子模板中定义”list”块并访问循环中的”item”变量 1 2 3 {% block list %} em{{ item }}/em {% endblock %} 你会发现页面上什么数字也没显示。如果你想在块内访问这个块外的变量你就需要在块声明时添加”scoped”关键字。比如我们在”layout.html”中这样声明”list”块即可 1 2 3 {% for item in range(5) %} li{% block list scoped %}{% endblock %}/li {% endfor %} 宏 (Macro) 文章的开头我们就讲过Jinja2的宏功能有些类似于传统程序语言中的函数既然是函数就有其声明和调用两个部分。那就让我们先声明一个宏 1 2 3 {% macro input(name, typetext, value) -%} input type{{ type }} name{{ name }} value{{ value|e }} {%- endmacro %} 代码中宏的名称就是”input”它有三个参数分别是”name”, “type”和”value”后两个参数有默认值。现在让我们使用表达式来调用这个宏 1 2 3 p{{ input(username, valueuser) }}/p p{{ input(password, password) }}/p p{{ input(submit, submit, Submit) }}/p 大家可以在页面上看到一个文本输入框一个密码输入框及一个提交按钮。是不是同函数一样啊其实它还有比函数更丰富的功能之后我们来介绍。 访问调用者内容 我们先来创建个宏”list_users” 1 2 3 4 5 6 7 8 {% macro list_users(users) -%} table trthName/ththAction/th/tr {%- for user in users %} trtd{{ user.name |e }}/td{{ caller() }}/tr {%- endfor %} /table {%- endmacro %} 宏的作用就是将用户列表显示在表格里表格每一行用户名称后面调用了”{{ caller( ) }}”方法这个有什么用呢先别急我们来写调用者的代码 1 2 3 4 5 6 7 8 {% set users[{name:Tom,gender:M,age:20}, {name:John,gender:M,age:18}, {name:Mary,gender:F,age:24}] %} {% call list_users(users) %} tdinput namedelete typebutton valueDelete/td {% endcall %} 与上例不同这里我们使用了”{% call %}”语句块来调用宏语句块中包括了一段生成”Delete”按钮的代码。运行下试试你会发现每个用户名后面都出现了”Delete”按钮也就是”{{ caller( ) }}”部分被调用者”{% call %}”语句块内部的内容替代了。不明觉厉吧其实吧这个跟函数传个参数进去没啥大区别个人觉得主要是有些时候HTML语句太复杂如上例不方便写在调用参数上所以就写在”{% call %}”语句块里了。 Jinja2的宏不但能访问调用者语句块的内容还能给调用者传递参数。嚯这又是个什么鬼我们来扩展下上面的例子。首先我们将表格增加一列性别并在宏里调用”caller()”方法时传入一个变量”user.gender” 1 2 3 4 5 6 7 8 {% macro list_users(users) -%} table trthName/ththGender/ththAction/th/tr {%- for user in users %} trtd{{ user.name |e }}/td{{ caller(user.gender) }}/tr {%- endfor %} /table {%- endmacro %} 然后我们修改下调用者语句块 1 2 3 4 5 6 7 8 9 10 {% call(gender) list_users(users) %} td {% if gender M %} img src{{ url_for(static, filenameimg/male.png) }} width20px {% else %} img src{{ url_for(static, filenameimg/female.png) }} width20px {% endif %} /td tdinput namedelete typebutton valueDelete/td {% endcall %} 大家注意到我们在使用”{% call %}”语句时将其改为了”{% call(gender) … %}”这个括号中的”gender”就是用来接受宏里传来的”user.gender”变量。因此我们就可以在”{% call %}”语句中使用这个”gender”变量来判断用户性别。这样宏就成功地向调用者传递了参数。 宏的内部变量 上例中我们看到宏的内部可以使用”caller( )”方法获取调用者的内容。此外宏还提供了两个内部变量 varargs这是一个列表。如果调用宏时传入的参数多于宏声明时的参数多出来的没指定参数名的参数就会保存在这个列表中。 kwargs这是一个字典。如果调用宏时传入的参数多于宏声明时的参数多出来的指定了参数名的参数就会保存在这个字典中。 让我们回到第一个例子input宏在调用时增加其传入的参数并在宏内将上述两个变量打印出来 1 2 3 4 5 6 {% macro input(name, typetext, value) -%} input type{{ type }} name{{ name }} value{{ value|e }} br / {{ varargs }} br / {{ kwargs }} {%- endmacro %} p{{ input(submit, submit, Submit, more arg1, more arg2, extmore arg3) }}/p 可以看到varargs变量存了参数列表”[‘more arg1’, ‘more arg2’]”而kwargs字典存了参数”{‘ext’:’more arg3′}”。 宏的导入 一个宏可以被不同的模板使用所以我们建议将其声明在一个单独的模板文件中。需要使用时导入进来即可而导入的方法也非常类似于Python中的”import”。让我们将第一个例子中”input”宏的声明放到一个”form.html”模板文件中然后将调用的代码改为 1 2 3 4 {% import form.html as form %} p{{ form.input(username, valueuser) }}/p p{{ form.input(password, password) }}/p p{{ form.input(submit, submit, Submit) }}/p 运行下效果是不是同之前的一样你也可以采用下面的方式导入 1 2 3 4 {% from form.html import input %} p{{ input(username, valueuser) }}/p p{{ input(password, password) }}/p p{{ input(submit, submit, Submit) }}/p 包含 (Include) 这里我们再介绍一个Jinja2模板中代码重用的功能就是包含 (Include)使用的方法就是”{% include %}”语句。其功能就是将另一个模板加载到当前模板中并直接渲染在当前位置上。它同导入”import”不一样”import”之后你还需要调用宏来渲染你的内容”include”是直接将目标模板渲染出来。它同block块继承也不一样它一次渲染整个模板文件内容不分块。 我们可以创建一个”footer.html”模板并在”layout.html”中包含这个模板 1 2 3 4 body ... {% include footer.html %} /body 当”include”的模板文件不存在时程序会抛出异常。你可以加上”ignore missing”关键字这样如果模板不存在就会忽略这段”{% include %}”语句。 1 {% include footer.html ignore missing %} “{% include %}”语句还可以跟一个模板列表 1 {% include [footer.html,bottom.html,end.html] ignore missing %} 上例中程序会按顺序寻找模板文件第一个被找到的模板即被加载而其后的模板都会被忽略。如果都没找到那整个语句都会被忽略。 本篇中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(七)–本地化 一个强大的工具一般都支持扩展或插件的开发功能来允许第三方通过开发新扩展或插件扩充工具本身功能并可以贡献给社区。Jinja2也不例外Jinja2本身提供了一部分扩展你可以在程序中启用。同时你还可以创建自己的扩展来扩充模板引擎功能。本篇会先介绍Jinja2自带的扩展”jinja2.ext.i18n”的使用自定义扩展的开发会放在下一篇阐述。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展在Flask中启用Jinja2扩展 任何时候使用Jinja2时都需要先创建Jinja2环境所以启用扩展的方法就是在创建环境时指定 1 2 from jinja2 import Environment jinja_env Environment(extensions[jinja2.ext.i18n,jinja2.ext.do]) 但是你在使用Flask时其已经有了一个Jinja2环境你不能再创建一个所以你需要想办法添加扩展。Flask对于扩展不像过滤器或测试器那样封装了添加方法和装饰器这样你就只能直接访问Flask中的Jinja2环境变量来添加。 1 2 3 4 from flask import Flask app Flask(__name__) app.jinja_env.add_extension(jinja2.ext.i18n) app.jinja_env.add_extension(jinja2.ext.do) 注Flask默认已加载了”jinja2.ext.autoescape”和”jinja2.ext.with_”扩展。 Jinja2内置扩展 在本系列第一篇中我们已经介绍了四个Jinja2内置扩展的使用”jinja2.ext.autoescape”, “jinja2.ext.with_”, “jinja2.ext.do”和”jinja2.ext.loopcontrols”。除了这几个以外Jinja2还有一个非常重要的扩展就是提供本地化功能的”jinja2.ext.i18n”。它可以与”gettext”或”babel”联合使用接下来我们采用”gettext”来介绍怎么使用这个本地化扩展。 注本地化这个其实就是翻译页面语言这个详看Flask-Babel 创建本地化翻译文件 建议大家先去了解下Python gettext相关知识篇幅关系本文就不准备细讲。这里我们使用Python源代码记住不是安装包中”Tools/i18n”目录下的工具来创建翻译文件。 首先我们生成翻译文件模板在”Tools/i18n”目录中找到”pygettext.py”并运行$ python pygettext.py 上述命令会在当前目录下生成一个名为”message.pot”的翻译文件模板内容如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR EMAILADDRESS, YEAR. # msgid msgstr Project-Id-Version: PACKAGE VERSION\n POT-Creation-Date: 2016-02-22 21:45CST\n PO-Revision-Date: YEAR-MO-DA HO:MIZONE\n Last-Translator: FULL NAME EMAILADDRESS\n Language-Team: LANGUAGE LLli.org\n MIME-Version: 1.0\n Content-Type: text/plain; charsetCHARSET\n Content-Transfer-Encoding: ENCODING\n Generated-By: pygettext.py 1.5\n 将”message.pot”中”CHARSET”和”ENCODING”替换成”UTF-8″。同时你可以更改注释信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Jinja2 i18n Extention Sample # Copyright (C) 2016 bjhee.com # Billy J. Hee billybjhee.com, 2016. # msgid msgstr Project-Id-Version: PACKAGE VERSION\n POT-Creation-Date: 2016-02-22 21:45CST\n PO-Revision-Date: YEAR-MO-DA HO:MIZONE\n Last-Translator: FULL NAME EMAILADDRESS\n Language-Team: LANGUAGE LLli.org\n MIME-Version: 1.0\n Content-Type: text/plain; charsetUTF-8\n Content-Transfer-Encoding: UTF-8\n Generated-By: pygettext.py 1.5\n 修改完后将其另存为翻译文件”lang.po”。 在”lang.po”中添加你要翻译的文字比如 1 2 msgid Hello World! msgstr 世界你好 将其加在文件末尾。这里”msgid”指定了待翻译的文字而”msgstr”就是翻译后的文字。 生成”lang.mo”文件我们依然使用”Tools/i18n”目录提供的工具”msgfmt.py” $ python msgfmt.py lang.po 执行完后当前目录生成了”lang.mo”文件。注意只有这个”*.mo”文件才能被应用程序识别。另外推荐一个工具Poedit很强的图形化po编辑工具也可以用来生成mo文件非常好用Mac和Windows下都能用。 将po, mo文件加入应用我们在当前Flask工程下创建子目录”locale/zh_CN/LC_MESSAGES/”并将刚才生成的”lang.po”和”lang.mo”文件放到这个目录下。这里”locale”子目录的名字可以更改其他的个人建议不要改。 在模板中使用本地化 让我们在Flask应用代码中启用”jinja2.ext.i18n”并加载刚创建的翻译文件。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #coding:utf8 import gettext from flask import Flask,render_template app Flask(__name__) # 加载扩展 app.jinja_env.add_extension(jinja2.ext.i18n) # lang表示翻译文件名为lang.molocale表示所有翻译文件都在locale子目录下 # zh_CN表示二级子目录含义上讲就是加载中文翻译。所以下面的代码会加载文件 # locale/zh_CN/LC_MESSAGES/lang.mo gettext.install(lang, locale, unicodeTrue) translations gettext.translation(lang, locale, languages[zh_CN]) translations.install(True) app.jinja_env.install_gettext_translations(translations) 这个”install_gettext_translations()”方法就是Jinja2提供来加载”gettext”翻译文件对象的。加载完后你就可以在模板中使用本地化功能了。方法有两种”{% trans %}”语句或”gettext()”方法。我们先来试下”{% trans %}”语句在模板文件中我们加上 1 h1{% trans %}Hello World!{% endtrans %}/h1 运行下有没有看到页面上打印了”世界你好”恭喜你成功了使用”gettext()”方法如下效果同”{% trans %}”语句一样。 1 h1{{ gettext(Hello World!) }}/h1 Jinja2还提供了”_( )”方法来替代”gettext( )”代码看起来很简洁个人推荐使用这个方法。 1 h1{{ _(Hello World!) }}/h1 上面的例子是在程序中指定本地化语言你也可以在请求上下文中判断请求头”Accept-Language”的内容来动态的设置本地化语言。 翻译内容带参数 有时候待翻译的文字内有一个变量必须在运行时才能确定怎么办我可以在翻译文字上加参数。首先你要在po文件中定义带参数的翻译文字并生成mo文件 1 2 msgid Hello %(user)s! msgstr %(user)s你好 然后你就可以在模板中使用”{% trans %}”语句或”gettext()”方法来显示它 1 2 h1{% trans username %}Hello {{ user }}!{% endtrans %}/h1 h1{{ _(Hello %(user)s!)|format(username) }}/h1 上例中我们把模板中的变量”name”赋给了翻译文字中的变量”user”。翻译文字上可以有多个变量。 新样式 (Newstyle) Jinja2从2.5版本开始支持新的gettext样式使得带参数的本地化更简洁上面的例子在新样式中可以写成 1 h1{{ _(Hello %(user)s!, username) }}/h1 不过使用新样式前你必须先启用它。还记得我们介绍过Jinja2加载翻译文件的方法吗对就是”install_gettext_translations()”。调用它时加上”newstyleTrue”参数即可。 1 app.jinja_env.install_gettext_translations(translations, newstyleTrue) 单复数支持 英文有个特点就是名词有单复数形式一般复数都是单数后面加s而中文就不区分了哎老外就是麻烦。所谓外国人创造的Python gettext自然也对单复数提供了特殊的支持。让我们现在po文件中加上下面的内容并生成mo文件 1 2 3 4 msgid %(num)d item msgid_plural %(num)d items msgstr[0] %(num)d个物品 msgstr[1] %(num)d个物品集 什么意思呢这个”msgid_plural”就是指定了它上面”msgid”文字的复数形式。而”msgstr”的[0], [1]分别对应了单复数形式翻译后的内容。为什么这么写你别管了照着写就是了。 在模板中我们加上下面的代码 1 2 3 {% set items [1,2,3,4,5] %} {{ ngettext(%(num)d item, %(num)d items, items|count) }}br / {{ ngettext(%(num)d item, %(num)d items, items|first) }} 你会很惊奇的发现当”num”变量为5时页面显示”5个物品集”而当”num”变量为1时页面显示”1个物品”。也就是程序自动匹配单复数。很神奇吧 本来准备在本篇把扩展都介绍完的发现单写个”i18n”后篇幅就很长了只好把自定义扩展部分另起一篇。 本篇中的示例代码可以在这里下载。 Flask中Jinja2模板引擎详解(八)–自定义扩展 说实话关于自定义扩展的开发Jinja2的官方文档写得真心的简单。到目前为止网上可参考的资料也非常少你必须得好好读下源码还好依然有乐于奉献的大牛们分享了些文章来帮助我理解怎么开发扩展。本文我就完全借鉴网上前人的例子来给大家演示一个Jinja2的自定义扩展的开发方法。 系列文章 Flask中Jinja2模板引擎详解(一)–控制语句和表达式Flask中Jinja2模板引擎详解(二)–上下文环境Flask中Jinja2模板引擎详解(三)–过滤器Flask中Jinja2模板引擎详解(四)–测试器Flask中Jinja2模板引擎详解(五)–全局函数Flask中Jinja2模板引擎详解(六)–块和宏Flask中Jinja2模板引擎详解(七)–本地化Flask中Jinja2模板引擎详解(八)–自定义扩展Pygments Pygments是Python提供语法高亮的工具官网是pygments.org。我们在介绍Jinja2的自定义扩展时为什么要介绍Pygments呢因为Jinja2的功能已经很强了我一时半会想不出该开发哪个有用的扩展写个没意义的扩展嘛又怕误导了读者。恰巧网上找到了一位叫Larry的外国友人开发了一个基于Pygments的代码语法高亮扩展感觉非常实用。他的代码使用了MIT License那就我放心拿过来用了不过还是要注明下这位Larry才是原创。 你需要先执行”pip install pygments”命令安装Pygments包。代码中用到Pygments的部分非常简单主要就是调用”pygments.highlight( )”方法来生成HTML文档。Pygments强的地方是它不把样式写在HTML当中这样就给了我们很大的灵活性。开始写扩展前让我们预先通过代码 1 2 from pygments.formatters import HtmlFormatter HtmlFormatter(stylevim).get_style_defs(.highlight) 生成样式内容并将其保存在”static/css/style.css”文件中。这个css文件就是用来高亮语法的。 想深入了解Pygments的朋友们可以先把官方文档看一下。 编写扩展 我们在Flask应用目录下创建一个”pygments_ext.py”文件内容如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #coding:utf8 from jinja2 import nodes from jinja2.ext import Extension from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import guess_lexer, get_lexer_by_name # 创建一个自定义扩展类继承jinja2.ext.Extension class PygmentsExtension(Extension): # 定义该扩展的语句关键字这里表示模板中的{% code %}语句会该扩展处理 tags set([code]) def __init__(self, environment): # 初始化父类必须这样写 super(PygmentsExtension, self).__init__(environment) # 在Jinja2的环境变量中添加属性 # 这样在Flask中就可以用app.jinja_env.pygments来访问 environment.extend( pygmentsself, pygments_supportTrue ) # 重写jinja2.ext.Extension类的parse函数 # 这是处理模板中{% code %}语句的主程序 def parse(self, parser): # 进入此函数时即表示{% code %}标签被找到了 # 下面的代码会获取当前{% code %}语句在模板文件中的行号 lineno next(parser.stream).lineno # 获取{% code %}语句中的参数比如我们调用{% code python %} # 这里就会返回一个jinja2.nodes.Const类型的对象值为python lang_type parser.parse_expression() # 将参数封装为列表 args [] if lang_type is not None: args.append(lang_type) # 下面的代码可以支持两个参数参数之间用逗号分隔不过本例中用不到 # 这里先检查当前处理流的位置是不是个逗号是的话就再获取一个参数 # 不是的话就在参数列表最后加个空值对象 # if parser.stream.skip_if(comma): # args.append(parser.parse_expression()) # else: # args.append(nodes.Const(None)) # 解析从{% code %}标志开始到{% endcode %}为止中间的所有语句 # 将解析完后的内容存在body里并将当前流位置移到{% endcode %}之后 body parser.parse_statements([name:endcode],drop_needleTrue) # 返回一个CallBlock类型的节点并将其之前取得的行号设置在该节点中 # 初始化CallBlock节点时传入我们自定义的_pygmentize方法的调用 # 两个空列表还有刚才解析后的语句内容body return nodes.CallBlock(self.call_method(_pygmentize, args), [], [], body).set_lineno(lineno) # 这个自定义的内部函数包含了本扩展的主要逻辑。 # 其实上面parse()函数内容大部分扩展都可以重用 def _pygmentize(self, lang_type, caller): # 初始化HTML格式器 formatter HtmlFormatter(linenostable) # 获取{% code %}语句中的内容 # 这里caller()对应了上面调用CallBlock()时传入的body content caller() # 将模板语句中解析到了lang_type设置为我们要高亮的语言类型 # 如果这个变量不存在则让Pygmentize猜测可能的语言类型 lexer None if lang_type is None: lexer guess_lexer(content) else: lexer get_lexer_by_name(lang_type) # 将{% code %}语句中的内容高亮即添加各种span, class等标签属性 return highlight(content, lexer, formatter) 这段程序解释起来太麻烦我就把注释都写在代码里了。总的来说扩展中核心部分就在”parse()”函数里而最关键的就是这个”parser”对象它是一个”jinja2.parser.Parser”的对象。建议大家可以参考下它的源码。我们使用的主要方法有 parser.stream 获取当前的文档处理流它可以基于文档中的行迭代所以可以使用”next()”方法向下一行前进并返回当前行parser.parse_expression() 解析下一个表达式并将结果返回parser.parse_statements() 解析下一段语句并将结果返回。可以连续解析多行。它有两个参数第一个是结束位置”end_tokens”上例中是”{% endcode %}”标签它是个列表可是设置多个结束标志遇到其中任意一个即结束第二个是布尔值”drop_needle”默认为False即解析完后流的当前位置指向结束语句”{% endcode %}”之前。设为True时即将流的当前位置设在结束语句之后在”parse()”函数最后我们创建了一个”nodes.CallBlock”的块节点对象并将其返回。初始化时我们先传入了”_pygmentize()”方法的调用然后两个空列表分别对应了字段和属性本例中用不到所以设空再传入解析后的语句块”body”。”CallBlock”节点初始化完后还要记得将当前行号设置进去。接下来我们对于语句块的所有操作都可以写在”_pygmentize()”方法里了。 “_pygmentize()”里的内容我就不多介绍了只需要记得声明这个方法时最后一定要接收一个参数caller它是个回调函数可以获取之前创建”CallBlock”节点时传入的语句块内容。 使用自定义扩展 扩展写完了其实也没几行代码就是注释多了点。现在我们在Flask应用代码中将其启用 1 2 3 4 5 from flask import Flask,render_template from pygments_ext import PygmentsExtension app Flask(__name__) app.jinja_env.add_extension(PygmentsExtension) 然后让我们在模板中试一下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 head link relstylesheet href{{ url_for(static, filenamecss/style.css) }} /head body pA sample of JS code/p {% autoescape false %} {% code javascript %} var name World; function foo() { console.log(Hello name); } {% endcode %} {% endautoescape %} /body 运行下页面上这段代码是不是有VIM的效果呀这里我们引入了刚才创建在”static/css”目录下”style.css”样式文件另外千万别忘了要将自动转义关掉不然你会看到一堆的HTML标签。 另外提醒下大家网上有文章说对于单条语句也就是不需要结束标志的语句”parse()”函数里无需调用”nodes.CallBlock”只需返回”return self.call_method(xxx)”即可。别相信他看看源码就知道这个方法返回的是一个”nodes.Expr”表达式对象而”parse()”必须返回一个”nodes.Stmt”语句对象。 本篇中的示例代码可以在这里下载。 转载于:https://www.cnblogs.com/sanduzxcvbnm/p/9339759.html