flask 第一个小应用Flaskr

首先声明,不知是否个人原因,跟着[flask0.10.1文档][1] 做的完全不对(这个文档长得像官网文档,翻译过来的实际不太对)最后是跟着[官方文档][2]才做出来的,真的坑。

所需用到的:

  1. python3.*
  2. virtualenv
  3. flask
  4. sqlite3

步骤0:创建文件夹(亦可理解为创建项目)

结构如下:

/flaskr --项目名称和项目的根目录
    /static --短暂的理解为存放样式的地方
    /templates --页面模板

flaskr 文件夹不是一个 Python 包,只是个我们放置文件的地方。在接下来的步骤中,我们会直接把数据库模式和主模块放在这个目录中。 用户可以通过 HTTP 访问 static 文件夹中的文件,也即存放 css 和 javascript 文件的地方。Flask 会在 templates 文件夹里寻找 Jinja2 模板,之后教程中创建的模板将会放在这个文件夹里。

步骤1:数据库模式(亦可理解为数据库表)

由于应用很简单,所以简单的创一张表就行。把以下的内容放进 schema.sql,并且放在项目根目录

drop table if exists entries;
create table entries (
  id integer primary key autoincrement,
  title string not null,
  text string not null
);

步骤2:应用程序代码部分

有了数据库表,开始创建应用的模块了。在这里我们把它叫做flaskr.py,并放在根目录下,我们从添加所需的导入语句和添加配置部分开始。对于小型应用,可以直接把配置放在主模块里,正如我们现在要做的一样。但更简洁的方案是创建独立的 .ini 或 .py 文件,并载入或导入里面的值。

In flaskr.py:

# all the imports
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'

Next we can create our actual application and initialize it with the config from the same file, in flaskr.py:

# create our little application :)创建我们的小应用程序:)--实例化
app = Flask(__name__)
app.config.from_object(__name__)

通常,加载一个单独的、环境特定的配置文件是个好主意。Flask 允许你导入多份配置,并且使用最后的导入中定义的设置。这使得配置设定过程更可靠。 from_envvar() 可用于达此目的。

app.config.from_envvar('FLASKR_SETTINGS', silent=True)

关于这里 官网还有一长串的解释,看不懂。[传送门][3]

连接数据库

我们也添加一个方法方便地连接到指定的数据库。可以用来打开一个连接请求,也从Python交互式shell或脚本。以后这将派上用场。

def connect_db():
    return sqlite3.connect(app.config['DATABASE'])

最后,如果我们想要把这个文件当做独立应用来运行,我们只需在可启动服务器文件的末尾添加这一行:

if __name__ == '__main__':
    app.run()

如此我们便可以开始顺利运行这个应用,使用如下命令:

python flaskr.py

步骤3:创建数据库

Flaskr之前是一个数据库驱动的应用程序中,更准确地说,一个应用程序由一个关系数据库系统。这样的系统需要一个模式,告诉他们如何存储这些信息。所以在第一次启动服务器创建该模式是很重要的。

可以通过管道把 schema.sql 作为 sqlite3 命令的输入来创建这个模式,命令为如下:

sqlite3 /tmp/flaskr.db < schema.sql

这种方法的缺点是需要安装 sqlite3 命令,而并不是每个系统都有安装。而且你必须提供数据库的路径,否则将报错。用函数来初始化数据库是个不错的想法。

如果你想这样做,你首先必须导入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', mode='r') as f:
            db.cursor().executescript(f.read())
        db.commit()

关于这个方法,[官网][4]也有详细的说明,英语不好不照搬了

有了这个函数,就在python交互环境中初始化数据库

>>> from flaskr import init_db
>>> init_db()

步骤4:Request Database Connections

知道如何打开数据库连接,如何请求,

Flask allows us to do that with the before_request(), after_request() and teardown_request() decorators:

@app.before_request
def before_request():
    g.db = connect_db()

@app.teardown_request
def teardown_request(exception):
    db = getattr(g, 'db', None)
    if db is not None:
        db.close()

Functions marked with before_request() are called before a request and passed no arguments. Functions marked with after_request() are called after a request and passed the response that will be sent to the client. They have to return that response object or a different one. They are however not guaranteed to be executed if an exception is raised, this is where functions marked with teardown_request() come in. They get called after the response has been constructed. They are not allowed to modify the request, and their return values are ignored. If an exception occurred while the request was being processed, it is passed to each function; otherwise, None is passed in.

We store our current database connection on the special g object that Flask provides for us. This object stores information for one request only and is available from within each function. Never store such things on other objects because this would not work with threaded environments. That special g object does some magic behind the scenes to ensure it does the right thing.

For an even better way to handle such resources see the Using SQLite 3 with Flask documentation.

步骤5:创建视图函数

显示条目

这个视图显示数据库中存储的所有条目。它绑定在应用的根地址,并从数据库查询出文章的标题和正文。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', entries=entries)

添加条目

这个视图允许已登入的用户添加新条目,并只响应 POST 请求,实际的表单显示在 show_entries 页。如果一切工作正常,我们会用 flash() 向下一次请求发送提示消息,并重定向回 show_entries 页:

@app.route('/add', methods=['POST'])
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 successfully posted')
    return redirect(url_for('show_entries'))

登入和登出

这些函数用来让用户登入登出。登入通过与配置文件中的数据比较检查用户名和密码, 并设定会话中的 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() 方法并传入第二个参数(默认), 这个方法会从字典中删除这个键,如果这个键不存在则什么都不做。这很有用,因为我们不需要检查用户是否已经登入。

@app.route('/logout')
def logout():
    session.pop('logged_in', None)
    flash('You were logged out')
    return redirect(url_for('show_entries'))

步骤6:模板(前端页面)

接下来我们应该创建模板了。如果我们现在请求 URL,只会得到 Flask 无法找到模板的异常。 模板使用 Jinja2 语法并默认开启自动转义。这意味着除非你使用 Markup 标记或在模板中使用 |safe 过滤器,否则 Jinja 2 会确保特殊字符,比如 < 或 > 被转义为等价的 XML 实体。
我们也会使用模板继承在网站的所有页面中重用布局。

将下面的模板放在 templates 文件夹里:

layout.html

这个模板包含 HTML 主体结构、标题和一个登入链接(用户已登入则提供登出)。 如果有,它也会显示闪现消息。 {% block body %} 块可以被子模板中相同名字的块( body )替换。

session 字典在模板中也是可用的。你可以用它来检查用户是否已登入。 注意,在 Jinja 中你可以访问不存在的对象/字典属性或成员。比如下面的代码, 即便 'logged_in' 键不存在,仍然可以正常工作:

<!doctype html>
<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() 函数传入的消息。我们还告诉表单使用 HTTP 的 POST 方法提交信息到 add_entry 函数:

{% 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>

login.html

最后是登入模板,只是简单地显示一个允许用户登入的表单:

{% 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 %}

步骤7:添加样式表

现在其它的一切都可以正常工作,是时候给应用添加样式了。只需在之前创建的 static 文件夹中创建一个名为 style.css 的样式表:

body            { font-family: sans-serif; background: #eee; }
a, h1, h2       { color: #377BA8; }
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; }
.flash          { background: #CEE5F5; padding: 0.5em;
                  border: 1px solid #AACBE2; }
.error          { background: #F0D6D6; padding: 0.5em; }

如何登陆? 一开始在application code中就定义了。
USERNAME = 'admin'
PASSWORD = 'default'
[1]: http://docs.jinkan.org/docs/flask/
[2]: http://flask.pocoo.org/docs/0.10/
[3]: http://flask.pocoo.org/docs/0.10/tutorial/setup/#tutorial-setup
[4]: http://flask.pocoo.org/docs/0.10/tutorial/dbinit/#tutorial-dbinit

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,156评论 22 257
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 本文首发于Gevin的博客 原文链接:Flask 入门指南 未经 Gevin 授权,禁止转载 1. 初识Flask...
    Gevin阅读 16,695评论 10 237
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,057评论 25 707
  • 第二部分 Blog例子 第八章 用户验证 大部分程序需要追踪用户身份。当用户连接到程序,通过一系列步骤使自己的身份...
    易木成华阅读 1,275评论 0 4