/flaskr
/static
/template
用户可以通过HTTP访问static文件夹中的文件,这里也是css和javascript文件存放位置,flask将会在templates文件夹中寻找Jinja2模板,
要支持SQLite,将下列文件放入schema.sql
drop table if exists entries;
create table entries(
id interger primary key autoincrement,
title string not null,
text string not null);
在flaskr.py中,
import sqlite3
from flask import Flask,request,session,g,redirect,url_for,abort,render_template,flash
#configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'
创建应用,在同一文件中配置初始化:
app = Flask(__name__)
app.config.from_object(__name__)
from_object()将会寻找给定的对象(如果他是一个字符串),搜寻里面定义的全部大写的变量,
从配置文件加载配置是from_envvar()所做的,用于替换from_object()
app.config.from_envvar('FLASKr_SETTINGS',silent=True)
这种设置方法我们可以设置一个名为FLASKR_SETTINGS环境变量来设定一个配置文件载入之后是否覆盖默认值,静默开关告诉FLask不去关心这个环境变量键值是否存在
secret_key是需要为了保持客户端的会话安全,
添加一个轻松连接到指定数据库的方法,这个方法用于请求时打开一个连接.
def connect_db():
return sqlite3.connect(app.config['DATABASE'])
如果想要把那个文件当做独立应用来运行,只需要加上:
if __name__ == '__main__':
app.run()
顺利开始运行这个应用.使用如下命令:
python flaskr.py
创建数据库
Flaskr是一个使用关系型数据库的应用程序,这样的系统需要一个模式告诉他们如何存储信息,因此在首次启动服务器之前,创建数据库模式,可以通过管道把schema.sql作为sqlite3命令的输入来创建这个模式,命令如下:
sqlite3 / tmp/flaskr.db <schema.sql>
这种方法的缺点是需要安装sqlite3命令,而并不是每个系统都有安装,必须提供数据库的路径,否则将报错,
可以添加一个函数来初始化数据库
首先从contextlib包中导入contextlib.closing()函数,并且在flaskr.py文件中添加如下的内容;
from contextlib import closing
接着可以创建一个称为init_db函数,该函数用来初始化数据库,我们可以使用之前定义的connect_db函数,添加这样的函数:
def init_db():
with closing(connect_db()) as db:
with app.open_resource('schema.sql') as f:
db.cursor().executescript(f.read())
db.commit()
closing()助手函数允许我们在with块中保持数据库连接可用,应用对象的open_resource()方法在其方框外也支持这个功能,因此可以在with块中直接使用,这个函数从资源位置(你的flaskr文件夹)中打开一个文件,并且允许读取
当我们连接到数据库时会得到一个数据库连接对象,这个对象提供给我们一个数据库指针,
请求数据库连接:
在函数中需要数据库连接,在请求之前初始化他们,请求结束后自动关闭
Flask允许我们使用before_request(),after_request()和teardown_request()装饰器来实现这个功能:
@app.before_request
def before_request():
g.db = connect_db()
@app.teardown_request
def teardown_request(exception):
g.db.close()
使用before_request()装饰器的函数会在请求之前被调用而且不带参数.使用after_request()装饰器的函数会在请求之后被调用且传入将要发给客户端的响应.他们必须返回那个响应对象或是不同的响应对象,但当异常被抛出时,他们不一定会被执行,并不允许修改请求,返回的值会被忽略.如果在请求已经被处理的时候抛出异常,他会被传递到每个函数,否则会传入一个None
我们把当前的数据库连接保存在Flask提供的g特殊对象中,这个对象只能保存一次请求的信息,并且在每个函数里都可用,不要用其他对象来保存信息,因为在多线程环境下将不再可行,特殊的对象g在后台有一些神奇的机制来保证他在做正确的事情.
视图函数
显示条目:
这个视图显示所有存储在数据库的条目,他监听着应用的根地址以及会从数据库中查询标题和内容,id值最大的条目将在前面,从游标返回行是按select语句中声明的列组织的元组,视图函数将会把条目作为字典传入show_entries.html模板及返回渲染结果
@app.route('/')
def show_entries():
cur = g,db.execute('select title,text from entries order by id desc')
entries = [dict(title=row[0],text=row[1])for row in cur.fetchall())]
return render_template('show_entries.html',enries=entries)
添加新条目
这个视图允许登录的用户添加新的条目,他只回应POST请求,实际的表单是显示在show_entries页面,如果一些工作正常,我们用flash()向下一个请求闪现一条信息并且跳转回show_entries页:
@app.route('/add',methods=['POS'])
def add_entry():
if not session.get('logged_in'):
abort(401)
g.db.execute('insert into entries(title,text) values (?,?)',[request.form['title'],request.form['text']])
g.db.commit()
flash('New entry was successful posted')
return redirect (url_for('show-entries'))
登录和注销
这些函数是用于用户登录以及注销,依据在配置中的值登录时检查用户名和密码并且在会话中设置logged_in键值,如果用户登录成功,logged_in键值被设置成True,并跳转回show_entries页,此外,会有消息闪现来提示用户登入成功,如果发生错误,模板会通知并提示重新登录:
@app.route('/login',methods=['GET','POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAMe']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSworD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('show_entries'))
return render_template('login.html',error=error)
另一方面注销函数会从会话中移除了logged_in键值,这里我们使用一个大绝招,如果你使用字典的pop()方法并传入第二个参数(默认),这个方法会从字典中删除这个键,如果这个键不存在则什么都不做,我们不需要检查用户是否登录
@aa.route('/lgout')
def logout():
session.pop('logged_in',None)
flash('You were logged out')
return redirect(url_for('show_entries'))
模板
模板使用Jinja2语言以及默认开启自动转义,意味着除非使用Markup标记或在模板中使用|safe过滤器,否则jinja2会确保特殊字符比如<或>被转义成等价的XML实体,session字典在模板中同样可用的,注意在Jinja中你可以访问不存在的对象/字典属性或成员,及时键不存在仍然可以正常工作
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> <div class=page> <h1>Flaskr</h1> <div class=metanav> {% if not session.logged_in %} <a href="{{ url_for('login') }}">log in</a> {% else %} <a href="{{ url_for('logout') }}">log out</a> {% endif %} </div> {% for message in get_flashed_messages() %} <div class=flash>{{ message }}</div> {% endfor %} {% block body %}{% endblock %} </div>
show_entries.html
这个模板继承了上面的layout.html模板来显示信息,注意for遍历了所有用render_template()函数传入的信息,同样告诉表单提交到add_entry函数且使用HTTP的POST方法:
{% extends "layout.html" %}
{% block body %} {% if session.logged_in %} <form action="{{ url_for('add_entry') }}" method=post class=add-entry> <dl> <dt>Title: <dd><input type=text size=30 name=title> <dt>Text: <dd><textarea name=text rows=5 cols=40></textarea> <dd><input type=submit value=Share> </dl> </form> {% endif %} <ul class=entries> {% for entry in entries %} <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }} {% else %} <li><em>Unbelievable. No entries here so far</em> {% endfor %} </ul> {% endblock %}