创建应用教程
创建文件夹
先创建应用所需的文件夹,接下来会直接把数据库模式和主模块放在这个目录中。用户可以通过HTTP访问static文件夹中的文件,即存放css和javascript文件的地方:
1 2 3 /tutorweb /static /templates
创建数据库模式
创建schema.sql文件,文件里编写以下内容,放到tutorweb文件中:
1 2 3 4 5 6 drop table if exists entries;create table entries( id integer primary key autoincrement, title string not null , text string not null );
该模式包含一个名为entries的表,该表中的每行都包含一个id、一个title和一个text。id是一个自增的整数,也是主键,其余的两个是字符串且不允许为空。
应用设置代码
创建应用模块
创建应用的模块tutorweb.py并放置在tutorweb目录下。从添加所需的导入语句和添加配置部分开始。对于小型应用,可以直接把配置放在主模块里,但更简洁的方案是创建独立的.ini
或.py
文件,并载入里面的值。
首先在tutorweb.py里导入内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import osimport sqlite3from flask import Flask,request,session,g,redirect,url_for,abort,\ render_template,flash DATABASE='./sqlite_db' USERNAME='admin' PASSWORD='default' DEBUG=True app=Flask(__name__) app.config.from_object(__name__) app.secret_key='develop key'
数据库路径
操作系统有进程当前工作目录的概念,但由于在Web应用中相同的进程运用多个应用,因此不能依赖此概念。Flask提供了app.root_path
属性以获取应用的路径,配合os.path
模块的使用,可以轻松到达任意文件。在本例中,数据库放在根目录下。
通常只能加载一个单独的、环境特定的配置文件。Flask中使用from_envvar()
方法可以实现允许导入多份配置文件,并使用最后的导入的设置。
1 app.config.from_envvar('FLASKR_SETTINGS' ,slient=True )
只需要设置一个名为FLASKR_SETTINGS
的环境变量指向要加载的配置文件,启动静默模式告诉Flask在没有设置该环境变量的情况下噤声。
此外还可以使用配置对象上的from_object()
方法,并传递一个模块的导入名作为参数。Flask会从这个模块初始化变量,注意只有名称全为大写字母的变量才会被采用 。
secret_key
是保证客户端会话安全的重要所在。尽量选择一个尽可能难猜测、尽可能复杂的密钥。
调试标志关系交互式调试器的开启。永远不要在生产系统中激活调试模式 ,因为它将允许用户在服务器上执行代码。
添加一个连接到指定数据的方法是,用于请求时开启一个数据库连接,并在交互式Python shell和脚本中也能使用。在tutorweb.py文件中创建一个简单的SQLite数据库的连接,并让它用sqlite3.Row表示数据库中的行,使得可以通过字典而不是元组的形式访问行:
1 2 3 4 5 def connect_db (): rv=sqlite3.connect(app.config['DATABASE' ]) rv.row_factory=sqlite3.Row return rv
最后,若想要把这个文件当作独立应用来运行,只需在可启动服务器文件tutorweb.py的末尾加上这行代码即可运行这个应用:
1 2 if __name__=='__main__' : app.run()
此时在浏览器中访问服务器会遇到404 page not found错误,这是因为还没有创建任何视图的缘故。在创建视图之前,应该先让数据库工作起来。
数据库连接
Flask提供了两种环境:应用环境和请求环境。不同环境有不同的特殊变量。例如request变量与当前请求的请求对象有关,而g是当前应用环境有关的通用变量。
在tutorweb.py文件中可以写一个辅助函数把数据库连接存放在g对象上。这个函数首次调用时会为当前的函数创建一个数据库连接,调用成功后返回已经建立好的连接:
1 2 3 4 5 def get_db (): if not hasattr (g,'sqlite_db' ): g.sqlite_db=connect_db() return g.sqlite_db
Flask提供了teardown_appcontext()
装饰器来断开数据库连接,它将在每次应用环境销毁时执行:
1 2 3 4 5 @app.teardown_appcontext def close_db (error ): if hasattr (g,'sqlite_db' ): g.sqlite_db.close()
本质上,应用环境在请求传入前创建,每当请求结束时销毁。销毁有两种原因:一切正常(错误参数是None)或发送异常,后者错误会被传递给销毁函数。
创建数据库
Flask是一个由关系数据库驱动的应用。关系数据库系统需要一个模式来决定存储信息的方式。
创建模式
可以通过管道把schema.sql作为sqlite3命令的输入来创建这个模式:
1 sqlite3 /tmp/flaskr.db < schema.sql
这种方法的缺点是需要安装sqlite3命令,且必须提供数据库的路径,否则会报错。因此比较推荐使用函数来初始化数据库。
创建一个名为init_db的函数来初始化数据库,只需要把这个函数放在tutorweb.py里的connect_db函数后面并调用init_db()
函数来创建数据库:
1 2 3 4 5 6 7 8 9 def init_db (): with app.app_context(): db=get_db() with app.open_resource('schema.sql' ,mode='r' ) as f: db.cursor().executescript(f.read()) db.commit() init_db()
应用环境在每次请求传入时创建,这里并没有请求,故需要手动创建一个应用环境。g在应用环境外无法获知它属于哪个应用,因为可能会有多个应用同时存在。
SQLite的数据库连接对象提供了一个游标对象,游标上有一个方法可以执行完整的脚本。最后只需提交变更,SQLite3和其他支持事务的数据库只会在显示提交的时候提交。
视图函数
数据库连接正常工作后,共需要写四个视图函数。
显示条目
这个视图显示数据库中存储的所有条目,它绑定在应用的根地址,并从数据库查询出文章的标题和正文。id值最大的条目(最新的条目)会显示在最上方。从指针返回的行是按select语句中声明的列组织元组。
视图函数会将条目作为字典传递给show_entries.html
模板,并返回渲染结果:
1 2 3 4 5 6 7 @app.route('/' ) def show_entries (): g.db=get_db() 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' ,entries=entries)
添加条目
这个视图允许已登入的用户添加新条目,并只响应POST请求,实际的表单显示在show_entries
页。若一切工作正常,用flash()向下一次请求发送提示消息,并重定向回show_entries
页:
1 2 3 4 5 6 7 8 9 10 11 @app.route('/add' ,methods=['POST' ] ) def add_entry (): g.db=get_db() 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 successfully posted' ) return redirect(url_for('show_entries' ))
注意这里的用户登入检查(logged_in键在会话中存在且为True)
安全提示:
确保像上面代码一样使用问号标记来构建SQL语句。否则当使用格式化字符串构建SQL语句时,所建立的应用容易遭受SQL注入。
登入和登出
这个函数用来让用户登入。登入通过与配置文件中的数据比较检查用户名和密码,并设定会话中的logged_in
键值。若用户成功登入,这个键值会被设为True,并跳转回show_entries
页。此外还会有信息闪现来提示用户登入成功。如果发生一个错误,模板会通知并提示重新登录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @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()方法并传入第二个参数(默认),以从字典删除这个键,若这个键不存在则什么都不做。
1 2 3 4 5 6 @app.route('/logout' ) def logout (): session.pop('logged_in' ,None ) flash('You were logged out' ) return redirect(url_for('show_entries' ))
模板
若现在请求URL,只会得到Flask无法找到模板的异常。模板使用Jinja2语法并默认开启自动转义。这意味着除非使用Markup标记或在模板中使用|safe
过滤器,否则Jinja2会确保特殊字符,比如<
或>
被转义为等价的XML实体。
layout.html
将下面的模板放进templates文件夹里。这个模板包含HTML主体结构、标题和一个登入链接(用户已登入则提供登出)。若有,它会显示闪现消息。
模块可以被子模版中相同名字的块(body)替换。
session字典在模板中也是可用的,可以用它来检查用户是否已登入。在Jinja中你可以访问不存在的对象、字典属性或成员 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!doctype html > <title > tutorweb</title > <link rel =stylesheet type =text/css href ="{{url_for('static',filename='style.css')}}" > <div class =page > <h1 > tutorweb</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()
函数传入的消息。还告诉表单使用HTTP的POST方法提交信息到add_entry
函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 {% 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 %}
login.html
最后是登入模板,简单地显示一个允许用户登入的表单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 {% extends "layout.html" %} {% block body %} <h2 > Login</h2 > {% if error %}<p class =error > <strong > Error:</strong > {{error}}{% endif %} <form action ="{{url_for('login')}}" method =post > <dl > <dt > Username: <dd > <input type =text name =username > <dt > Password <dd > <input type =password name =password > <dd > <input type =submit value =Login > </dl > </form > {% endblock %}
添加样式
现在其他的一切都可以正常工作,是时候给应用添加样式了。只需在static文件夹中创建一个名为style.css的样式表即可:
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 body { font-family :sans-serif; background :#eee ; } a ,h1 ,h2 { color :#377BAB ; } h1 ,h2 { font-family :'Georgia' ,serif; margin :0 ; } h1 { border-bottom :2px solid #eee ; } h2 { font-size :1.2em ; } .page { margin :2em auto; width :35em ; border :5px solid #ccc ; padding :0.8em ; background :white; } .entries { list-style :none; margin :0 ; padding :0 ; } .entries li { margin :0.8em 1.2em ; } .entries li h2 { margin-left :-1em ; } .add-entry { font-size :0.9em ; border-bottom :1px solid #ccc ; } .add-entry dl { font-weight :bold; } .metanav { text-align :right ; font-size :0.8em ; padding :0.3em ; margin-bottom :1em ; background :#fafafa ; } .toturweb { background :#CEE5F5 ; padding :0.5em ;; border :1px solid #AACBE2 ; } .error { background :#F0D6D6 ; padding :0.5em ; }
最后运行tutorweb.py程序即可在127.0.0.0:5000
上看到创建的应用。