一、视图函数
现在数据库连接已经正常工作,我们可以开始编写视图函数。我们需要四个视图函数:
显示条目
这个视图显示所有存储在数据库中的条目。它监听着应用的根地址以及将会从数据库中查询标题和内容。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)
1. 添加新条目
这个视图允许登录的用户添加新的条目。它只回应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
)。
安全提示:
确保像上面例子中一样,使用问号标记来构建 SQL 语句。否则,当你使用格式化字符串构建 SQL 语句时, 你的应用容易遭受 SQL 注入。
3. 登录和注销
这些函数是用于用户登录以及注销。登录时依据在配置中的值检查用户名和密码并且在会话中设置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()
方法并传入第二个参数(默认), 这个方法会从字典中删除这个键,如果这个键不存在则什么都不做。这很有用,因为我们不需要检查用户是否已经登入。
@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
二、模版
现在我们应该开始编写模版。如果我们现在请求 URLs ,我们将会得到一个 Flask 无法找到模版的异常。 模版使用 Jinja2 语言以及默认开启自动转义。这就意味着除非你使用Markup
标记或在模板中使用|safe
过滤器, 否则 Jinja2 会确保特殊字符比如<
或>
被转义成等价的XML
实体。
我们使用模版继承使得在网站的所有页面中重用布局成为可能。
请把如下的模版放入templates
文件夹(当模板文件比较多的时候,推荐使用实验楼的桌面上的应用Brackets
编写模板文件):
1. 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>
2. 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 %}
3. 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 %}
三、添加样式
现在其他一切都正常工作,是时候给应用添加些样式。只要在我们之前创建的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; }
四、测试
最后运行flaskr.py,运行结果:
五、小结
本节中我们完成了最后的视图和模板,并加了css
样式表。
六、练习
有了这个简单的例子,请实现自己的一个小型博客吧!