Flask 基本使用
Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。
http://www.pythondoc.com/flask/quickstart.html
安装
pip3 install Flask
简单使用:
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
外部可见服务器
当你运行服务器,你会注意到它只能从你自己的计算机上访问,网络中其它任何的地方都不能访问。 这是因为默认情况下,调试模式,应用中的一个用户可以执行你计算机上的任意 Python 代码。
如果你关闭 debug 或者信任你所在网络上的用户,你可以让你的服务器对外可用,只要简单地改变方法 run()
的调用像如下这样:
app.run(host='0.0.0.0')
这让你的操作系统去监听所有公开的 IP。
调试模式
run()
方法是十分适用于启动一个本地开发服务器,但是你需要在修改代码后手动重启服务器。 这样做并不好,Flask 能做得更好。如果启用了调试支持,在代码修改的时候服务器能够自动加载, 并且如果发生错误,它会提供一个有用的调试器。
有两种方式开启调式模式。一种是在应用对象上设置标志位:
app.debug = True
app.run()
或者作为 run 的一个参数传入:
app.run(debug=True)
两种方法效果是一样的。
配置文件
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为: { 'DEBUG': get_debug_flag(default=False), 是否开启Debug模式 'TESTING': False, 是否开启测试模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, } 方式一: app.config['DEBUG'] = True PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...) 方式二: app.config.from_pyfile("python文件名称") 如: settings.py DEBUG = True app.config.from_pyfile("settings.py") app.config.from_envvar("环境变量名称") 环境变量的值为python文件名称名称,内部调用from_pyfile方法 app.config.from_json("json文件名称") JSON文件名称,必须是json格式,因为内部会执行json.loads app.config.from_mapping({'DEBUG':True}) 字典格式 app.config.from_object("python类或类的路径") app.config.from_object('pro_flask.settings.TestingConfig') settings.py 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 PS: 从sys.path中已经存在路径开始写 PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
路由
@app.route('/') def index(): return 'Index Page' @app.route('/hello') def hello(): return 'Hello World'
但是不仅如此!你可以动态地构造 URL 的特定部分,也可以在一个函数上附加多个规则。
变量规则
@app.route('/user/<username>') def show_user_profile(username): # show the user profile for that user return 'User %s' % username @app.route('/post/<int:post_id>') def show_post(post_id): # show the post with the given id, the id is an integer return 'Post %d' % post_id
- @app.route('/user/<username>')
- @app.route('/post/<int:post_id>')
- @app.route('/post/<float:post_id>')
- @app.route('/post/<path:path>')
- @app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种
唯一 URLs / 重定向行为
Flask 的 URL 规则是基于 Werkzeug 的 routing 模块。 该模块背后的想法是基于 Apache 和早期的 HTTP 服务器定下先例确保优雅和唯一的 URL。
以这两个规则为例:
@app.route('/projects/') def projects(): return 'The project page' @app.route('/about') def about(): return 'The about page'
虽然它们看起来确实相似,但它们结尾斜线的使用在 URL 定义 中不同。 第一种情况中,规范的 URL 指向 projects 尾端有一个斜线。 这种感觉很像在文件系统中的文件夹。访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范URL去。
然而,第二种情况的 URL 结尾不带斜线,类似 UNIX-like 系统下的文件的路径名。 访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。
当用户访问页面时忘记结尾斜线时,这个行为允许关联的 URL 继续工作, 并且与 Apache 和其它的服务器的行为一致。另外,URL 会保持唯一,有助于避免搜索引擎索引同一个页面两次。
构建 URL
如果它可以匹配 URL,那么 Flask 能够生成它们吗?
当然 Flask 能够做到。你可以使用函数 url_for()
来针对一个特定的函数构建一个 URL。它能够接受函数名作为第一参数,以及一些关键字参数, 每一个关键字参数对应于 URL 规则的变量部分。未知变量部分被插入到 URL 中作为查询参数。
>>> from flask import Flask, url_for >>> app = Flask(__name__) >>> @app.route('/') ... def index(): pass ... >>> @app.route('/login') ... def login(): pass ... >>> @app.route('/user/<username>') ... def profile(username): pass ... >>> with app.test_request_context(): ... print url_for('index') ... print url_for('login') ... print url_for('login', next='/') ... print url_for('profile', username='John Doe') ... / /login /login?next=/ /user/John%20Doe
@app.route('/index',endpoint="xxx") #endpoint是别名 def index(): v = url_for("xxx") print(v) return "index"
子域名
@app.route("/static_index", subdomain="admin") def static_index(): return "admin.bjg.com"
请求
对于Http请求,Flask会把请求信息封装在request中(werkzeug.wrappers.BaseRequest),提供的如下常用方法和字段以供使用:
request.method
request.args
request.form
request.values
request.files
request.cookies
request.headers
request.path
request.full_path
request.script_root
request.url
request.base_url
request.url_root
request.host_url
request.host
@app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error)
响应
@app.route('/index/', methods=['GET', 'POST']) def index(): return "index" @app.route('/index/', methods=['GET', 'POST']) def index(): return render_template("index.html") @app.route('/index/', methods=['GET', 'POST']) def index(): # return redirect('/login/') return redirect(url_for('login'))
设置404页面
from flask import abort, redirect, url_for @app.route('/') def index(): return redirect(url_for('login')) @app.route('/login') def login(): abort(401) this_is_never_executed()
这是一个相当无意义的例子因为用户会从主页重定向到一个不能访问的页面( 401意味着禁止访问), 但是它说明了重定向如何工作。
默认情况下,每个错误代码会显示一个黑白错误页面。如果你想定制错误页面,可以使用 errorhandler()
装饰器:
from flask import render_template @app.errorhandler(404) def page_not_found(error): return render_template('page_not_found.html'), 404
设置响应信息
使用make_response可以对相应的内容进行操作
@app.route('/index/', methods=['GET', 'POST']) def index(): response = make_response(render_template('index.html')) # response是flask.wrappers.Response类型 # response.delete_cookie # response.set_cookie # response.headers['X-Something'] = 'A value' return response
Session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
-
设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
app.config['SESSION_COOKIE_NAME'] = 'session_lvning' #设置session的名字
关于session的配置
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True, #是否每次都跟新
'PERMANENT_SESSION_LIFETIME': timedelta(days=31)
- session超时时间
'PERMANENT_SESSION_LIFETIME': timedelta(days=31)
闪现(flash)
flash是基于session创建的,flash支持往里边放值,只要你取一下就没有了,相当于pop了一下。不仅可以拿到值,而且可以把其从session里的去掉,
基于Session实现的用于保存数据的集合,其特点是:使用一次就删除。
from flask import Flask, flash, redirect, render_template, \ request, url_for app = Flask(__name__) app.secret_key = 'some_secret' @app.route('/') def index(): return render_template('index.html') @app.route('/login', methods=['GET', 'POST']) def login(): error = None if request.method == 'POST': if request.form['username'] != 'admin' or \ request.form['password'] != 'secret': error = 'Invalid credentials' else: flash('You were successfully logged in') return redirect(url_for('index')) return render_template('login.html', error=error) if __name__ == "__main__": app.run()
<!doctype html> <title>My Application</title> {% with messages = get_flashed_messages() %} {% if messages %} <ul class=flashes> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} {% block body %}{% endblock %}
中间件
from flask import Flask,session,Session,flash,get_flashed_messages,redirect,render_template,request app = Flask(__name__) app.secret_key ='sdfsdfsdf' @app.before_request def process_request1(): print('process_request1') @app.after_request def process_response1(response): print('process_response1') return response @app.before_request def process_request2(): print('process_request2') @app.after_request def process_response2(response): #参数也得有 print('process_response2') return response #必须有返回值 @app.route('/index') def index(): print('index') return 'Index' @app.route('/order') def order(): print('order') return 'order' @app.route('/test') def test(): print('test') return 'test' if __name__ == '__main__': app.run()
还有一个@app.before_first_request:表示,当程序运行起来,第一个请求来的时候就只执行一次,下次再来就不会在执行了
视图函数
Flask中的CBV
def auth(func): def inner(*args, **kwargs): result = func(*args, **kwargs) return result return inner class IndexView(views.MethodView): # methods = ['POST'] #只允许POST请求访问 decorators = [auth,] # 如果想给所有的get,post请求加装饰器,就可以这样来写,也可以单个指定 def get(self): #如果是get请求需要执行的代码 v = url_for('index') print(v) return "GET" def post(self): #如果是post请求执行的代码 return "POST" app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) #name即FBV中的endpoint,指别名 if __name__ == '__main__': app.run()
模板
Flask使用的是Jinja2模板,所以其语法和Django无太大差别
Flask中模板里面,执行函数时,需要带()才执行
1.Markup
在后端使用Markup,等价于Django里的mark_safe
v = Markup("<input type='text' />") 在前端使用safe {{ v1|safe }}
2.静态文件的两种导入方式
动态的 web 应用同样需要静态文件。CSS 和 JavaScript 文件通常来源于此。理想情况下, 你的 web 服务器已经配置好为它们服务,然而在开发过程中 Flask 能够做到。 只要在你的包中或模块旁边创建一个名为 static 的文件夹,在应用中使用 /static 即可访问。
给静态文件生成 URL ,使用特殊的 'static'
端点名:
url_for('static', filename='style.css')
<link rel="stylesheet" href="{{url_for('static',filename='admin/bootstrap/css/bootstrap.min.css')}}">
自定义模板
from flask import Flask,url_for,render_template,Markup app = Flask(__name__) def test(a,b): #自定义的标签,此方法在使用时,需要在render_temlate中传入到指定以页面使用 return a+b @app.template_global() # 不需要传入,可直接在页面使用 def sb(a1, a2): return a1 + a2 + 100 @app.template_filter() #不需要传入,使用时要在一个值(此值作为第一个参数传入到过滤器中)的后面加入|,然后再加参数 def db(a1, a2, a3): return a1 + a2 + a3 @app.route('/index') def index(): v1 = "字符串" v2 = [11,22,33] v3 = {"k1":"v3","sdf":"sdgfgf"} v4 = "<input type='text' />" v5 = Markup("<input type='text' />") return render_template("index.html",v1=v1,v2=v2,v3=v3,v4=v4,v5=v5,test=test) if __name__ == '__main__': app.run(debug=True)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width"> <title>Title</title> </head> <body> {{ v1 }} <ul> {% for foo in v2 %} <li>{{ foo }}</li> {% endfor %} {{ v2.1 }} {% for k,v in v3.items() %} <li>{{ k }} {{ v }}</li> {% endfor %} {{ v3.k1 }} {{ v3.get("k1") }} {{ v4|safe }} {{ v5 }} <h1>{{ test(1,2) }}</h1> <p>{{ sb(1,2) }}</p> <p>{{ 1| db(2,3) }}</p> </ul> </body> </html>
宏
只有定义的东西在很多地方去使用的时候才去用它,比如分页之类的
{% macro xx(name, type='text', value='') %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> {% endmacro %} {{ xx('n1') }}
分页宏
{% macro page(data,url) %} {%if data%} <ul class="pagination pagination-sm no-margin pull-right"> <li><a href="{{url_for(url,page=1)}}">首页</a></li> {%if data.has_prev %} <li><a href="{{url_for(url,page=data.prev_num)}}">上一页</a></li> {%else%} <li class="disabled"><a href="#">上一页</a></li> {%endif%} {% for v in data.iter_pages() %} {%if v== data.page%} <li class="active"><a href="#">{{v}}</a></li> {%else%} <li><a href="{{url_for(url,page=v)}}">{{v}}</a></li> {%endif%} {%endfor%} {%if data.has_next %} <li><a href="{{url_for(url,page=data.next_num)}}">下一页</a></li> {%else%} <li class="disabled"><a href="">下一页</a></li> {%endif%} <li><a href="{{url_for(url,page=data.pages)}}">尾页</a></li> </ul> {%endif%} {%endmacro%}
模板继承
母版 <html> {%block css%} {%endblock%} {%block content%} {%endblock%} {%block js%} {%endblock%} </html>
{%extends 'admin/admin.html'%}}
{%block content%}
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
{%endblock%}
蓝图
manage.py
from app import app if __name__ == "__main__": app.run(host='0.0.0.0')
__init__.py
# coding:utf8 from flask import Flask, render_template from flask_sqlalchemy import SQLAlchemy from .config import Config import pymysql app = Flask(__name__) app.debug = True app.config.from_object(Config) db = SQLAlchemy(app) from app.home import home as home_blueprint from app.admin import admin as admin_blueprint app.register_blueprint(home_blueprint) app.register_blueprint(admin_blueprint, url_prefix="/admin") @app.errorhandler(404) def page_lost(error): return render_template('home/404.html'), 404
app/home/__init__.py
from flask import Blueprint home =Blueprint('home',__name__) import app.home.views
app/home/views.py
from . import home from flask import render_template,redirect,url_for @home.route("/") def index(): return render_template('home/index.html')
cofig.py
import os class Config: SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:sxf4750@127.0.0.1:3306/world?charset=utf8" SQLALCHEMY_TRACK_MODIFICATIONS = True SECRET_KEY = '4a1b214d102547b6a1957a49c89dd7e5' UP_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)),'static/uploads/') # SQLALCHEMY_ECHO=True