zoukankan      html  css  js  c++  java
  • flask笔记

    flask 与 HTTP

    HTTP请求

    请求报文

    请求的实质是发送到服务器上的一些数据,浏览器与服务器之间交互的数据称之为报文.

    报文由报文首部和报文主体组成,两者由空行分隔,请求报文的主体一般为空

    报文首部包含了请求的各种信息和设置,比如客户端的类型,是否设置缓存,语言偏好等等

    Request对象

    请求对象Request封装了从客户端发来的请求报文,我们能从它获取请求报文中的所有数据

    属性/方法说明
    args 存储解析后的查询字符串,可以通过字典方式获取键值
    data 包含字符串形式的请求数据
    method 请求的HTTP方法
    json 包含解析后的json数据,内部调用get_json,可通过字典方式获取键值

    获取请求的查询字符串

    from flask import Flask,request

    app = Flask(__name__)
    @app.route('/hello')
    def hello():
       name = request.args.get('name','Flask')
       return '<h1>Hello,%s!</h1>'%name

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

    URL处理

    flask内置的URL变量转换器

    转换器说明
    string 不包含斜线的字符串
    int 整型
    fioat 浮点型
    path 包含斜线的字符串
    any 匹配一系列给定值中的一个元素
    uuid UUID字符串

    请求钩子

    我们需要对请求进行预处理和后处理,这时可以使用Flask提供的一些请求钩子,他们可以注册在请求处理不同阶段执行的处理函数(或称回调函数)

    钩子说明
    before_first_request 注册一个函数,在处理第一个请求前运行
    before_request 注册一个函数,在处理每个请求前运行
    after_this_request 在视图函数中注册一个函数,会在这个请求结束后运行
       
       

    每个钩子可以注册任意多个处理函数,函数名并不是必须和钩子名称相同

    HTTP响应

    响应报文主要由协议版本,状态码,原因短语,响应首部和响应主体组成

    错误响应

    如果想手动返回错误响应,方便的方法是使用Flask提供的abort()函数

    在abort()函数中传入状态码即可返回对应的错误响应

    from flask import Flask,request,abort

    app = Flask(__name__)

    @app.route('/404')
    def not_found():
       abort(404)

    响应格式

    在HTTP响应中,数据可以通过多种格式传输,不同的响应数据格式需要设置不同的MIME类型,MIME类型在首部的Content-Type字段中定义.

    如果使用其他的MIME类型,通过Flask提供的make_response()方法生成响应对象,传入响应的主体作为参数,然后使用响应对象的mimetype属性设置MIME类型

    from flask import Flask,request,abort,make_response
    @app.route('/foo')
    def foo():
       response = make_response('Hello,World')
       response.mimetype = 'text/plain'
       return response
    • 纯文本: text/plain

    • HTML: text/html

    • XML: application/xml

    • JSON: application/json

    jsonify()函数

    借助jsonify()函数,我们仅需要数据或参数,它会对我们传入的参数进行序列化,转换为JSON字符串最为响应的主体,然后生成一个响应对象,并且设置正确的MIME类型

    from flask import jsonify
    @app.route('/fooo')
    def fooo():
       return jsonify(name="Green",gender="male")
    既可以传入普通参数,也可以传入关键字参数
    @app.route('/foooo')
    def foooo():
       return jsonify({"name"="Green","gender"="male"})

    cookie

    在Flask中,如果想要在响应中添加一个cookie,使用Response类提供的set_cookie()方法,使用这个方法,需要先使用make_response()方法手动生成一个响应对象,传入响应主体作为参数,

    Response类的常用属性和方法

    方法/属性说明
    headers 表示响应首部,可以像字典一样操作
    status 状态码,文本类型
    status_code 状态码,整型
    mimetype MIME类型
    set_cookie 可以用来设置一个cookie

    set_cookie方法的参数

    属性说明
    key cookie的键
    value cookie的值
    max_age cookie被保存的时间数,单位为秒
    expires 具体的过期时间
    path 限制cookie只在给定的路径可用,默认为整个域名
    domain 设置cookie可用的域名
    secure 如果设置为True,只有通过HTTPS才可以使用
    httponly 如果设置为True,禁止客户端JavaScript获取cookie

    设置程序秘钥

    session通过秘钥对数据进行签名以加密数据,因此得先设置一个秘钥,秘钥就是一个具有一定复杂度和随机性的字符串.

    程序的秘钥可以通过Flask.secret_key属性或配置变量SECRET_KEY设置

    更安全的做法是把秘钥写进系统环境变量,或者保存在.env文件中

    登出用户

    @app.route('/logout')
    def logout():
       if "logged_in" in session:
           session.pop("logged_in")
       return redirect(url_for('hello'))

    Flask上下文

    flask有两种上下文,程序上下文和请求上下文

    程序上下文存储了程序运行所必须的信息

    请求上下文包含了请求的各种信息,比如请求的URL,请求的HTTP方法

    Flask的上下文变量

    变量名上下文类别说明
    current_app 程序上下文 指向处理请求的当前程序实例
    g 程序上下文 用于存储全局数据,每次请求都会重设
    request 请求上下文 封装客户端发出的请求报文数据
    session 请求上下文 用于记住请求之间的数据,通过签名的cookie实现

    在hello视图函数中从查询字符串获取name值,如果每一个视图都需要这个值,那么就要在每个视图重复这行代码,借助g我们可以将这个操作移动到before_request处理函数中执行,然后保存在g的任意属性上

    from flask import g

    @app.before_request
    def get_name():
       g.name = request.args.get('name')

    设置这个函数后,其他视图中可以直接使用g.name获取对应的值,另外,g也支持使用类似字典的get(),pop(),以及setdefault()方法进行操作

     

    模板

    模板引擎的作用就是读取并执行模板中的特殊语法标记,根据传入的数据将变量替换为实际值,输出最终的HTML页面,这个过程呗称之为渲染

    上下文

    内置上下文变量

    Flask在模板上下文中提供了一些内置变量,可以在模板中直接使用

    标准模板全局变量

    变量说明
    config 当前的配置对象
    request 当前的请求对象,在已激活的请求环境下可用
    session 当前的会话对象,在已激活的请求环境下可用
    g 与请求绑定的全局变量,在已激活的请求环境下可用

    自定义上下文

    如果多个模板都要使用同一变量,更好的办法是设置一个模板全局变量,Flask提供了一个app.context_process装饰器,可以用来注册模板上下文处理函数

    @app.context_processor
    def inject_foo():
       foo = {"age":26}
       return dict(foo=foo)

    当我们渲染任意一个模板时,所有使用qpp.context_process装饰器注册的模板上下文处理函数都会被执行,这些函数的返回值会被添加到模板中,因此我们可以在模板中直接使用foo变量

    全局对象

    全局对象是指在所有模板中都可以直接使用的对象

    jinja2内置全局函数

    函数说明
    range([start,]stop[,step]) 和python中的用法相同
    lipsum(n=5,html=True,min=20,max=100) 生成随机样本,可以在测试的时候用来填充页面,默认生成5段HTML文本,每段包含20-100个单词
    dict(**item) 和python的dict()用法相同

    自定义全局函数

    默认使用函数的原名称传入模板,app.template_global()仅能注册全局函数

    @app.template_global()
    def bar():
       return 'I am bar'

    其他模板可以直接使用:

    <h2>{{bar()}}</h2>

    过滤器

    内置过滤器

    过滤器说明
    default 设置默认值,默认值作为参数传入,别名为d
    escape(s) 转义HTML文本,别名为e
    first(seq) 返回序列的第一个元素
    last(seq) 返回序列的最后一个元素
    random(seq) 返回序列的随机元素
    safe(value) 将变量标记为安全,避免转义
    trim(value) 清除变量值前后的空格

    测试器

    内置测试器

    测试器说明
    callable(object) 判断对象是否可调用
    define(value) 判断变量是否已定义
    none(value) 判断变量是否为none
    number(value) 判断变量是否是数字
    sequence(value) 判断变量是否是序列
    sameas(value,other) 判断变量与other是否指向相同的内存地址
       

    如:

    {% if foo is sameas bar %}

    模板环境对象

    配置选项,上下文变量,全局函数,过滤器和测试器存储在app.jinja_env

    模板环境中的全局函数,过滤器,测试器分别存储在Environment对象的globals,filters和test属性中.

    添加自定义全局对象

    和app.template_global装饰器不同,直接操作globals字典允许我们传入任意python对象,

    def bar():
       return 'i am bar'
    foo = 'i am foo'

    app.jinja_env.globals['bar'] = bar
    app.jinja_env.globals['foo'] = foo

    添加自定义过滤器

    def smiling(s):
       return s + ':)'
    app.jinja_env.filters["smiling"] = smiling

     

    宏:

    模板中的宏和python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,把一些不固定的值抽取出来当成一个变量,使用宏的时候,参数可以设置为默认值

    为了方便管理,把宏存储在单独的文件中,这个文件通常命名为macros.html 或_macros.html使用macro和endmacro标签声明宏的开始和结束,在开始标签中定义宏的名称和接受的参数.

    {% macro qux(amount=1) %}
    {% if amount == 1 %}
       I am qux
    {% elif amount > 1 %}
       We are quxs.
    {% endif %}
    {% endmacro %}

    使用时,使用import语句导入它,然后作为函数调用,传入必要的参数

    {% from 'macros.html' import qux %}
    ...
    {{ qux(amoubt=5)}}

     

    空白控制

    如果想在渲染时自动去掉空行,可以在定界符内侧添加减号

    <div>
      {% if True -%}
           <p>Hello !</p>
      {%- endif %}
    </div>

    我们也可以使用模板环境对象提供的trim_blocks,lstrip_blocks属性设置,前者删除jinja2语句的第一个空行,后者用来删除jinja2语句所在行之前的空格和制表符.

    app.jinja_env.trim_blocks = True
    app.jinja_env.lstrip_blocks = True

    宏内的空白控制行不受trim_blocks,和lstrip_blocks属性控制,需要进行手动控制

    {% macro qux(amount=1) %}
    {% if amount == 1 -%}
       I am qux
    {% elif amount > 1 -%}
       We are quxs.
    {%- endif %}
    {% endmacro %}

    加载静态文件

    <img src="{{ url_for('static', filename='avatar.jpg') }}" width="50"> {{ user.username }}
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css' ) }}">

    Favicon

    是一个在浏览器标签页地址栏书签收藏夹显示的小图标

    使用宏加载静态资源

    为了方便加载静态资源,我们可以创建一个专门用于加载静态资源的宏

    {% macro stacic_file(type,filename_or_url,local=True) %}
      {% if local %}
          {% set filename_or_url=url_for('static',filename=filename_or_url) %}
      {% endif %}
      {% if type == 'css' %}
           <link rel="stylesheet" type="text/css" href="{{ filename_or_url }}">
      {% endif %}
      {% if type == 'js' %}
           <link  type="text/javascript" href="{{ filename_or_url }}">
      {% endif %}
      {% if type=='icon' %}
           <link rel="icon" href="{{ filename_or_url }}">
      {% endif %}
    {% endmacro %}

    在模板中导入宏后,只需要在调用的时候传入静态资源的类别和文件路径就会获得完整的资源加载语句,如:

    static_file('css','css/bootstrap.min.css')

    使用它也可以从CDN中加载资源,只需要将关键字参数设置为False

    消息闪现

    使用功能flash()函数发送的消息会存储在session中,我们需要在模板中使用全局函数get_flashed_message()获取消息并将其展示出来

    在基模板中渲染flash消息

    <main>
      {% for message in get_flashed_message()  %}
           <div class="alert">{{ message }}</div>
      {% endfor %}
      {% block content %}
      {% endblock %}
    </main>

     

     

     

    表单

    定义表单类

    from flask_wtf import FlaskFormfrom wtforms import SubmitField,StringField,PasswordField,BooleanFieldfrom wtforms.validators import DataRequired,Lengthclass Login(FlaskForm):    username = StringField('Username',validators=[DataRequired()])    password = PasswordField('Password',validators=[DataRequired,Length(8,128)])    remember = BooleanField('Remember me')    submit = SubmitField('Log in')

    常用的WTForms

    字段类说明对应的HTML显示
    BooleanField 复选框,值被处理为True或False <input type = "checkbox">
    DateField 文本字段,值会被处理为datetime.date <input type = "text">
    DateTimeField 文本字段,值会被处理为datetime.datetime <input type = "text">
    FileField 文件上传字段 <input type = "file">
    SelectField 下拉列表 <select><option></option></select>
    SubmitField 提交按钮 <input type = "submit">
    PasswordField 密码文本字段 <input type = "password">

    实例化字段常用参数

    参数说明
    lable 字段标签<lable>的值,渲染后显示在输入字段前的文字
    render_kw 一个字典,用来设置对应的HTML<input>标签的属性,比如传入{“placehoder”:“Your name”}渲染后的HTML代码会将<input>标签的placehoder属性设为Your name
    validators 一个列表,会在表单提交后逐一调用验证表单数据
    default 为表单字段设置默认值

    在实例化验证表单类时,message参数用来传递自定义错误信息,如果没有设置则使用自定义的错误信息

    如:

        name = StringField('Your name',validators=[DataRequired(message="名字不能为空")]) 

    使用render_kw属性

    username = StringField("Username",render_kw={"placehoder":"Your name"})

    调用后输出的HTML代码如下所示

    <input type="text" id="username" name="username" placeholder="Your name">

    处理表单数据

    表单数据的处理大致会经历一下步骤

    • 解析请求,获取表单数据

    • 对数据进行必要的转换,比如勾选框的值转换成python的布尔值

    • 验证数据是否符合要求,同时验证CSRF令牌

    • 如果验证未通过则需要生成错误信息,同时在模板中显示错误信息

    • 如果验证通过,就把数据保存到数据库或者做进一步的处理

    WTForms会自动对CSRF令牌进行验证,如果没有渲染该字段,会导致验证出错

    Flask-WTF提供的validate_on_submit()方法合并了这两个操作

    @app.route('/basic')
    def basic():
       form = LoginForm()
       if form.validate_on_submit():
           username = form.username.data
           flash('Welcome home,%s!'% username)
           return redirect(url_for('index'))
       return render_template('basic.html',form=form)

     

    数据库操作

    默认情况下,Flask-SQLAlchemy会自动为模型生成一个repr()方法,当在调用模型的对象时,repr()方法会返回一个类似<模型类名 主键值>的字符串** **

    比如<Note 2>

    例:

    class Note(db.model):
      ...
       def __rper__(self):
           return '<Note %r>'%self.body

    在flask shell中的操作

    如果将模型类定义在单独的模块中,那么必须在调用db.creat_all()方法前导入相应模块,以便让SQLAlchemy获取模型类被创建时生成的表信息,进而正确生成数据库

    1.creat

    >>> from app import db,Note
    >>> note1 = Note(body='remember sanny')
    >>> note2 = Note(body='SHAVE')
    >>> note3 = Note(body='He is the one')
    >>> db.session.add(note1)
    >>> db.session.add(note2)
    >>> db.session.add(note3)
    >>> db.session.commit()

    2.read

    一个完整的查询遵循下面的模式** **

    <模型类>.query.<过滤方法>.<查询方法>

    >>> Note.query.all()
    [<Note 1>, <Note 2>, <Note 3>]
    >>> Note.query.get(2)
    <Note 2>
    >>> Note.query.count()
    3
    >>> Note.query.first()
    <Note 1>

    filter()是最基础的查询方法,相比较而言,filter_by()方法更易于使用,在filter_by()方法中,可以使用关键字表达式来指定过滤规则,如:

    >>> Note.query.filter_by(body='SHAVE').first()
    <Note 2>

    like:

    filter(Note.body.like('%foo%'))

    in:
    filter(Note.body.in_(['foo','bar','zaz']))

    and:
    form sqlalchemy import and_

    filter(and_(Note.body == 'foo',Note.title == 'FooBar'))

    3.update

    直接赋值给模型类的字段属性就可以改变字段值,然后调用commit()方法提交即可

    >>> note = Note.query.get(2)
    >>> note.body
    'SHAVE'
    >>> note.body = 'SHAVE LEFT THIGH'
    >>> db.session.commit()

    4.delete

    删除记录和添加记录很相似,把add()方法换成delete()方法,最后都要调用commit()方法提交修改

    >>>note = Note.query.get(2)
    >>>db.session.delete(note)
    >>>db.session.commit()

    定义关系

    class Author(db.Model):
       id = db.Column(db.Integer,primary_key=True)
       name = db.Column(db.String(70),unique=True)
       phone = db.Column(db.String(20))
       articles = db.relationship('Article')

    class Article(db.Model):
       id = db.Column(db.Integer,primary_key=True)
       title = db.Column(db.String(50),index=True)
       body = db.Column(db.Text)
       author_id = db.Column(db.Integer,db.ForeignKey('author.id'))

    一对多

    定义外键

    外键只能存储单一数据,所以外键总是在多这一侧,字段使用db.ForeignKey类定义为外键,传入关系另一侧的表名和主键字段名** **

    定义关系属性

    关系属性的名称没有限制,相当于一个快捷查询,不会作为字段写入数据库中,如articles=db.relationship('Article')

    这个关系返回多个记录,称之为集合关系集合,relationship()函数的第一个参数为关系另一侧的模型名称,它会告诉SQLAlchemy会找到另一侧(即aiticle表)的外键字段(author_id),然后反向查询article表中的所有author_id值作为当前的表主键值(author.id)的记录,返回包含这些记录的列表,也就是返回某个作者对应的多篇文章记录

    建立关系

    建立关系有两种方式,第一种方式为外键字段赋值,

    foo = Author(name='Foo')
    spam = Article(title='Spam')
    ham = Article(title='Ham')

    sapm.author_id = 1
    ham.author_id = 1
    db.session.commit()

    另一种方式是操作关系属性

    foo.articles.append(spam)
    foo.articles.append(ham)
    db.session.commit()

    建立双向关系

    class Writer(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(70),unique=True)
       books = db.relationship('Book',back_populates='writer')
       
    class Book(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(50),index=True)
       writer_id = db.Column(db.Interger,db.ForeignKey('writer.id'))
       writer = db.relationship('Writer',back_populates='books')

    使用backref简化双向关系

    在一对多关系中,backref自动为关系另一侧添加关系属性,作为反向引用,赋予的值会作为关系另一侧的关系属性名称,比如在Singer一侧的关系函数中将backref参数设为singer,SQLAlchemy会自动为Song类添加一个singer属性

    class Singer(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(70),unique=True)
       songs = db.relationship('Song',backref='singer')
       
    class Song(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(50),index=True)
       singer_id = db.Column(db.Interger,db.ForeignKey('singer.id'))

    多对一

    多个居民居住在同一个城市,关系属性在关系模式的出发侧定义,当出发点在“多”这一侧时,在Citizen中添加一个关系属性city来获取对应的城市对象,这个关系属性返回单个值,称之为标量关系属性,所以在多对一关系属性和外键都定义在多这一侧

    class Citizen(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(70),unique=True)
       city_id = db.Column(db.Interger,db.ForeignKey('city.id'))
       city = db.relationship('City')
    class City(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(30),unique=True)

    一对一

    一对一关系实际上是通过建立双向关系的一对多关系的基础上转化而来,要确保关系两侧的关系属性都是标量属性,都只返回单个值,所以要在定义集合属性的关系函数中将uselist参数设为False,这时一对多关系被转换为一对一关系

    class Coutry(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(30),unique=True)
       capital = db.relationship('Capital',uselist=False)
    class Capital(db.Model):
       id = db.Column(db.Interger,primary_key = True)
       name = db.Column(db.String(30),unique=True)
       country_id = db.Column(db.Interger,db.ForeignKey('country.id'))
       country = db.relation('Country')

    多对多

    想要表示多对多关系,除了关系两侧的模型外,还需要创建一个关联表,关联表不存储数据,值用来存储关系两侧模型的外键对应关系

    secondary需要值设置为关联表的名称

    association_table = db.Table('association',
                                db.Column('student_id', db.Integer, db.ForeignKey('student.id')),
                                db.Column('teacher_id', db.Integer, db.ForeignKey('teacher.id'))
                                )
    class Student(db.Model):
       id = db.Column(db.Integer, primary_key=True)
       name = db.Column(db.String(70), unique=True)
       grade = db.Column(db.String(20))
       teachers = db.relationship('Teacher',
                                  secondary=association_table,
                                  back_populates='students')  # collection
       def __repr__(self):
           return '<Student %r>' % self.name
    class Teacher(db.Model):
       id = db.Column(db.Integer, primary_key=True)
       name = db.Column(db.String(70), unique=True)
       office = db.Column(db.String(20))
       students = db.relationship('Student',
                                  secondary=association_table,
                                  back_populates='teachers')  # collection
       def __repr__(self):
           return '<Teacher %r>' % self.name

     

    更新数据库

    重新生成表

    @app.cli.command()
    @click.option('--drop', is_flag=True, help='Create after drop.')
    def initdb(drop):
       if drop:
           click.confirm('This operation will delete the database,do you want to continue?',abort=True)
           db.drop_all()
           click.echo('Drop tables.')
       db.create_all()
       click.echo('Initialized database.')

    执行下面命令会重建数据库和表:

    flask initdb --drop

    使用Flask-Migrate迁移数据库

    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    from flask_migrate import Migrate

    add = Flask(__name__)
    ...
    db=SQLAlchemy(app)
    migrate=Migrate(app, db)

    创建迁移环境: flask db init

    生成迁移脚本:flask db migrate

    更新数据库: flask db upgrade

    级联操作

     

     

     

     

     

     

  • 相关阅读:
    windows下Yarn安装与使用(两种方法)
    git配置公钥---解决码云出现git@gitee.com: Permission denied (publickey)
    npm使用国内镜像的两种方法
    【LeetCode】33. Search in Rotated Sorted Array (4 solutions)
    【LeetCode】83. Remove Duplicates from Sorted List
    【LeetCode】82. Remove Duplicates from Sorted List II
    【LeetCode】85. Maximal Rectangle
    【LeetCode】84. Largest Rectangle in Histogram
    【LeetCode】87. Scramble String
    【LeetCode】162. Find Peak Element (3 solutions)
  • 原文地址:https://www.cnblogs.com/erlchixiha/p/11864936.html
Copyright © 2011-2022 走看看