在flask中,有一个app.session_interface = SecureCookieSessionInterface(),也就是存session,调用open_session方法,取session调用save_session方法
所以若是咱们想要本身定制session的存储位置,那么直接修改app.session_interface便可。这里咱们介绍一个第三方的组件,叫作flask-session,直接pip install flask-session便可
html
from flask import Flask from flask_session import Session app = Flask(__name__) Session(app) @app.route("/login") def login(): return "login" if __name__ == "__main__": app.run(port=8888, debug=True)
咱们看看Session(app)这一步都作了些什么
python
class Session(object): def __init__(self, app=None): # 传入app,这里确定会部位None self.app = app if app is not None: # 调用init_app self.init_app(app) def init_app(self, app): # 能够看到,帮咱们把app.session_interface给换掉了 # 由于默认的是SecureCookieSessionInterface(),而后调用了_get_interface(app) app.session_interface = self._get_interface(app) def _get_interface(self, app): # 这里的配置文件先不用管 config = app.config.copy() config.setdefault('SESSION_TYPE', 'null') config.setdefault('SESSION_PERMANENT', True) config.setdefault('SESSION_USE_SIGNER', False) config.setdefault('SESSION_KEY_PREFIX', 'session:') config.setdefault('SESSION_REDIS', None) config.setdefault('SESSION_MEMCACHED', None) config.setdefault('SESSION_FILE_DIR', os.path.join(os.getcwd(), 'flask_session')) config.setdefault('SESSION_FILE_THRESHOLD', 500) config.setdefault('SESSION_FILE_MODE', 384) config.setdefault('SESSION_MONGODB', None) config.setdefault('SESSION_MONGODB_DB', 'flask_session') config.setdefault('SESSION_MONGODB_COLLECT', 'sessions') config.setdefault('SESSION_SQLALCHEMY', None) config.setdefault('SESSION_SQLALCHEMY_TABLE', 'sessions') # 这里经过app.config,指定SESSION_TYPE,建立一个新的session_interface # 可使redis,memcached,文件系统,数据库等等 if config['SESSION_TYPE'] == 'redis': session_interface = RedisSessionInterface( # 若是是redis,则必须指定'SESSION_REDIS' # 后面的配置不指定,可使用默认的 config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) elif config['SESSION_TYPE'] == 'memcached': session_interface = MemcachedSessionInterface( config['SESSION_MEMCACHED'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) elif config['SESSION_TYPE'] == 'filesystem': session_interface = FileSystemSessionInterface( # 若是是文件,则须要指定文件路径 config['SESSION_FILE_DIR'], config['SESSION_FILE_THRESHOLD'], config['SESSION_FILE_MODE'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) elif config['SESSION_TYPE'] == 'mongodb': session_interface = MongoDBSessionInterface( config['SESSION_MONGODB'], config['SESSION_MONGODB_DB'], config['SESSION_MONGODB_COLLECT'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) elif config['SESSION_TYPE'] == 'sqlalchemy': session_interface = SqlAlchemySessionInterface( app, config['SESSION_SQLALCHEMY'], config['SESSION_SQLALCHEMY_TABLE'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) else: # 若是在配置中不指定,那么很差意思,open_session返回None """ class NullSessionInterface(SessionInterface): def open_session(self, app, request): return None """ session_interface = NullSessionInterface() # 最后返回session_interface return session_interface
使用flask-session
正则表达式
from flask import Flask from flask_session import Session app = Flask(__name__) # 个人阿里云没有安装redis,因此这里就使用文件了 """ 若是是redis的话,也是同样的 import redis app.config["SESSION_TYPE"] = "redis" app.config["SESSION_REDIS"] = redis.Redis() """ app.config["SESSION_TYPE"] = "filesystem" app.config["SESSION_FILE_DIR"] = "session_dir" Session(app) # 能够看到,在使用层面上,加上三行代码就能够了 @app.route("/login") def login(): return "login" if __name__ == "__main__": app.run(port=8888, debug=True)
能够看到这里多了一个session_dir目录,里面多了两个文件
redis
固然我这里没有设置session,咱们来设置一下
sql
from flask import Flask from flask_session import Session from flask import session app = Flask(__name__) # 个人阿里云没有安装redis,因此这里就使用文件了 """ 若是是redis的话,也是同样的 import redis app.config["SESSION_TYPE"] = "redis" app.config["SESSION_REDIS"] = redis.Redis() """ app.config["SESSION_TYPE"] = "filesystem" app.config["SESSION_FILE_DIR"] = "session_dir" Session(app) # 能够看到,在使用层面上,加上三行代码就能够了 @app.route("/login") def login(): session["username"] = "satori" return "login" @app.route("/index") def index(): print(session["username"]) return "index" if __name__ == "__main__": app.run(port=8888, debug=True)
访问/login,再访问/index
mongodb
能够看到是成功的。此时的session就再也不经过序列化、加密写到用户的浏览器的cookie里面去了,而是写到咱们指定的地方。而后返回给用户浏览器的则是一个随机字符串,用户再来请求的时候会拿随机字符串来进行匹配。 那么这是怎么作到的呢? 咱们以前在看session源码的时候,知道要经过open_session获取session,经过save_session存储session,既然咱们把SecureCookieSessionInterface()给换掉了,那么是否是就觉得着,咱们用来替代的方法中也必需要有open_session和save_session呢?答案是确定的。
每一个SESSION_TYPE都会对应一个类,里面的open_session和save_session会从对应的位置获取和存储session
数据库
WTForms表单的两个主要的功能就是就是验证用户提交数据的合法性以及渲染模板。固然还包括其余的功能:CSRF保护,文件上传等等
flask
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/register" method="post"> <table> <tbody> <tr> <td>姓名:</td> <td><input name="username" type="text"></td> </tr> <tr> <td>密码:</td> <td><input name="password" type="password"></td> </tr> <tr> <td>重复密码:</td> <td><input name="repeat_passwd" type="password"></td> </tr> <tr> <td></td> <td><input type="submit" value="当即注册"></td> </tr> </tbody> </table> </form> </body> </html>
from flask import Flask, request, render_template from wtforms import Form, StringField from wtforms.validators import Length, EqualTo app = Flask(__name__) # 定义一个类,继承自Form class RegisterForm(Form): # 咱们这里的左值,就是html里面的name,这里必定要保持一直,不然不会验证 # 这里传入一个列表,由于验证条件会有多个,因此以列表形式传值,这里表示长度为6到10位 username = StringField(validators=[Length(min=6, max=10)]) password = StringField(validators=[Length(min=6, max=10)]) # 这里的repeat_passwd必需要和password保持一致,因此这里的Length也能够不要,まぁいいや repeat_passwd = StringField(validators=[Length(min=6, max=10), EqualTo("password")]) @app.route("/register", methods=["GET", "POST"]) def register(): if request.method == 'GET': return render_template("register.html") else: # 直接将request.form丢进去就能够了 form = RegisterForm(request.form) # 若是知足条件,那么form.validate()会返回True if form.validate(): # 而后调用form.username.data和form.password.data便可获取值了 # 而form.username和form.password则为StringField,加上()调用的话则会生成html标签 print(f"username={form.username}, password={form.password}") return "登录成功" else: # 没经过的话,那么错误信息会存储在form.errors里面 return f"登陆失败:{form.errors}" if __name__ == "__main__": app.run(port=8888, debug=True)
可是这种报错信息彷佛显得不友好,所以咱们也能够本身指定
浏览器
class RegisterForm(Form): # 咱们这里的左值,就是html里面的name="username",这里必定要保持一直,不然不会验证 # 这里传入一个列表,由于验证条件会有多个,因此以列表形式传值,这里表示长度为6到10位 username = StringField(validators=[Length(min=6, max=10, message="用户名必须在6到10位")]) password = StringField(validators=[Length(min=6, max=10, message="密码必须在6到10位")]) # 这里的repeat_passwd必需要和password保持一致,因此这里的Length也能够不要,まぁいいや repeat_passwd = StringField(validators=[Length(min=6, max=10, message="密码必须在6到10位"), EqualTo("password", message="两次输入的密码不一致")])
经常使用的验证器: 数据发送过来,通过表单验证,所以须要验证器来进行验证,如下是一些经常使用的验证器
cookie
class RegisterForm(Form): # 必须为邮箱 email = StringField(validators=[Email()]) # 必须输入 username = StringField(validators=[InputRequired()]) # 是一个数字,要在18到60之间 age = StringField(validators=[NumberRange(min=18, max=60)]) # 以1开头的11位数字 phone = StringField(validators=[Regexp(r"1\d{10}")]) # url格式 url = StringField(validators=[URL()]) # uuid类型 uuid = StringField(validators=[UUID()])
没有什么技术难度,具体再也不演示了
from flask import Flask, request, render_template from wtforms import Form, StringField from wtforms.validators import Length, EqualTo, Email, InputRequired, NumberRange, Regexp, URL, UUID, ValidationError app = Flask(__name__) class RegisterForm(Form): # 如何自定义表单,好比咱们在html里面有一个name="username" # 那么就须要定义一个函数叫作,validate_username,当验证username的时候,会自动执行相应的函数 username = StringField(validators=[InputRequired(), Length(min=8, max=15, message="用户名要在8到15位")]) # 必须得有username = # 不然即便定义了validate_username也是无效的 def validate_username(self, field): # field.data,就是咱们在html中获取的值 print(type(field)) # <class 'wtforms.fields.core.StringField'> # 若是所有username所有是数字,那么不容许 if set(field.data) <= set("123456789"): raise ValidationError("用户名不能全为数字") @app.route("/register", methods=["GET", "POST"]) def register(): if request.method == 'GET': return render_template("register.html") else: # 直接将request.form丢进去就能够了 form = RegisterForm(request.form) if form.validate(): return "登录成功" else: # 没经过的话,那么错误信息会存储在form.errors里面 return f"登陆失败:{form.errors}" if __name__ == "__main__": app.run(port=8888, debug=True)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .text_color { background-color: yellow; } </style> </head> <body> <form action="/index" method="post"> <table> <tbody> <tr> <td>{{ form.username.label }}</td> <td>{{ form.username(class='text_color') }}</td> </tr> <tr> <td>{{ form.age.label }}</td> <td>{{ form.age() }}</td> </tr> <tr> <td>{{ form.remember.label }}</td> <td>{{ form.remember() }}</td> </tr> <tr> <td>{{ form.tags.label }}</td> <td>{{ form.tags() }}</td> </tr> <tr> <td></td> <td> <input type="submit" value="提交"> </td> </tr> </tbody> </table> </form> </body> </html>
from flask import Flask, request, render_template from wtforms import Form, StringField, BooleanField, SelectField from wtforms.validators import Length, EqualTo, Email, InputRequired, NumberRange, Regexp, URL, UUID, ValidationError app = Flask(__name__) class IndexForm(Form): # 第一个参数指定以后,会自动传到html中的form.username.label,若是不指定,那么自动为左值而且首字母大写 # 而form.username()则是一个input标签,这里的左值username到html中就会变成,name="username" username = StringField("用户名:", validators=[InputRequired()]) # 同理age也是同样,我这里指定了"年龄:", 那么form.age.label就等于"年龄:" # form.age()则是一个input标签,等价于<input name="age" type="text"/> # 固然form.age()里面还能够指定属性,好比class,id等等 age = StringField("年龄:", validators=[InputRequired()]) # 还有BooleanField,在页面中就是一个小框,点击是否肯定 remember = BooleanField("记住我:") # SelectField,则是菜单模式,能够选择 tags = SelectField("选项:", choices=[("1", "古明地觉"), ("2", "椎名真白"), ("3", "四方茉莉")]) @app.route("/index", methods=["GET", "POST"]) def index(): if request.method == "GET": # 若是咱们在html中本身指定表单信息的话,这里是不须要IndexForm的 # 可是如今html中的信息是须要咱们生成的,建立将form丢进去 form = IndexForm() return render_template("index.html", form=form) else: # 当用户把信息填完以后,将request.form再丢进IndexForm中,进行验证 form = IndexForm(request.form) if form.validate(): # 此时form.username获取的是一个StringField # 若是获取值,须要调用form.username.data # form.username()的话,则会打印对应标签 print(type(form.username)) # <class 'wtforms.fields.core.StringField'> print(form.username.name) # username print(form.username.data) # satori print(form.username.type) # StringField print(form.username.label) # <label for="username">用户名:</label> print(form.username()) # <input id="username" name="username" required type="text" value="satori"> print(type(form.age)) # <class 'wtforms.fields.core.StringField'> print(form.age.name) # age print(form.age.data) # 16 print(form.age.type) # StringField print(form.age.label) # <label for="age">年龄:</label> print(form.age()) # <input id="age" name="age" required type="text" value="16"> print(type(form.remember)) # <class 'wtforms.fields.core.BooleanField'> print(form.remember.name) # remember print(form.remember.data) # True print(form.remember.type) # BooleanField print(form.remember.label) # <label for="remember">记住我:</label> print(form.remember()) # <input checked id="remember" name="remember" type="checkbox" value="y"> print(type(form.tags)) # <class 'wtforms.fields.core.SelectField'> print(form.tags.name) # tags print(form.tags.data) # 1 print(form.tags.type) # SelectField print(form.tags.label) # <label for="tags">选项:</label> print(form.tags()) # <select id="tags" name="tags"><option selected value="1">古明地觉</option><option value="2">椎名真白</option><option value="3">四方茉莉</option></select> return f"xxxxx" else: return f"error occurred:{form.errors}" if __name__ == "__main__": app.run(port=8888, debug=True)
flask中如何接收用户上传的文件呢?
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <table> <tr> <td>头像:</td> <td><input type="file" name="avatar"></td> </tr> <tr> <td>描述:</td> <td><input type="text" name="describe"></td> </tr> <tr> <td></td> <td><input type="submit" value="提交"></td> </tr> </table> </form> </body> </html>
from flask import Flask, request, render_template import os app = Flask(__name__) @app.route("/upload", methods=["GET", "POST"]) def upload(): if request.method == 'GET': return render_template("upload.html") else: describe = request.form.get("describe") avatar = request.files.get("avatar") # avatar就是咱们所获取的文件 # avatar.filename则是文件名,那么如何将文件保存起来呢?能够直接使用save # 因为直接使用用户获取的文件名比较危险,那么能够from werkzeug.utils import secure_filename,而后进行转化 avatar.save(os.path.join(os.path.dirname(__file__), "upload_file", avatar.filename)) return f"{avatar.filename}上传成功, 描述信息为{describe}" if __name__ == "__main__": app.run(port=8888, debug=True)
就拿咱们刚才上传的图片为例
from flask import Flask, request, render_template import os app = Flask(__name__) @app.route("/images/<filename>") def get_image(filename): from flask import send_from_directory if os.path.exists(os.path.join(os.path.dirname(__file__), "upload_file", filename)): # send_from_directory表示将图片直接返回 # 接收两个参数,一个是图片的目录,一个是图片的名字 return send_from_directory(os.path.join(os.path.dirname(__file__), "upload_file"), filename) else: return "没有该图片。。。。。。" if __name__ == "__main__": app.run(port=8888, debug=True)
以前咱们接收用户上传的文件,也没有进行判断,好比说用户上传头像,应该是一张图片,可若是用户上传的是txt,或者py文件怎么办呢?这时候咱们应该对用户上传的文件进行一个判断。flask-wtf是一个专门用于对文件进行验证的第三方插件。一样须要pip install flask-wtf
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <table> <tr> <td>头像:</td> <td><input type="file" name="avatar"></td> </tr> <tr> <td>描述:</td> <td><input type="text" name="describe"></td> </tr> <tr> <td></td> <td><input type="submit" value="提交"></td> </tr> </table> </form> </body> </html>
from flask import Flask, request, render_template from wtforms import Form, StringField, FileField from wtforms.validators import InputRequired # FileRequired表示文件必须上传,FileAllowed能够输入容许的文件格式 from flask_wtf.file import FileRequired, FileAllowed from werkzeug.datastructures import CombinedMultiDict app = Flask(__name__) class UpLoadForm(Form): avatar = FileField(validators=[FileRequired(), FileAllowed(["jpg", "png", "gif"], message="文件不符合格式呢?")]) describe = StringField(validators=[InputRequired()]) @app.route("/upload", methods=["GET", "POST"]) def get_img(): if request.method == "GET": return render_template("upload.html") else: # 由于咱们既有文件类型,还有通常的表单类型。所以须要把二者组合在一块 form = UpLoadForm(CombinedMultiDict([request.form, request.files])) if form.validate(): avatar = form.avatar.data # 或者avatar = request.files.get("avatar") describe = form.describe.data # 或者describe= request.form.get("describe") return f"上传成功,上传的文件为:{avatar.filename}, 描述信息为:{describe}" else: return f"error occurred:{form.errors}" if __name__ == "__main__": app.run(port=8888, debug=True)
from flask_wtf.csrf import CsrfProtect # 开启csrf保护 CsrfProtect(app)
注意,须要为CSRF保护设置一个密钥,但一般状况下,和Flask应用的SECRET_KEY是同样的。若是你设置的模板中存在表单,你只须要在表单中添加以下
<form method="post" action="/"> {{ form.csrf_token() }} </form>
若是没有模板中没有表单,你仍然须要一个 CSRF 令牌:
<form method="post" action="/"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> </form>
若是网站没有经过CSRF验证,都会返回400响应,咱们能够自定义这个错误响应:
from flask_wtf.csrf import CsrfProtect csrf = CsrfProtect() @csrf.error_handler def csrf_error(reason): return render_template('csrf_error.html', reason=reason)
这里官方强烈建议对全部视图启用CSRF保护,也提供了给某些视图函数不须要保护的装饰器
@csrf.exempt # 不对index进行保护 @app.route('/foo', methods=('GET', 'POST')) def index(): return "index"
你也能够在全部的视图中禁用CSRF保护,app.config中经过设置 WTF_CSRF_CHECK_DEFAULT
为 False,仅仅当你须要保护的时候选择调用csrf.protect()手动开启保护