/home/user/Projects/flask-tutorial ├── flaskr/ #一个包含应用代码和文件的 Python 包。 │ ├── __init__.py │ ├── db.py │ ├── schema.sql │ ├── auth.py │ ├── blog.py │ ├── templates/ │ │ ├── base.html │ │ ├── auth/ │ │ │ ├── login.html │ │ │ └── register.html │ │ └── blog/ │ │ ├── create.html │ │ ├── index.html │ │ └── update.html │ └── static/ │ └── style.css ├── tests/ #一个包含测试模块的文件夹。 │ ├── conftest.py │ ├── data.sql │ ├── test_factory.py │ ├── test_db.py │ ├── test_auth.py │ └── test_blog.py ├── venv/ #一个 Python 虚拟环境,用于安装 Flask 和其他依赖的包。 ├── setup.py └── MANIFEST.in 假设使用 git 来进行版本控制,那么使用 .gitignore 来设置应当忽略 的文件
Setuptools是一个通常被用来分发Python库和扩展的外部扩展库。 setup.py from setuptools import find_packages, setup setup( name='flaskr', version='1.0.0', packages=find_packages(), include_package_data=True, zip_safe=False, #zip_safe可以用来强迫或者避免zip Archive的创建。 install_requires=[ #依赖作为一个list被声明在install_requires参数中,这个list中的每一项应该从PyPI中下载下来, 'flask', #缺省下载最近的版本,但是可以提供最小和最大的版本值。 'SQLAlchemy>=0.6', ], ) packages 告诉 Python 包所包括的文件夹(及其所包含的 Python 文件)。find_packages() 自动找到这些文件夹,这样就不用手动写出来。 为了包含其他文件夹,如静态文件和模板文件所在的文件夹,需要设置 include_package_data =True 。 include_package_data告诉setuptools去寻找一个MAINFEST.in文件,并且下载所有匹配包数据的项, 可以用这个来伴随你的python模块分发静态文件和模版文件。 MANIFEST.in include flaskr/schema.sql graft flaskr/static graft flaskr/templates global-exclude *.pyc 这告诉 Python 复制所有 static 和 templates 文件夹中的文件, schema.sql 文件,但是排除所有字节文件。
该类的实例将会成为 WSGI 应用。 class Flask(_PackageBoundObject): def __init__( self, import_name, static_url_path=None, static_folder='static', static_host=None, host_matching=False, subdomain_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, root_path=None ) import_name: 让Flask了解属于您的应用程序的内容,如果您使用单个模块,'__name__'始终是正确的值。 但是,如果您使用的是软件包,通常建议在那里对软件包的名称进行硬编码。 static_url_path:可用于为Web上的静态文件指定不同的路径。 默认为'static_folder'文件夹的名称。 static_folder: 包含静态文件的文件夹,应该在'static_url_path'中提供。 默认为应用程序根路径中的'static'文件夹。 static_host: 添加静态路由时使用的主机。 默认为无。 host_matching: subdomain_matching: template_folder: instance_path: instance_relative_config: root_path:
不管你使用何种方式载入配置,都可以使用 Flask 对象的 config 属性来操作配置的值。 config 实质上是一个字典的子类,可以像字典一样操作: app = Flask(__name__) app.config['TESTING'] = True 一次更新多个配置值: app.config.update( TESTING=True, SECRET_KEY=b'_5#y2L"F4Q8z\n\xec]/' ) 使用类和类的继承来配置: class Config(object): DEBUG = False TESTING = False DATABASE_URI = 'sqlite:///:memory:' class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True app.config.from_object('configmodule.ProductionConfig') app.config.from_envvar('YOURAPPLICATION_SETTINGS') app.config.from_pyfile('application.cfg', silent=True) 实例文件夹 显式定义使用 instance_path 参数: app = Flask(__name__, instance_path='/path/to/instance/folder') app.config.from_json(filename, silent=False) app.config.from_mapping(*mapping, **kwargs)
@app.route('/') 实际上是调用了 @setupmethod def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): endpoint:已注册URL规则的端点。 Flask默认将视图函数的名称视为端点 self.view_functions[endpoint] = view_func provide_automatic_options:控制是否应自动添加'OPTIONS'方法。 这也可以通过在添加规则之前设置'view_func.provide_automatic_options = False'来控制。 options:'methods','redirect_to','host','alias','build_only','subdomain' #如果使用了 GET 方法, Flask 会自动添加 HEAD 方法支持,OPTIONS 也会自动实现。 创建一个Rule实例 Rule(rule, methods=methods, **options),并将其放入app.url_map中, 然后设置视图函数app.view_functions[endpoint] = view_func #rule必须以'/'开头 @app.route('/projects/') def projects(): return 'The project page' @app.route('/about') def about(): return 'The about page' projects 的 URL 尾部有一个斜杠,看起来就如同一个文件夹。 访问一个没有斜杠结尾的 URL 时 Flask 会自动进行重定向,帮你在尾部加上一个斜杠。 about 的 URL 没有尾部斜杠,因此其行为表现与一个文件类似。 如果访问这个 URL 时添加了尾部斜杠就会得到一个 404 错误。 url_for() 函数用于构建指定函数的 URL。它把函数名称作为第一个 参数。 它可以接受任意个关键字参数,每个关键字参数对应 URL 中的变量。 未知变量 将添加到 URL 中作为查询参数。 url_for('login') --> /login url_for('login', next='/') --> /login?next=/
class UserAPI(MethodView): def get(self, user_id): if user_id is None: # 返回一个包含所有用户的列表 pass else: # 显示一个用户 pass def post(self): # 创建一个新用户 pass def delete(self, user_id): # 删除一个用户 pass def put(self, user_id): # update a single user pass def register_api(view, endpoint, url, pk='id', pk_type='int'): view_func = view.as_view(endpoint) app.add_url_rule(url, defaults={pk: None}, view_func=view_func, methods=['GET',]) app.add_url_rule(url, view_func=view_func, methods=['POST',]) app.add_url_rule('%s<%s:%s>' % (url, pk_type, pk), view_func=view_func, methods=['GET', 'PUT', 'DELETE']) register_api(UserAPI, 'user_api', '/users/', pk='user_id') 装饰视图 #定义装饰器 import functools def user_required(func): @functools.wraps(func) def inner(*args,**kwargs): if not g.user: abort(401) return func(*args,**kwargs) return inner #添加装饰器 class UserAPI(MethodView): decorators = [user_required]
render_template() 方法可以渲染模板,提供模板名称和context 作为参数传递给模板的变量就行了。 Flask 会在 templates 文件夹内寻找模板。 情形 1 : 一个模块: /application.py /templates /hello.html 情形 2 : 一个包: /application /__init__.py /templates /hello.html 自动转义默认开启。因此,如果 name 包含 HTML ,那么会被自动转义。 如果你可以 信任某个变量,且知道它是安全的 HTML ,那么可以使用 Markup 类把它标记为安全的, 或者在模板 中使用 |safe 过滤器。
HTML 表单中设置 enctype="multipart/form-data" 属性 已上传的文件被储存在内存或文件系统的临时位置。 你可以通过请求对象 files 属性来访问上传的文件。每个上传的文件都储存在这个 字典型属性中。 @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': f = request.files['the_file'] f.save('/var/www/uploads/uploaded_file.txt') #文件上传之前其在客户端系统中的名称,可以使用 filename 属性。 f.filename #如果想要把客户端的文件名作为服务器上的文件名, 可以通过 Werkzeug 提供的 secure_filename() 函数 from werkzeug.utils import secure_filename f.save(current_app.config['UPLOAD_FOLDER'] + secure_filename(f.filename)) 注意: secure_filename()函数只返回ASCII字符,非ASCII字符会被过滤掉。 所以会导致中文出错,可以将中文文件名转换成英文,可以使用pypinyin来转换(使用pip安装) from pypinyin import lazy_pinyin name , ext = f.filename.split('.') new_filename= '_'.join(lazy_pinyin(name)) + '.' + ext f.save((os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename))
from flask import request username = request.cookies.get('username') #获取cookies resp = make_response(render_template(...)) resp.set_cookie('username', 'the username') #设置cookies 注意, cookies 设置在响应对象上。通常只是从视图函数返回字符串, Flask 会把它们 转换为响应对象。 如果你想显式地转换,那么可以使用 make_response() 函数,然后再修改它。
Request的常用操作
Flask之request - 道友请多指教 - 博客园 http://www.cnblogs.com/95lyj/p/9508723.html
注册出错处理器 1.使用 errorhandler() 装饰函数来注册 @app.errorhandler(404) def page_not_found(error): return render_template('page_not_found.html'), 404 注意 render_template() 后面的 404 ,这表示页面对就的出错 代码是 404 ,即页面不存在。缺省情况下 200 表示:一切正常。 2.使用 register_error_handler() 来注册 app.register_error_handler(400, handle_bad_request) @app.errorhandler(werkzeug.exceptions.BadRequest) def handle_bad_request(e): return 'bad request!', 400 BadRequest ,和它们的 HTTP 代码是可替换的。 ( BadRequest.code == 400 ) app.error_handler_spec = {None: {400: {<class 'werkzeug.exceptions.BadRequest'>: <function handle_bad_request>}}} 我们可以自定义一个 HTTPException 子类: class InsufficientStorage(werkzeug.exceptions.HTTPException): code = 507 description = 'Not enough storage space.' app.register_error_handler(InsuffcientStorage, handle_507)
视图函数的返回值会自动转换为一个响应对象。 如果返回值是一个字符串,那么会被转换 为一个包含作为响应体的字符串、一个 200 OK 出错代码 和一个 text/html 类型的响应对象。 以下是转换的规则: 1.如果视图返回的是一个响应对象,那么就直接返回它。 2.如果返回的是一个字符串,那么根据这个字符串和缺省参数生成一个用于返回的 响应对象。 3.如果返回的是一个元组,那么元组中的项目可以提供额外的信息。 元组中必须至少 包含一个项目,且项目应当由 (response, status, headers) 或者 (response, headers) 组成。 status 的值会重载状态代码, headers 是一个由额外头部值组成的列表或字典。 4.如果以上都不是,那么 Flask 会假定返回值是一个有效的 WSGI 应用并把它转换为 一个响应对象。 if len_rv == 3: rv, status, headers = rv elif len_rv == 2: if isinstance(rv[1], (Headers, dict, tuple, list)): rv, headers = rv else: rv, status = rv app.response_class(rv, status=status, headers=headers) 如果想要在视图内部掌控响应对象的结果,那么可以使用 make_response() 函数。 如设置cookies、添加响应头
除了请求对象之外还有一种称为 session 的对象,允许你在不同请求 之间储存信息。 这个对象相当于用密钥签名加密的 cookie ,即用户可以查看你的 cookie ,但是如果没有密钥就无法修改它。 使用会话之前你必须设置一个密钥。 from flask import Flask, session, escape, request app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' 或者使用随机字符串,更安全 import os; app.secret_key = os.urandom(16)
Blueprint 是一种组织一组相关视图及其他代码的方式。 蓝图方式是把它们注册到蓝图,然后在工厂函数中 把蓝图注册到应用。 和普通应用一样,蓝图一般都放在一个文件夹中。虽然多个蓝图可以共存于同一个文 件夹中,但是最好不要这样做。 创建蓝图 from flask import Blueprint simple_page = Blueprint('admin', __name__,static_folder='static', template_folder='templates',url_prefix='/admin') 创建了一个名称为 'admin' 的 Blueprint 。 __name__ 这个参数 指定与蓝图相关的逻辑 Python 模块或包。 url_prefix 会添加到所有与该蓝图关联的 URL 前面。 资源文件夹 第二个参数: 如果这个参数指向的是实际的 Python 包 (文件系统中的一个文件夹),那么它就是资源文件夹。 如果是一个模块,那么这个 模块包含的包就是资源文件夹。 可以通过 Blueprint.root_path 属性来查 看蓝图的资源文件夹 (返回绝对路径) 可以使用 open_resource() 函数快速打开这个文件夹中的资源 with simple_page.open_resource('static/style.css') as f: code = f.read() 静态文件 静态文件 URL 就是 /admin/static 模版文件 如果你有一个名为 admin 的蓝图,该蓝图指定的模版文件是 index.html , admin/ templates/ admin/ index.html __init__.py 当你需要渲染模板的时候就可以使用 admin/index.html 来找到模板。 注册蓝图 使用 app.register_blueprint() 导入并注册 蓝图。新的代码放在工厂函数的尾部返回应用之前。 def create_app(): app = ... # existing code omitted from . import auth app.register_blueprint(admin.simple_page) return app 蓝图视图 @simple_page.route('/register', methods=('GET', 'POST')) def register(): ... return render_template('admin/register.html') 当 Flask 收到一个指向 /admin/register 的请求时就会调用 register 视图并把其返回值作为响应。 使用url_for 创建 URL 和通常一样使用 url_for() 函数,只是要把蓝图名称作为端点的前缀,并且用一个点( . )来分隔 url_for('simple_page.index')
from flask import current_app,request,session,g current_app = LocalProxy(_find_app) #_app_ctx_stack.top.app request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session')) #getattr(_request_ctx_stack.top, 'request') #getattr(_request_ctx_stack.top, 'session') g = LocalProxy(partial(_lookup_app_object, 'g')) #getattr(_app_ctx_stack.top, 'g') app __call__ 方法: def wsgi_app(self, environ, start_response): ctx = RequestContext(self, environ) try: ctx.push() #1 _app_ctx_stack = LocalStack() _app_ctx_stack._local.__storage__ = { self.__ident_func__():{ 'stack' : [Appcontext(),] } } #2 _request_ctx_stack = LocalStack() _request_ctx_stack._local.__storage__ = { self.__ident_func__():{ 'stack' : [RequestContext(),] } } app_ctx = AppContext(self.app) app_ctx.push() _app_ctx_stack.push(self) ctx._implicit_app_ctx_stack = [app_ctx] _request_ctx_stack.push(self) response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) return response(environ, start_response)
@app.before_first_request app.before_first_request_funcs.append(f) @app.url_value_preprocessor #在请求匹配到且能够执行基于url值的代码的时候立即执行 app.url_value_preprocessors.setdefault(None, []).append(f) func(request.endpoint, request.view_args) @app.before_request app.before_request_funcs.setdefault(None, []).append(f) #如果有返回值,则跳过视图函数,按反顺序执行after_request(rv) @app.after_request app.after_request_funcs.setdefault(None, []).append(f) #按照添加的顺序反向执行 @app.teardown_request @app.errorhandler @app.url_defaults app.url_default_functions.setdefault(None, []).append(f) 实例: @app.url_defaults def add_language_code(endpoint, values): if 'lang_code' in values or not g.lang_code: return if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): values['lang_code'] = g.lang_code #url_map 的 is_endpoint_expecting函数用于找出往endpoint函数传递该参数是否有意义。 @app.route('/<lang_code>/') def index(): pass # 此时会调用add_language_code函数,endpoint就是传入url_for的第一个参数,就是处理url的函数index, values是包括url_for函数所有后面参数的dict,添加values的值,用于生成url >>> url_for('index', lang_code='Zh') # 假设此时g.lang_code已经存在,且值为‘Zh’ >>> url_for('index') '/Zh' @blueprint.before_app_first_request @blueprint.before_app_request @blueprint.before_request @blueprint.after_request @blueprint.after_app_request @blueprint.teardown_app_request @blueprint.teardown_request