项目功能
这个小程序可以实现用户(仅一个,且在程序中已经规定了用户名和密码)的登录和注销,可以显示数据库中 的条目。
涉及到的知识点
涉及到了配置文件、数据库的初始化、连接和断开,表单的提交读取等等。
需要注意的
要仔细想想页面之间的跳转,整个程序的构架。
程序流程
1. 运行程序后,打开 http://127.0.0.1:5000/ 页面,出现的是一下界面
显示的是show_entries.html模块的内容。
show_entries.html模块
{% extends "layout.html" %} <!--模板继承 ,继承layout.html的内容-->
{% block body %}
{% if session.logged_in %} <!--初次登录,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 %}
关于模板继承
模板继承允许你创建一个基础的骨架模板, 这个模板包含您网站的通用元素,并且定义子模>板可以重载的 blocks 。使用 {% block %} 标签定义了四个子模板可以重载的块。 block 标签所做的的所有事情就是告诉模板引擎: 一个子模板可能会重写父模板的这个部分。
{% extends %} 是关键,它会告诉模板引擎这个模板继承自另一个模板的, 模板引擎分析这个模板时首先会定位其父父模板。extends 标签必须是模板的首个标签。 想要渲染父模板中的模板需要使用 {{ super() }}。
更多:
- 点开log in后,页面跳转到’/login’
画面上显示的是login.html模板的内容,填好表单后按下’Login’,提交表单,执行login()函数。
login.html模板
{% extends "layout.html" %} <!--模板继承-->
{% block body %} <!--重载-->
<h2>Login</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<!-- 即error不为None时,就显示出错误原因。-->
<form action="{{ url_for('login') }}" method=post> <!--action后面是form表单提交的地址 -->
<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 %}
login()函数
@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) # 用login.html模板通知,提示重新登录
3.登录成功后,会出现以下界面。
画面上显示的是show_entries.html的内容。
{% extends "layout.html" %} <!--模板继承 ,继承layout.html的内容-->
{% block body %}
{% if session.logged_in %} <!--session.logged_in为True,显示以下内容-->
<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 %}
- 填写好内容按下Share以后,执行add_entry()函数。
@app.route('/add',methods=['POST']) # 只回应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']])
# 使用问号标记来构建 SQL 语句。否则,当使用格式化字符串构建 SQL 语句时, 应用容易遭受 SQL 注入。
g.db.commit()
flash('New entry was successfully posted') # 用flash()向下一个请求闪现一条信息
return redirect(url_for('show_entries')) # 重定向,跳转回show_entries页
7. 点击log out,执行logout()函数
logout()函数
@app.route('/logout')
def logout():
session.pop('logged_in', None) # 不需要检查用户是否已经登入,因为如果不存在则什么都不做
flash('You were logged out')
return redirect(url_for('show_entries'))
执行后,就回到了show_entries.html设置的页面。
编程过程中遇到的问题汇总
1.无法利用初始化创建数据库。
原始代码
from __future__ import with_statement #__future__导入必须先于其他的导入
import sqlite3
from flask import Flask
from contextlib import closing
DATABASE = 'G:/python/flask/flaskr/tmp/flaskr.db'
app = Flask(__name__)
app.config.from_object(__name__)
def connect_db():
return sqlite3.connect(app.config['DATABASE'])
def init_db():
with closing(connect_db()) as db:
with open('schema.sql') as f:
db.cursor().executescript(f.read())
db.commit()
init_db()
程序运行后,会出现以下报错:
原因
DATABASE的路径有问题,在调用init_db()函数前,flaskr目录下并没有tmp这个文件夹,程序也可创建数据库,却没办法创建一个文件夹。所以指令无法执行。
解决方法
修改路径,将’/tmp’去掉或者在flaskr文件夹中建立一个tmp文件夹,运行后即可生成flaskr.db文件。
2. 400 Bad Request报错
问题描述:
登录页面提交时,无法提交成功,出现该报错。
解决
400 Bad Request,该状态码表示请求报文中存在语法错误。
所以就检查了一下login()函数,发现果然有一处拼写错误,将“username”拼成了“usename”,修改完之后,运行程序,就可以登录了。
3. 关于配置的用户名和密码
问题描述:
在登录界面时,由于这个小程序是已经规定好了只有一个用户可以使用,也在程序中规定了用户名和密码。但是我一直没找到用户名和密码。。。最后才想起来好像在开头的时候配置过用户名和密码,这才登录成功。
# 一直在里找密码。。。完全忘记之前配置过USERNAME和PASSWORD了
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
#如果从表单中读取到的username和之前配置的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'))
之前配置的USERNAME和PASSWORD
#配置文件
DATABASE = 'G:/python/flask/flaskr/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'
4. 应用全局变量
只在一个请求内,从一个函数到另一个函数共享数据,全局变量并不够好。因为这在线程环境下行不通。Flask提供了一个特殊的对象来确保只在活动中的请求有效,并且每个请求都返回不同的值。
flask.g
在这上存储你任何你想要存储的。例如一个数据库连接或者当前登入的用户。
从 Flask 0.10 起,对象 g 存储在应用上下文(?)中而不再是请求上下文中(?),这意味着即使在应用上下文中它也是可访问的而不是只能在请求上下文中。在结合 伪造资源和上下文 模式使用来测试时这尤为有用。
另外,在 0.10 中你可以使用 get() 方法来获取一个属性或者如果这个属性没设置的话将得到 None (或者第二个参数)。 这两种用法现在是没有区别的:
user = getattr(flask.g, 'user', None)
user = flask.get.get('user', None)
现在也能在 g 对象上使用 in 运算符来确定它是否有某个属性,并且它将使用 yield 关键字来生成这样一个可迭代的包含所有keys的生成器。
5. 上下文(context)
Flask从客户端接收请求时,要让视图函数能访问一些对象,这样才能处理请求。请求对象就是一个很好的例子,它封装了客户端发送HTTP请求。
要想让视图函数能够访问请求对象,可以将其作为参数传进视图函数。不过这会导致程序中的每个视图函数都要增加一个参数。除了访问请求对象,如果视图函数在请求时还要访问其他对象,情况会变得更糟糕。
为了避免大量可有可无的参数把视图函数弄得一团糟,Flask 使用上下文临时把某些对象变为全局可访问。有了上下文,就可以写出下面的视图函数:
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>Your browser is %s</p>' % user_agent
这个视图函数中我们把request当作全局变量使用。事实上,request不可能是全局变量。试想,在多线程服务器中,多个线程同时处理不同客户端发送的不用请求时,每个线程看到的request对象必然不同。Flask使用上下文让特定的变量在一个线程中全局可访问,与此同时却不会干扰其他进程。
F
lask有两种上下文:应用上下文( application context )和请求上下文(request context)。Flask在分发请求之前激活(或推送)应用和请求上下文,请求处理完成后再将其删除。应用上下文被推送后,就可以在线程中使用current_app和g变量。类似地,请求上下文被推送后,就可以使用request和session变量。
关于Flask中的before_request、after_request,teardown_request
before_request :在请求收到之前绑定一个函数做一些事情。
after_request:每一个请求之后绑定一个函数,如果请求没有异常。
teardown_request:每一次请求后绑定一个函数,即使遇到了异常。
6. execute()
将字符串内容当作命令来执行,即执行原生的SQL语句。
cur = g.db.execute('select title,text from entries order by id desc')
- desc–降序(descend)
- asc – 升序(ascend)
程序代码
flask2.py
#导入所有模块
from __future__ import with_statement #__future__导入必须先于其他的导入
import sqlite3
from flask import Flask,request,session,g,redirect,url_for,abort,render_template,flash
from contextlib import closing
#配置文件
DATABASE = 'G:/python/flask/flaskr/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'
#创建应用
app = Flask(__name__)
app.config.from_object(__name__)
def connect_db():
return sqlite3.connect(app.config['DATABASE']) #使用配置
#初始化数据库
def init_db():
with closing(connect_db()) as db:
with open('schema.sql') as f:
db.cursor().executescript(f.read())
db.commit()
#在请求收到之前连接数据库
@app.before_request
def before_request():
g.db = connect_db()
@app.teardown_request
def teardown_request(exception):
g.db.close()
@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)
#添加新条目,实际的表单是显示在show_entries页面
@app.route('/add',methods=['POST']) # 只回应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']])
# 使用问号标记来构建 SQL 语句。否则,当使用格式化字符串构建 SQL 语句时, 应用容易遭受 SQL 注入。
g.db.commit()
flash('New entry was successfully posted') # 用flash()向下一个请求闪现一条信息
return redirect(url_for('show_entries')) # 重定向,跳转回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) # 用login.html模板通知,提示重新登录
#注销
@app.route('/logout')
def logout():
session.pop('logged_in', None) # 不需要检查用户是否已经登入,因为如果不存在则什么都不做
flash('You were logged out')
return redirect(url_for('show_entries'))
init_db()
if __name__=='__main__':
app.run()
layout.html
<!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>
login.html
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<!-- 即error不为None时,就显示出错误原因。-->
<form action="{{ url_for('login') }}" method=post> <!--action后面是form表单提交的地址 -->
<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 %}
show_entries.html
{% 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 %}