轻量级web开发框架:Flask 实战

3 篇文章 9 订阅

Flask静态文件及渲染模板

请创建一个模板和CSS文件,并在模板引入CSS文件,当访问网站首页时显示一个绿色的Welcome to  my website!字样。

参考答案

文件目录结构如下所示:

/test.py
/templates
    /index.html
/static
    /index.css

hello.py 文件中的代码如下所示:

from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def get_welcome():
    return render_template('index.html')

templates/hello.html 存放的是前端代码,如下所示:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='index.css') }}">
</head>
<body>
    <h1>Welcome to  my website!</h1></body>
</html>

static/hello.css 存放的是 css 代码,如下所示:

h1{
    color: green;
    text-align: center;
}

文件上传

请实现一个上传图片到服务器的功能。具体要求如下:

  • 在目录下新建 upload_file.py 文件并在其中写入本练习的代码。
  • 要求上传的文件保存位置为upload_file.py的同级 目录下。
  • 当访问首页 http://127.0.0.1 时出现表单可以上传文件,当上传成功后在浏览器可以看到 <上传的文件名> upload successed!

参考代码

在 upload_file.py 文件中添加如下代码:

import os
from flask import Flask, request
from werkzeug import secure_filename   # 获取上传文件的文件名

UPLOAD_FOLDER = ''   # 上传路径
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])   # 允许上传的文件类型

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

def allowed_file(filename):   # 验证上传的文件名是否符合要求,文件名必须带点并且符合允许上传的文件类型要求,两者都满足则返回 true
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':   # 如果是 POST 请求方式
        file = request.files['file']   # 获取上传的文件
        if file and allowed_file(file.filename):   # 如果文件存在并且符合要求则为 true
            filename = secure_filename(file.filename)   # 获取上传文件的文件名
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))   # 保存文件
            return '{} upload successed!'.format(filename)   # 返回保存成功的信息
    # 使用 GET 方式请求页面时或是上传文件失败时返回上传文件的表单页面
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form action="" method=post enctype=multipart/form-data>
      <p><input type=file name=file>
         <input type=submit value=Upload>
    </form>
    '''
if __name__ = "_main__":
    app.run(debug = True)

Flask 用户登录

请实现一个完整的用户登录功能:

  • 当访问地址 http://127.0.0.1:5000/login ,出现登录页面,可以使用用户名和密码填写登录表单。
  • 如果用户名和密码都为admin,那么就把用户名数据放到session中,把地址重定向到首页显示Hello admin,同时闪现消息 you were logged in。
  • 如果用户名和密码不对,依然把地址重定向到首页显示hello world,同时闪现消息 username or password invalid。

参考答案

文件目录结构如下所示:

 

/login.py
/templates
    /index.html
    /login.html

在 login.py 文件中添加如下代码:

from flask import Flask, request
from flask import flash, redirect, url_for, render_template, session

app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/<name>')
def index(name):
    return render_template('index.html',name=name)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        if request.form['username'] != 'admin' or request.form['password'] != 'aadmin':
            flash('username or password invalid')
            return redirect(url_for('index', name='world'))
        else:
            session['username'] = request.form['username']
            name = request.form['username']
            flash('you were logged in')
            return redirect(url_for('index', name=name))
    return render_template('login.html')

在 templates/index.html 文件中添加如下代码:

<!doctype html>
<title>Index</title>
<div>
    {% for message in get_flashed_messages() %}
        <div class=flash>{{ message }}</div>
    {% endfor %}
    <h1>hello {{name}}</h1>
</div>

在 templates/login.html 文件中添加如下代码:

<!doctype html>
<title>Login</title>
<div>
    <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>
</div>

 

Flask项目实战之博客开发(1) 

1.1 项目内容

将介绍基于 Flask 的简单博客项目创建。

  • 项目创建
  • 基本配置

1.2 项目环境

  • Python 3.7
  • Flask 1.0.3

 

这里我们将博客应用起名为tkblog-flaskr,基本上我们想要它做如下的事情:

  1. 根据配置文件中的认证允许用户登录以及注销。仅仅支持一个用户。
  2. 当用户登录后,他们可以添加新的条目,这些条目是由纯文本的标题和 HTML 的正文构成。因为我们信任用户这里的 HTML 是安全的。
  3. 页面倒序显示所有条目(新的条目在前),并且用户登入后可以在此添加新条目。

我们将在这个应用中直接使用 SQLite 3 因为它足够应付这种规模的应用。对更大的应用使用 SQLAlchemy 是十分有意义的,它以一种更智能方式处理数据库连接,允许你一次连接多个不同的关系数据库。你也可以考虑流行的 NoSQL 数据库,前提是你的数据更适合它们。

最终应用的截图:

 

1.3创建文件夹

在开始之前,让我们为这个应用创建需要的文件夹:

/tkblog-flaskr
    /static
    /templates

其中:

  • tkblog-flaskr:项目总文件夹。其中会存放数据库模式代码文件 schema.sql 和 主代码文件 flaskr.py。
  • static:项目的资源文件。用于存放 css 和 javascript 文件。
  • templates:项目前端页面文件。用于存放前端页面模板。

1.4 创建数据库模式文件(schema.sql)

首先我们要创建数据库模式。对于这个应用仅一张表(entries)就足够了,而且我们只想支持 SQLite ,所以很简单。在 tkblog-flaskr 目录下新建 schema.sql 文件并向其中写入如下代码:

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

这个模式由一个称为entries的单表构成,在这个表中每行包含一个id,一个title和一个textid是一个自增的整数而且是主键,其余两个为非空的字符串。

1.5 应用设置代码

在 tkblog-flaskr 目录下新建 flaskr.py 文件。对于小应用,直接把配置放在主模块里,正如我们现在要做的一样,这是可行的。然而一个更干净的解决方案就是单独创建.ini或者.py文件接着加载或者导入里面的值。

在 flaskr.py 文件中写入如下代码:

# 导入所有的模块
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash

app = Flask(__name__)

app.config.from_object(__name__)
或者
import config
app.config.from_object('config')

from_object()将会寻找给定的对象(如果它是一个字符串,则会导入它),搜寻里面定义的全部大写的变量。

在我们的这种情况中,配置文件就是我们下面写的几行代码。 你也可以将他们分别存储到多个文件。比如将其放在config.py中:

DATABASE = 'flaskr.db'
ENV = 'development'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

 

通常从配置文件中加载配置是一个好的主意。这时可以使用from_envvar()来实现,可以用它替换上面的from_object():

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

这种方法我们可以设置一个名为FLASKR_SETTINGS的环境变量来设定一个配置文件载入后是否覆盖默认值。静默开关silent=True告诉 Flask 不去关心这个环境变量键值是否存在。

 

SECRET_KEY是为了保持客户端的会话安全。明智地选择该键,使得它难以猜测,最好是尽可能复杂。

DEBUG 调试标志启用或禁用交互式调试。决不让调试模式在生产系统中启动,因为它将允许用户在服务器上执行代码!

 

我们还添加了一个轻松地连接到指定数据库的方法,这个方法用于在请求时打开一个连接,并且在交互式 Python shell 和脚本中也能使用,这将方便后面的使用。

在  flaskr.py 文件中继续添加如下代码:

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

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

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

到目前为止flaskr.py的完整代码如下:

import sqlite3
from flask import Flask,render_template,flash,abort,g,request,session,redirect,url_for

DATABASE = '/flaskr.db'
ENV = 'development'
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'])

if __name__ = ‘__main__’:
    app.run()

本文介绍了一个简单 flask 项目的搭建,下一节中将创建数据库。

 

Flask项目实战之博客开发(2)

2.1 创建数据库

如前面所述,Flaskr 是一个数据库驱动的应用程序,准确地来说,Flaskr 是一个使用关系数据库系统的应用程序。这样的系统需要一个模式告诉它们如何存储信息。因此在首次启动服务器之前,创建数据库模式是很重要的。

添加一个函数来对数据库进行初始化是个不错的想法。如果你想要这么做,首先必须从contextlib包中导入contextlib.closing()函数。在 flaskr.py 文件中添加如下代码:

from contextlib import closing

接着我们可以创建一个称为 init_db 函数,该函数用来初始化数据库。为此我们可以使用之前定义的 connect_db 函数。 只要在 flaskr.py 文件中的 connect_db 函数下添加这样的函数:

def init_db():
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:
            db.cursor().executescript(f.read().decode())
        db.commit()

closing()函数允许我们在with块中保持数据库连接可用。

应用对象的open_resource()方法在其方框外也支持这个功能,因此可以在with块中直接使用。这个函数从当前位置(tkblog-flaskr 文件夹)中打开一个文件,并且允许你读取它。

我们在这里用它在数据库连接上执行一个脚本。

当我们连接到数据库时会得到一个数据库连接对象(这里命名它为 db),这个对象提供给我们一个数据库指针。指针上有一个可以执行完整脚本的方法。

2.2 请求数据库连接

所有我们的函数中都需要数据库连接,因此在请求之前初始化它们,在请求结束后自动关闭它们就很有意义。

Flask 允许我们使用before_request(),after_request()和 teardown_request()装饰器来实现这个功能,在flaskr.py 文件中添加如下代码:

 

Flask项目实战之博客开发(3)

登录

登录时依据在配置中的值检查用户名和密码并且在会话中设置logged_in键值。如果用户成功登录,logged_in键值被设置成True,并跳转回show_entries页面。此外,会有消息闪现来提示用户登录成功。

如果发生错误,模板会通知,并提示重新登录。在 flaskr.py 文件中添加如下代码:

@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   # 成功登录,在 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)   # 移除 logged_in 键
    flash('You were logged out')   # 闪现消息
    return redirect(url_for('show_entries'))   # 重定向到首页

模板

现在我们应该开始编写模板。如果我们现在请求 URLs ,将会得到一个 Flask 无法找到模板的异常。模板使用 Jinja2 语言以及默认开启自动转义。这就意味着除非你使用Markup标记或在模板中使用|safe过滤器,否则 Jinja2 会确保特殊字符比如<或>被转义成等价的XML实体。

我们使用模板继承使得在网站的所有页面中重用布局成为可能。

我们的模板统一存放在flaskr/templates文件夹中。

layout.html

这个模板包含 HTML 主体结构,标题和一个登录链接(或者当用户已登录则提供注销)。如果有闪现信息的话它也将显示闪现信息。{% block body %} 块能够被子模板中的同样名字(body)的块替代。

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

在 flaskr/templates 目录下新建 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>

show_entries.html

这个模板继承了上面的layout.html模板用来显示信息。注意for遍历了所有我们用render_template()函数传入的信息。我们同样告诉表单提交到add_entry函数通过使用 HTTP 的POST方法。

在 flaskr/templates 目录下新建 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 %}

login.html

最后是登录模板,基本上只显示一个允许用户登录的表单。

在 flaskr/templates 目录下新建 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 %}

添加样式

现在其它一切都正常工作,是时候给应用添加些样式。

在 Code/flaskr/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代码如下:

import sqlite3 as sql
from flask import Flask,render_template,flash,abort,g,request,session,redirect,url_for
from contextlib import closing
import config

#Flask 提供了两种环境(Context):应用环境(Application Context)和请求环境(Request Context)。
# 暂且你所需了解的是,不同环境有不同的特殊变量。
# 例如 request 变量与当前请求的请求对象有关,
# 而 g 是与当前应用环境有关的通用变量。


app = Flask(__name__)   #创建Flask应用
app.config.from_object('config')   #配置连接参数

#连接数据库的函数
def connect_db():
    return sql.connect(app.config['DATABASE'])

#执行schema.sql脚本来创建数据库初始化
def init_db():
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql') as f:
            db.cursor().executescript(f.read().decode())
        db.commit()

#Flask应用中数据库的连接
@app.before_request
def before_request():
    g.db = connect_db()

#Flask应用中数据库的断开
@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)

#视图函数——添加条目
@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'))

#视图函数——登入,通过与配置参数中的数据比较检查用户名和密码
@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   # 成功登录,在 session 中添加一个 logged_in 值为 True
            flash('You were logged in')   # 闪现一条消息
            return redirect(url_for('show_entries'))   # 重定向到首页
    return render_template('login.html', error=error)   # 如果没有成功登录则返回登录页面以及错误信息

#视图函数——注销
@app.route('/logout')
def logout():
    session.pop('logged_in', None)   # 移除 logged_in 键
    flash('You were logged out')   # 闪现消息
    return redirect(url_for('show_entries'))   # 重定向到首页


if __name__ == '__main__':
    init_db()
    app.run(debug = True)

运行应用

这样我们就完成了简单博客应用的代码编写,现在在终端执行如下命令运行程序。

访问首页 http://127.0.0.1:5000 ,效果如下:

点击首页的 log in 按钮,跳转到登录页面 http://127.0.0.1:5000/login,填入正确的用户名 admin 和密码 default,然后点击 Login 按钮进行表单提交,效果如下:

成功登录后的效果如下所示:

添加博客内容:

退出登录:

本文中我们完成了最后的视图和模板,并加了css样式表。

 

有了这个简单的例子,请实现一个自己的小型博客应用吧!

 

#Flask 提供了两种环境(Context):应用环境(Application Context)和请求环境(Request Context)。

# 暂且你所需了解的是,不同环境有不同的特殊变量。

# 例如 request 变量与当前请求的请求对象有关,

# 而 g 是与当前应用环境有关的通用变量。

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

使用before_request()装饰器的函数会在请求之前被调用而且不带参数。使用after_request()装饰器的函数会在请求之后被调用且传入将要发给客户端的响应。

它们必须返回那个响应对象或是不同的响应对象。但当异常抛出时,它们不一定会被执行,这时可以使用teardown_request()装饰器。它装饰的函数将在响应构造后执行,并不允许修改请求,返回的值会被忽略。如果在请求已经被处理的时候抛出异常,它会被传递到每个函数,否则会传入一个None。

我们把当前的数据库连接保存在 Flask 提供的 g 特殊对象中。这个对象只能保存一次请求的信息,并且在每个函数里都可用。不要用其它对象来保存信息,因为在多线程环境下将不可行。特殊的对象 g 在后台有一些神奇的机制来保证它在做正确的事情。

本文介绍创建数据库,接下来我们开始编写视图函数

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值