Python Flask 建站框架入门课程-微课学习-第三章 进阶

上传文件

用 Flask 处理文件上传很简单。只要确保你没忘记在 HTML 表单中设置 enctype=“multipart/form-data” 属性,不然你的浏览器根本不会发送文件。

已上传的文件存储在内存或是文件系统中一个临时的位置。你可以通过请求对象的 files 属性访问它们。每个上传的文件都会存储在这个字典里。它表现近乎为一个标准的 Python file 对象,但它还有一个 save() 方法,这个方法允许你把文件保存到服务器的文件系统上。

我们以上传图片为例:假设将上传的图片只允许’png’、‘jpg’、‘jpeg’、‘gif’ 这四种格式,通过url/upload使用POST上传,上传的图片存放在服务器端的static/uploads目录下。

首先在项目HelloWorld中创建目录static/uploads, 这时候我们的目录结构为:

HelloWorld/
  ├ static/
  │  ├ uploads/
  ├ templates/
  ├ server.py
  ├ client.py

werkzeug库可以判断文件名是否安全

我们安装这个库

pip install werkzeug

server.py代码:

from flask import Flask, request
from werkzeug.utils import secure_filename
import os
 
app = Flask(__name__)
 
# 文件上传目录
app.config['UPLOAD_FOLDER'] = 'static/uploads/'
# 支持的文件格式
app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}  # 集合类型
 

# 判断文件名是否是我们支持的格式
def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']
 

@app.route('/upload', methods=['POST'])
def upload():
    upload_file = request.files['image']
    if upload_file and allowed_file(upload_file.filename): # 上传前文件在客户端的文件名
        filename = secure_filename(upload_file.filename)
        # 将文件保存到 static/uploads 目录,文件名同上传时使用的文件名
        upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))
        return 'info is '+request.form.get('info', '')+'. success'
    else:
        return 'failed'
 
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

app.config中的config是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。

函数allowed_file(filename)用来判断filename是否有后缀以及后缀是否在app.config[‘ALLOWED_EXTENSIONS’]中。

upload_file是上传文件对应的对象。

app.root_path获取server.py所在目录在文件系统中的绝对路径。

upload_file.save(path)用来将upload_file保存在服务器的文件系统中,参数最好是绝对路径。函数os.path.join()用来将使用合适的路径分隔符将路径组合起来。

客户端client.py:

import requests
 
file_data = {'image': open('flask.jpg', 'rb')}
 
user_info = {'info': 'flask'}
 
r = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=file_data)
 
print(r.text)

在这里插入图片描述
我们看到图片已经上传成功了
在这里插入图片描述
要控制上产文件的大小,可以设置请求实体的大小,例如:

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #16MB

不过,在处理上传文件时候,需要使用try:…except:…。

如果要获取上传文件的内容可以:

file_content = request.files['image'].stream.read()

在这里插入图片描述

Restful URL - 变量规则

简单来说,Restful URL可以看做是对 URL 参数的替代。

编辑server.py:

from flask import Flask
 
app = Flask(__name__)
 

@app.route('/user/<username>')
def user(username):
    print(username)
    print(type(username))
    return 'hello ' + username
 
 
@app.route('/user/<username>/friends')
def user_friends(username):
    print(username)
    print(type(username))
    return 'In user_friends get username: ' + username
 
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

运行server.py。使用浏览器访问http://127.0.0.1:5000/user/Loen,server.py将输出:
在这里插入图片描述
而访问http://127.0.0.1:5000/user/Loen/
在这里插入图片描述
浏览器访问http://127.0.0.1:5000/user/Loen/friends,可以看到:
在这里插入图片描述
在这里插入图片描述

转换类型

由上面的示例可以看出,使用 Restful URL 得到的变量默认为str对象。我们可以用flask内置的转换机制,即在route中指定转换类型。

编写server.py:

from flask import Flask
app = Flask(__name__)

@app.route('/page/<int:num>')
def page(num):
    print(num)
    print(type(num))
    return 'hello world'
 
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

@app.route(‘/page/int:num’)会将num变量自动转换成int类型。

运行上面的程序,在浏览器中访问 http://127.0.0.1:5000/page/1,server.py控制台将输出如下内容:
在这里插入图片描述
如果访问的是http://127.0.0.1:5000/page/loen,我们会得到404响应。

有3个默认的转换器:

int     接受整数
floatint ,但是接受浮点数
path     和默认的相似,但也接受斜线

在这里插入图片描述

编写转换器

自定义的转换器是一个继承werkzeug.routing.BaseConverter的类,修改to_python和to_url方法即可。

to_python方法用于将url中的变量转换后供被@app.route包装的函数使用,to_url方法用于flask.url_for中的参数转换。

下面是一个示例,将server.py修改如下:

from flask import Flask, url_for
from werkzeug.routing import BaseConverter
 
class MyIntConverter(BaseConverter):
 
    def __init__(self, url_map):
        super(MyIntConverter, self).__init__(url_map)
 
    def to_python(self, value):
        return int(value)
 
    def to_url(self, value):
        return value * 2
 
 
app = Flask(__name__)
app.url_map.converters['my_int'] = MyIntConverter
 
@app.route('/page/<my_int:num>')
def page(num):
    print(num)
    print(url_for('page', num='145'))   # page 对应的是 page函数 ,num 对应对应`/page/<my_int:num>`中的num,必须是str
    return 'hello world'
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

浏览器访问http://127.0.0.1:5000/page/28后,server.py的输出信息是:

28

/page/145145

使用url_for生成链接

工具函数url_for可以让你以软编码的形式生成url,提供开发效率。

编辑 server.py:

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

@app.route('/')

def hello_world():

    pass

 

@app.route('/user/<name>')

def user(name):

    pass

 

@app.route('/page/<int:num>')

def page(num):

    pass


@app.route('/test')
def test():
    print(url_for('test'))
    print(url_for('user', name='loen'))
    print(url_for('page', num=1, q='welcome to w3c 15%2'))
    print(url_for('static', filename='uploads/flask.jpg'))
    return 'Hello'
 
if __name__ == '__main__':
    app.run(debug=True)

运行server.py。然后在浏览器中访问http://127.0.0.1:5000/test,server.py控制台将输出以下信息:
在这里插入图片描述
为什么你要构建 URL 而非在模板中硬编码?这里有三个绝妙的理由:

反向构建通常比硬编码的描述性更好。更重要的是,它允许你一次性修改 URL, 而不是到处边找边改。
URL 构建会转义特殊字符和 Unicode 数据,免去你很多麻烦。
如果你的应用不位于 URL 的根路径(比如,在 /myapplication 下,而不是 / ), url_for() 会妥善处理这个问题
在这里插入图片描述

使用redirect重定向网址

运行server.py,在浏览器中访问http://127.0.0.1:5000/old,浏览器的url会变成http://127.0.0.1:5000/new,并显示:redirect函数用于重定向,实现机制很简单,就是向客户端(浏览器)发送一个重定向的HTTP报文,浏览器会去访问报文中指定的url。

使用redirect时,给它一个字符串类型的参数就行了。

编辑 server.py:

from flask import Flask, url_for, redirect
 
app = Flask(__name__)
 
@app.route('/old')
def old():
    print('this is old')
    return redirect(url_for('new'))
 
@app.route('/new')
def new():
    print('this is new')
    return 'this is new'
 
if __name__ == '__main__':
    app.run(debug=True)

运行server.py,在浏览器中访问http://127.0.0.1:5000/old,浏览器的url会变成http://127.0.0.1:5000/new,并显示:
在这里插入图片描述
server.py 控制台显示
在这里插入图片描述
在这里插入图片描述

jinja2

jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用。

jinja2的优点
jinja2之所以被广泛使用是因为它具有以下优点:

相对于Template,jinja2更加灵活,它提供了控制结构,表达式和继承等。
相对于Mako,jinja2仅有控制结构,不允许在模板中编写太多的业务逻辑。
相对于Django模板,jinja2性能更好。
Jinja2模板的可读性很棒。
jinja2语法
作为一个模板系统,它还提供了特殊的语法,我们按照它支持的语法进行编写之后,就能使用jinja2模块进行渲染。基本语法在jinja2中,存在三种语法:

1.控制结构 {% %}

2.变量取值 {{ }}

3.注释 {# #}

下面是一个简单的jinja2例子:

{# This is jinja code #} 
{% for file in filenames %}
...
{% endfor %}

可以看到,for循环的使用方式和Python比较类似,但是没有了句尾的冒号,另外需要使用endfor做为结尾,

其实在jinja2中,if也是一样的,结尾需要使用endif
在这里插入图片描述

jinja2变量

jinja2模板中使用 {{ }} 语法表示一个变量,它是一种特殊的占位符。

当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、元组、对象等。

<p>this is a dicectory:{{ mydict['key'] }} </p>
<p>this is a list:{{ mylist[3] }} </p>
<p>this is a object:{{ myobject.something() }} </p> 

jinja2中的过滤器

变量可以通过“过滤器”进行修改,过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。常用的过滤器有:
在这里插入图片描述
如何使用这些过滤器呢?

只需要在变量后面使用管道 | 分割,多个过滤器可以链式调用,前一个过滤器的输出会作为后一个过滤器的输入

{{ 'abc' | captialize  }}
# Abc
{{ 'abc' | upper  }}
# ABC
{{ 'hello world' | title  }}
# Hello World
{{ "hello world" | replace('world','Loen') | upper }}
# HELLO LOEN
{{ 16.18 | round | int }}
# 16 

在这里插入图片描述

{{ “hello Nier” | replace(‘hello’,‘Loen’) | upper }}

最开始是hello Nier,然后使用replace将前一个的hello替换为Loen,

现在字符串为Loen Nier,最后使用upper将字符串全部转化为大写

结果为LOEN NIER

jinja2的for循环

jinja2中的for循环用于迭代Python的数据类型,包括列表,元组和字典。在jinja2中不存在while循环。迭代列表

<ul>
{% for user in users %}
<li>{{ user.username|title }}</li>
{% endfor %}
</ul>

迭代字典

<dl>
{% for key, value in my_dict.iteritems() %}
<dt>{{ key }}</dt>
<dd>{{ value}}</dd>
{% endfor %}
</dl> 

当然也可以加入else语句,在循环正确执行完毕后执行

jinja2的宏

宏类似于Python中的函数,我们在宏中定义行为,还可以进行传递参数,就像Python中的函数一样。在宏中定义一个宏的关键字是macro,后面跟其 宏的名称和参数等

{% macro input(name,age=18) %}   # 参数age的默认值为18
 <input type='text' name="{{ name }}" value="{{ age }}" >
{% endmacro %} 

调用方法也和Python的类似

<p>{{ input('Loen') }} </p>
<p>{{ input('Loen',age=28) }} </p> 

在这里插入图片描述

jinja2的继承和Super函数

jinja2中最强大的部分就是模板继承。

模板继承允许我们创建一个基本(骨架)文件,其他文件从该骨架文件继承,然后针对自己需要的地方进行修改。

jinja2的骨架文件中,利用block关键字表示其包涵的内容可以进行修改。

以下面的骨架文件base.html为例:

<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <link rel="stylesheet" href="style.css"/>
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
    {% block  footer %}
    <script>This is javascript code </script>
    {% endblock %}
</div>
</body>
</html> 

这里定义了四处 block,即:head,title,content,footer。

那怎么进行继承和变量替换呢?

注意看下面的文件

{% extend "base.html" %}       # 继承base.html文件
{% block title %} Dachenzi {% endblock %}   # 定制title部分的内容
{% block head %}
    {{  super()  }}        # 用于获取原有的信息
    <style type='text/css'>
    .important { color: #FFFFFF }
    </style>
{% endblock %}    
# 其他不修改的原封不同的继承

注意: super()函数 表示获取block块中定义的原来的内容

使用Jinja2模板引擎

在模板里,你也可以访问 request 、 session 和 g [1] 对象, 以及 get_flashed_messages() 函数。Flask与模板相关的函数有:

flask.render_template(template_name_or_list, **context)
flask.render_template_string(source, **context)
flask.get_template_attribute(template_name, attribute)
这个实例中使用了模板继承、if判断、for循环。
创建并编辑 templates/default.html

<html>

<head>

    <title>

        {% if page_title %}

            {{ page_title }}

        {% endif %}

    </title>

</head>

<!--

在``标签中使用了if判断,如果给模板传递了`page_title`变量,显示之,否则,不显示。

-->

<body>

 

{% block body %}

    {% for key in user_info %}

        <p>{{ key }}: {{ user_info[key] }} </p>

        

    {% endfor %}

{% endblock %}

变量user_info应该是一个字典,for循环用来循环输出键值对。
编辑 server.py

from flask import Flask, render_template
 
app = Flask(__name__)
 
@app.route('/user')
def user():
    user_info = {
        'name': 'loen',
        'email': '425389019@qq.com',
        'age':0,
        'github': 'https://github.com/lucoo01'
    }
    return render_template('default.html', page_title='loen\'s info', user_info=user_info)
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

render_template()函数的第一个参数指定模板文件,后面的参数是要传递的数据。
运行 server.py 在浏览器中访问http://127.0.0.1:5000/user,效果图如下:
在这里插入图片描述
查看网页源码:
在这里插入图片描述
注: 在模板里,你也可以访问 request 、 session 和 g 对象, 以及 get_flashed_messages() 函数
在这里插入图片描述

自定义404等错误的响应

要处理HTTP错误,可以使用flask.abort函数。

编辑server.py:

from flask import Flask, render_template_string, abort
app = Flask(__name__)

@app.route('/user')
def user():
    abort(401)  # Unauthorized 未授权
    print('Unauthorized, 请先登录')
 
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

运行 server.py,浏览器访问http://127.0.0.1:5000/user,效果如下:
在这里插入图片描述
要注意的是,server.py中abort(401)后的print并没有执行。
在这里插入图片描述

自定义错误页面

编辑server.py:

from flask import Flask, render_template_string, abort
app = Flask(__name__)
 
@app.route('/user')
def user():
    abort(401)  # Unauthorized
 
@app.errorhandler(401)
def page_unauthorized(error):
    return render_template_string('<h1> Unauthorized </h1><h2>{{ error_info }}</h2>', error_info=error), 401
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

page_unauthorized 函数返回的是一个元组,401 代表HTTP 响应状态码。

如果省略401,则响应状态码会变成默认的 200。

运行 server.py,浏览器访问 http://127.0.0.1:5000/user,效果如下:
在这里插入图片描述
在这里插入图片描述

用户会话

session 用来记录用户的登录状态,一般基于cookie实现。

编辑 server.py

from flask import Flask, render_template_string, \
    session, request, redirect, url_for
app = Flask(__name__)
 
app.secret_key = 'LoenDSdtj\9bX#%@!!*(0&^%)'
 

@app.route('/login')
def login():
    page = '''
    <form action="{{ url_for('do_login') }}" method="post">
        <p>name: <input type="text" name="user_name" /></p>
        <input type="submit" value="Submit" />
    </form>
    '''
    return render_template_string(page)
 
 
@app.route('/do_login', methods=['POST'])
def do_login():
    name = request.form.get('user_name')
    session['user_name'] = name
    return 'success'
 
 
@app.route('/show')
def show():
    return session['user_name']
 
 
@app.route('/logout')
def logout():
    session.pop('user_name', None)
    return redirect(url_for('login'))
 
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

代码的含义

app.secret_key用于给session加密。

在/login中将向用户展示一个表单,要求输入一个名字,submit后将数据以post的方式传递给/do_login,/do_login将名字存放在session中。

如果用户成功登录,访问/show时会显示用户的名字。此时,打开调试工具,选择session面板,会看到有一个cookie的名称为session。

/logout用于登出,通过将session中的user_name字段pop即可。Flask中的session基于字典类型实现,调用pop方法时会返回pop的键对应的值;如果要pop的键并不存在,那么返回值是pop()的第二个参数。

另外,使用redirect()重定向时,一定要在前面加上return。

进入http://127.0.0.1:5000/login,输入name,点击submit:
在这里插入图片描述

在这里插入图片描述

设置session的有效时间

设置session的有效时间设置为5分钟

from datetime import timedelta
from flask import session, app
 
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)

在这里插入图片描述

使用Cookie

Cookie是存储在客户端的记录访问者状态的数据。

常用的用于记录用户登录状态的session大多是基于cookie实现的。

cookie可以借助flask.Response来实现。

修改server.py:

from flask import Flask, request, Response, make_response
import time
 
app = Flask(__name__)
 
@app.route('/add')
def login():
    res = Response('add cookies')
    res.set_cookie(key='name', value='loen', expires=time.time()+6*60)
    return res
 
 
@app.route('/show')
def show():
    return request.cookies.__str__()
 
 
@app.route('/del')
def del_cookie():
    res = Response('delete cookies')
    res.set_cookie('name', '', expires=0)
    return res
 
 
if __name__ == '__main__':
    app.run(port=5000, debug=True)

使用Response.set_cookie添加和删除cookie。

expires参数用来设置cookie有效时间,值可以是datetime对象或者unix时间戳。

res.set_cookie(key='name', value='loen', expires=time.time()+6*60)

上面的expire参数的值表示cookie在从现在开始的6分钟内都是有效的。

要删除cookie,将expire参数的值设为0即可:

res.set_cookie('name', '', expires=0)

运行与测试
运行 server.py:
使用浏览器打开http://127.0.0.1:5000/add,浏览器界面会显示
在这里插入图片描述
下面查看一下cookie, 我用的是chrome自带的开发者工具, 在chrome界面按F12
在这里插入图片描述
在这里插入图片描述

闪存系统 flashing system

Flask 的闪存系统(flashing system)用于向用户提供反馈信息,这些反馈信息一般是对用户上一次操作的反馈。

反馈信息是存储在服务器端的,当服务器向客户端返回反馈信息后,这些反馈信息会被服务器端删除。

编写 server.py

from flask import Flask, flash, get_flashed_messages
import time
 
app = Flask(__name__)
app.secret_key = 'some_secret'

@app.route('/')
def index():
    return 'Hello index'
 
 
@app.route('/gen')
def gen():
    info = 'access at '+ time.time().__str__()
    flash(info)
    return info
 
 
@app.route('/show1')
def show1():
    return get_flashed_messages().__str__()
 
 
@app.route('/show2')
def show2():
    return get_flashed_messages().__str__()
 
 
if __name__ == "__main__":
    app.run(port=5000, debug=True)

运行 server.py:

打开浏览器,访问http://127.0.0.1:5000/gen,浏览器界面显示(注意,时间戳是动态生成的,每次都会不一样,除非并行访问):
在这里插入图片描述
查看浏览器的cookie,可以看到session,随着gen页面的生成不断变化:
在这里插入图片描述
访问show1 得到的列表内容 是之前访问http://127.0.0.1:5000/gen得到的内容。此时,cookie中已经没有session了。

如果使用浏览器访问http://127.0.0.1:5000/show1或者http://127.0.0.1:5000/show2,只会得到空
在这里插入图片描述
flash系统也支持对flash的内容进行分类。修改server.py内容:

from flask import Flask, flash, get_flashed_messages
import time
 
app = Flask(__name__)
app.secret_key = 'some_secret'
 
 
@app.route('/')
def index():
    return 'Hello Index'
 
 
@app.route('/gen')
def gen():
    info = 'access at '+ time.time().__str__()
    flash('show1 '+info, category='show1')
    flash('show2 '+info, category='show2')
    return info
 
 
@app.route('/show1')
def show1():
    return get_flashed_messages(category_filter='show1').__str__()
 
@app.route('/show2')
def show2():
    return get_flashed_messages(category_filter='show2').__str__()
 
 
if __name__ == "__main__":
    app.run(port=5000, debug=True)

不过,由上面的代码可以知道,此时生成了两个flash信息,但分类(category)不同。

使用浏览器访问http://127.0.0.1:5000/show1,得到如下内容:
在这里插入图片描述
而继续访问http://127.0.0.1:5000/show2,得到的内容为空:

在模板文件中获取flash的内容

在Flask中,get_flashed_messages()默认已经集成到Jinja2模板引擎中,易用性很强。下面是来自官方的一个示例:

{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
    <ul>
        {% for category, message in messages %}
        <li class="{{ category }}">{{ message }}</li>
        {% endfor %}
    </ul>
    {% endif %}
{% endwith %}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值