一、简介
一个Web应用的本质就是
- 浏览器发送一个HTTP请求;
- 服务器收到请求,生成一个HTML文档;
- 服务器把HTML文档作为HTTP响应的Body发送给浏览器;
- 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。
所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML返回。需要一个统一的接口,让我们专心用Python编写Web业务,这个接口就是WSGI:Web Server Gateway Interface。
二、Web框架
其实一个Web App,就是写一个WSGI的处理函数,针对每个HTTP请求进行响应。 但是直接自己编写WSGI处理函数会显得非常繁琐,这里完全可以用框架代替我们做。
Python web 三大框架:
1、Django
Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,模板T和视图V。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。
2、Flask
Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。
Flask也被称为 “microframework” ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具。
Flask 很轻,花很少的成本就能够开发一个简单的网站。非常适合初学者学习。Flask 框架学会以后,可以考虑学习插件的使用。例如使用 WTForm + Flask-WTForm 来验证表单数据,用 SQLAlchemy + Flask-SQLAlchemy 来对你的数据库进行控制。
3、Tornado
Tornado是一种 Web 服务器软件的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。
得利于其 非阻塞的方式和对epoll的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。
我尝试使用的是Flask框架。
1、Flask:
1 | pip install flask |
然后写一个app.py,处理3个URL,分别是:
- GET /:首页,返回Home;
- GET /signin:登录页,显示登录表单;
- POST /signin:处理登录表单,显示登录结果。
注意上面,同一个URL/signin分别有GET和POST两种请求,映射到两个处理函数中。
Flask通过Python的装饰器在内部自动地把URL和函数给关联起来,所以,我们写出来的代码就像这样:
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return '<h1>Home</h1>'
@app.route('/signin', methods=['GET'])
def signin_form():
return '''<form action="/signin" method="post">
<p><input name="username"></p>
<p><input name="password" type="password"></p>
<p><button type="submit">Sign In</button></p>
</form>'''
@app.route('/signin', methods=['POST'])
def signin():
# 需要从request对象读取表单内容:
if request.form['username']=='admin' and request.form['password']=='password':
return '<h3>Hello, admin!</h3>'
return '<h3>Bad username or password.</h3>'
if __name__ == '__main__':
app.run()
运行python app.py
,Flask自带的Server在端口5000上监听, 打开浏览器,输入首页地址 http://localhost:5000/看看效果。
再在浏览器地址栏输入http://localhost:5000/signin,会显示登录表单。 输入预设的用户名admin和口令password,登录成功,输入其他就显示登陆失败。
2、模板
使用模板,我们需要预先准备一个HTML文档,这个HTML文档不是普通的HTML, 而是嵌入了一些变量和指令,然后,根据我们传入的数据,替换后,得到最终的HTML,发送给用户:
这就是传说中的MVC:Model-View-Controller,中文名“模型-视图-控制器”。
Python处理URL的函数就是C:Controller,Controller负责业务逻辑,比如检查用户名是否存在,取出用户信息等等;
包含变量的模板就是V:View,View负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML。
MVC中的Model在哪?Model是用来传给View的,这样View在替换变量的时候,就可以从Model中取出相应的数据。
上面的例子中,Model就是一个dict:{ 'name': 'Michael' }
现在,我们把上次直接输出字符串作为HTML的例子用高端大气上档次的MVC模式改写一下:
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
return render_template('home.html')
@app.route('/signin', methods=['GET'])
def signin_form():
return render_template('form.html')
@app.route('/signin', methods=['POST'])
def signin():
username = request.form['username']
password = request.form['password']
if username=='admin' and password=='password':
return render_template('signin-ok.html', username=username)
return render_template('form.html', message='Bad username or password', username=username)
if __name__ == '__main__':
app.run()
Flask通过render_template()函数来实现模板的渲染。render_template的功能是对先引入index.html,同时根据后面传入的参数,对html进行修改渲染。
和Web框架类似, Python的模板也有很多种。Flask默认支持的模板是jinja2,所以我们先直接安装jinja2:
1 | pip install jinja2 |
然后,开始编写jinja2首页模板:
home.html
<html>
<head>
<title>Home</title>
</head>
<body>
<h1 style="font-style:italic">Home</h1>
</body>
</html>
用来显示登录表单的模板:
form.html
<html>
<head>
<title>Please Sign In</title>
</head>
<body>
{% if message %}
<p style="color:red">{{ message }}</p>
{% endif %}
<form action="/signin" method="post">
<legend>Please sign in:</legend>
<p><input name="username" placeholder="Username" value="{{ username }}"></p>
<p><input name="password" placeholder="Password" type="password"></p>
<p><button type="submit">Sign In</button></p>
</form>
</body>
</html>
其中: {{}}表示这是一个变量,可以根据用户在模块端给予的参数的不同,进行调整
登录成功的模板:
signin-ok.html
<html>
<head>
<title>Welcome, {{ username }}</title>
</head>
<body>
<p>Welcome, {{ username }}!</p>
</body>
</html>
登录失败的模板呢?我们在form.html中加了一点条件判断,把form.html重用为登录失败的模板。
最后,一定要把模板放到正确的templates目录下,templates和app.py在同级目录下
启动python app.py,看看使用模板的页面效果,不重复演示了
有了MVC,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率。
是不是对于jinja2 如何渲染html很疑惑,请看下面的例子:
ghtml.py
# cat ghtml.py
#!/usr/bin/env python
# coding=utf-8
# code from www.361way.com
import os
from jinja2 import Environment, FileSystemLoader
PATH = os.path.dirname(os.path.abspath(__file__))
TEMPLATE_ENVIRONMENT = Environment(
autoescape=False,
loader=FileSystemLoader(os.path.join(PATH, 'templates')),
trim_blocks=False)
def render_template(template_filename, context):
return TEMPLATE_ENVIRONMENT.get_template(template_filename).render(context)
def create_index_html():
fname = "output.html"
urls = ['http://www.361way.com/tag/python', 'http://www.361way.com/tag/linux', 'http://www.361way.com/tag/mysql']
context = {
'urls': urls
}
#
with open(fname, 'w') as f:
html = render_template('index.html', context)
f.write(html)
def main():
create_index_html()
########################################
if __name__ == "__main__":
main()
templates/index.html模板
# cat templates/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>generating html</title>
</head>
<body>
<center>
<h1>generating html</h1>
<p>{{ urls|length }} links</p>
</center>
<ol align="left">
{% for url in urls -%}
<li><a href="{{ url }}">{{ url }}</a></li>
{% endfor -%}
</ol>
</body>
</html>
执行后生成的内容
# cat output.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>generating html</title>
</head>
<body>
<center>
<h1>generating html</h1>
<p>3 links</p>
</center>
<ol align="left">
<li><a href="http://www.361way.com/tag/python">http://www.361way.com/tag/python</a></li>
<li><a href="http://www.361way.com/tag/linux">http://www.361way.com/tag/linux</a></li>
<li><a href="http://www.361way.com/tag/mysql">http://www.361way.com/tag/mysql</a></li>
</ol>
</body>
</html>
我对于Environment使用很疑惑,知道看到这样的代码段
#它的实例用来保存配置、全局对象,以及从本地文件系统或其它位置加载模板。
#autoescape XML/HTML自动转义,缺省为false. 就是在渲染模板时自动把变量中的<>&等字符转换为<>&。
jinja_environment = jinja2.Environment(autoescape=True, loader=jinja2.FileSystemLoader(
os.path.join(cwd, 'templates')))
另一个比较有意思的就是
模板继承
模板继承是Jinja中一个非常有用的功能。这个功能允许你创建一个包含有所有公共元素的页面基本骨架,在子模板中可以重用这些公用的元素。
使用模板继承其实很简单,下面我们开始用一个例子来介绍模板继承的用法。
基础模板
我们首先写一个名为"base.html"的模板,它包含下面的内容:
<pre>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
</pre>
在这个模板中有很多'block', 这些block中间的内容,我们将会在子模板中用其它内容替换。
子模板
我们再写一个名为"child.html"的模板,内容如下:
<pre>
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awsome homepage.
</p>
{% endblock %}
</pre>
:在这个模板的第一行,我们用{% extends "base.html" %}标明,这个模板将继承base.html.
在随后的内容中包含了很多跟base.html中相同的block,如title,content,这些block中的内容将会替换 base.html的内容后输出.
:extends后面的模板名称的写法依赖于此模板使用的模板加载器, 比如如果要使用FileSystemLoader,你可以在模板文件名中加入文件的文件夹名,如:
<pre>
{% extends "layout/default.html" %}
</pre>
在base.html中,我们定义了block “footer”,这个block在子模板中没有被重定义,那么Jinja会直接使用父模板中的内容输出。
另外要注意,在同一个模板中不能定义名称相同的block。
如果你要在模板中多次打印同一个block,可以用用self变量加上block的名字:
<pre>
<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1>
{% block body %}{% endblock %}
</pre>
和Python不同的地方是,Jinja不支持多继承。
super block
如果要在子模板中重写父模板的block中打印被重写的block的内容,可以调用super关键字。
<pre>
{% block sidebar %}
<h3>Table Of Contents</h3>
...
{{ super() }}
{% endblock %}
<pre>
还有对于
for
循环打印一个序列,例如:
<pre>
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
</pre>
在循环内部,你可以访问一些特殊的变量
if
if语句用来在Jinja中做比较判断,比较常见的用法是判断一个变量是否已定义,是否非空,是否为true
<pre>
{% if users %}
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
{% endif %}
</pre>
和python一样,也可以使用elif和else
<pre>
{% if kenny.sick %}
Kenny is sick.
{% elif kenny.dead %}
You killed Kenny! You bastard!!!
{% else %}
Kenny looks okay --- so far
{% endif %}
</pre>
if语句也可以被用来做内联表达式或者for语句过滤器。
另外Jinja2 简明使用手册也给了我很大的帮助
队友更多的API,有需要可以查阅Jinja2 的 API
对于更多的细节可查阅jinja2的中文文档