Flask入门小记-运行你的第一个Flask项目

2019-01-30  本文已影响0人  古十叶

一、准备工作

        1、全局安装了pipenv工具,然后在项目文件夹下用pipenv install命令为项目安装虚拟环境(项目绑定)

             输入pipenv shell 进入所创建的虚拟环境,在虚拟环境中没有安装任何包。相当于隔离。

        2、为项目所绑定的虚拟环境安装各种所需要的包。如安装flask 输入pipenv install flask

        3、常用命令:进入虚拟环境pipenv shell

                            退出虚拟环境:exit

                            卸载包:pipenv uninstall …

                            查看包依赖关系:pipenv graph

                            查看虚拟环境安装目录 pipenv —venv

        4、为pycharm配置所创建的虚拟环境,在pycharm中导入项目,并选择python解释器为虚拟环境

二、创建一个web项目并运行

        1、实例化Flask对象

        2、定义一个函数并用浏览器运行

        3、启动服务器(在虚拟环境下直接运行实例化了Flask的py文件)

        from flaskimport Flask

        app = Flask(__name__)

        #这个hello函数是视图函数,相当于是mvc中的控制器,除此之外还有基于类的视图,叫即插视图

        #视图函数相对于普通函数,返回值是经过flask封装的一个response对象,如会返回状态码,content-type,http

        #header ,content-type是告诉客户端浏览器解析视图函数返回文本的方式

        #路由注册的常用方法

        @app.route('/hello')

        def hello():

        #这里修改了返回的response对象的content-type,让客户端解析成json格式,用于设计api的时候

        #本质上web服务器的返回值都是字符串,只不过可以通过修改content-type来让客户端解析成不同格式

            headers = {

            'content-type' : ‘application/json'

            }

            #自定义response对象

            response = make_response('<html></html>',201)

            #将修改的header添加至自定义response对象中

            # response.headers = headers

            # return response

            # 也可以换一种方式,如(这种常用):这种不需要前面的自己创建response对象,但是实质上是

            #通过将这三个存入一个元组,然后在变成response对象返回

            return '<html></html>’,201,headers 

        #全局导入配置文件,这里写配置文件(模块)路径,这种方式要求文件中参数必须全部大写

        app.config.from_object('config')

        #另一种注册路由的方式,当你使用基于类的视图就用这种注册方式

        app.add_url_rule('/hello',view_func=hello)

        # app启动参数配置

        # host配置外部可访问的ip地址,配置0.0.0.0就是接受外网访问

        # debug进入调试模式,Flask就可以自动监听文件改动,自动重启服务器,调试模式还可以详细的报错信息

        # port改端口号

        if __name__  ==  '__main__':#确保if里的语句只在入口文件中执行,保证在生产环境中不会执行

            #因为在生产环境中是nginx作为接受请求然后转发至uwsgi,然后由uwsgi加载我们的py文件,此时

            #开发环境的入口文件就只是一个模块文件,如果没有这个if,则也会执行run语句

            app.run(host = '0.0.0.0',debug = app.config['DEBUG’])

三、Flask深入理解

        1、路由机制

        问:如何通过url来访问到不同的视图函数?flask是通过字典的方式,url对应的是字典中的key,而

               视图函数则是值,当不同的url访问时,则对应字典中的值。而若是要通过视图函数反向构建url时

               flask提供了一个叫endpoint的东西。

        问:如何算是成功注册了路由?首先url_map对象中必须要有我们url向endpoint的指向,其次

               view_functions里必须要记录endpoint指向的视图函数

        2、蓝图(blueprint)

             用来解决视图函数注册的问题,可以让视图函数注册在蓝图中,而不是直接注册在app核心对象中。

            类似于这种第三方插件,都必须实例化对象然后注册在flask核心对象中,才可以使用

            在__init__.py中注册蓝图,并导入相关视图函数模块。

        3、在路由注册路径中使用?传递参数

             使用flask中的request,直接导入,在视图函数中使用。

             request必须是由视图函数或者HTTP请求触发,才会成为想要的不可变的字典。

        4、对访问url路径的参数校验: 使用第三方插件WTForms进行参数校验,通过创建类且实例化对象来

            调用方法完成参数校验,获取参数时,使用form.data的方法获取。

            组合使用验证器(函数设计规范):可以使将不同的功能函数写成小函数,然后组合使用

        5、拆分配置文件,一般分为两个secure和setting,secure放机密信息如数据库配置参数和app key,

             setting放非机密信息,一般放生产环境和调试环境不改变的参数。

        6、使用Flask经典错误:No application found. Either work inside a view function or push an application

                context。

                AppContext、RequestContext、Flask与Request之间的关系:

                Flask的上下文环境:1、应用上下文AppContext(封装了Flask和外部的一些参数和方法如pop())   

                2、请求上下文RequestContext (封装了Request和外部一些方法)。(都是对象)

                  若想操作Flask和Request核心对象,请从对应的上下文对象中间接获取核心对象(通过LocalProxy 

                  代理来完成)

                3、 当Flask接收到一个请求,会让RequestContext创建一个对象封装这个请求,然后准备将这个对象

                 入栈,入栈前会检查app核心对象的栈顶元素是否为app,若不是,则新建一个app核心对象入栈,

                 然后才会将RequestContext对象入栈。在使用代理时,current_app和request一直都是指向两个栈的栈顶

                所以就是间接操作栈顶元素,只有当栈顶为空时,使用代理时才会报错。解决方法:手动创建context对象

                然后调用push()入栈。使用后再出栈。(一般做,离线应用,单元测试时候用)若有请求,则Flask会

                自动将app_context入栈

                4、使用with语句来代替手动将app核心对象入栈的操作(相当于语法糖,省略了push和pop操作):

                    with app.app.context():

                        a = current_app

                    上下文管理器:实现了__enter__和__exit__方法的对象,如AppContext

                    上下文表达式:必须要返回一个上下文管理器,如app_context()

                    with的其他使用场景:类似于操作数据库,可以将连接数据库放在__enter__中,将

                    编写sql语句等其他逻辑放在with的代码块中,将释放数据库资源放在__exit__中

                    如class A():

                            def __enter__(self): # 返回值会赋值给as 后的变量

                            def __exit__(self): # 这里用来释放资源和处理异常。返回True || False,默认为False

                                                        # 返回False意味着当出现异常在with中不再需要处理异常,True则需要

                        with A() as obj_a:

                            pass

                    这里A()是上下文表达式

            7、使用@contextmanager(可以将类变成上下文管理器)

                with只能搭配上下文管理器使用,但是上下文管理器要实现__enter__和__exit__方法,很繁琐

                所以可以使用装饰器@contextmanager来装饰某一个函数,在这个函数内部写__enter__和__exit__

                代码,然后配合yield关键字(可以让程序中断后继续执行,相当于加强版的return)如下:

                class MyResource:

def query(self):

print("query")

                @contextmanager

                def make_mysource():

print("connected!")

yield MyResource() # 这里yield后接实例化对象是因为后面的as r 的r要调用query()

print("disconnected")

                with make_mysource()as r:

                    r.query()

四、Flask操作数据库

        1、数据库表的创建方式:

            1)DataBase First :传统的创建表的方式,按照字段添加

            2)Model First :使用工具创建表的结构模型,然后自动生成表

            3)Code First : 特点:专注于业务模型设计,而不是数据库设计模型层 使用sqlalchemy为数据库自

                  动创建表,需在配置文件中配置mysql的uri

        2、关于聚合操作group_by:

            对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是

            不合法的,因为列不在GROUP BY从句中。简而言之,就是SELECT后面接的列必须被GROUP BY

            后面接的列所包含。如: 

            select a,b from table group by a,b,c; (正确) 

            select a,b,c from table group by a,b; (错误) 

            这个配置会使得GROUP BY语句环境变得十分狭窄,所以一般都不加这个配置

            所以要使用distinct,可以将group_by查询的关键字放在distinct中。

        3、一般查询模型内(一张表)的数据,用filter_by即可,若涉及到跨表查询则用db.session.filter,

             更加灵活,括号中写表达式,func.count搭配group_by可完成分组统计,使用in_查询一张表中的

            字段值和一张列表中相等的值,像这样使用链式调用的方式,要用关键字触发:

count_list = db.session.query(func.count(Wish.isbn),Wish.isbn).filter(

Wish.launched ==False,

                Wish.isbn.in_(isbn_list),

                Wish.status ==1).group_by(Wish.isbn).all()

            # 这里的count_list是一个元组,在函数中别返回元组,尽量返回对象或者字典,

            # 返回对象可以用nametuple,这里用返回字典的方式,使用列表推导式

count_list = [{'count':w[0],'isbn':w[1]}for win count_list]

            return count_list

        4、Flask中的first_or_404()方法:

            1、在Flask中查询数据库时经常用到链式调用,而链式调用要用first(),all()等触发,而当查询结果没有

            数据时,也就是first()为空,first_or_404()这个方法就会帮我们自动抛出一个异常,并且跳转到404

            页面,而实现这个方法,flask内部通过对first的封装,还有调用了_abort对象的一个方法,要实现

            把对象当成方法调用,则这个类内部必须实现__call__方法,实质上也就是调用这个__call__()方法。

            意义:1、简化了函数调用,可以直接调用实例化对象。2、统一函数调用接口

            2、那么如果first_or_404()抛出了异常,那页面是如何跳转至404页面的?

            原因在于response读取了NotFound(HttpException)

            3、装饰器app_errorhandler:(基于AOP思想(面向切片编程,将所有可能出现相同的情况集中处理))

                可以帮我们实现当任意页面抛出异常的时候,监听到这个异常,并且按照我们自定义的逻辑去执行。

                在web.init文件中定义如下:(监听到了404状态码的异常)

                @web.app_errorhandler(404)

                def not_found(e): # 从e中读取异常信息

return render_template('404.html'),404

            4、设计数据库时,要合理利用数据冗余记录历史状态:

                    例如完成一笔订单交易,对于这个订单交易表的设计,就要重新增加字段来存储相关信息,如

                    交易人的nickname等,就算这个字段和User表中的字段冗余。因为nickname是可以修改的,如果

                    和user表中关联,则交易记录中的数据也被修改,这是不能接受的

五、Flask中的多线程与线程隔离

        1、进程是竞争计算机资源的基本单位。

        2、线程是进程的一部分,进程与线程关系可以是1:1,也可以是1:n

            线程的引入是因为进程的粒度太大,不能有效利用cpu资源,进程之间的切换消耗很大的资源,

            所以引入一个轻量,小巧的资源调度的单元—线程

        3、进程主要用来分配资源,如cpu,内存;

             而线程主要是利用cpu来执行代码(不可以拥有资源,但可以访问进程资源),所以线程是属于进程

        4、一个文件默认有个主线程来执行,但是可以自己新增一个线程来执行其他函数

            new_t = threading.Thread(target=接函数名,name='自定义新增线程名')

            new_t.start() # 启动新增线程

        5、 Python不能很好地利用cpu多线程的优势,原因在于,pyhton的GIL机制(全局解释器锁)

             即同一时间只允许同一个线程执行。jpython中没有这个解释器。

             但是python对于IO密集型程序(如查询数据库,请求网络资源)有一定意义,python可以在

            一个线程执行请求IO的时候等待返回的那段时间将cpu腾出来给别的线程用。

        6、Flask的线程隔离:

            假设客户端发送了许多不同的request请求给服务器,由于每个发来的request都需要实例化一个Request对象

            来保存请求信息,所以我们的request变量不知道具体指向哪一个实例化的Request对象。解决思路,分别将

            不同实例化的Request对象赋值给不同的request变量,这样就可以一一对应就可以知道,但是这样操作不

            合理,所以我们就采用字典的方式来解决:用不同线程的id号来做key,将实例化的Request做value。

            Flask中解决线程隔离,思想如上,本质采用字典的形式来保存,然后将其封装成Local对象(线程隔离对象)

            操作原理:使用Local对象操作线程中的属性,和其他线程的属性互不干扰。

        7、Flask中的线程隔离栈LocalStack

              Local 和 LocalStack和字典的关系:(都是线程隔离对象)

              被线程隔离的对象AppContext,RequestContext/Request,session,g

              Local是用字典来实现了线程隔离,

              LocalStack是将Local对象当成自己内部属性,从而实现了一个线程隔离的栈。

              LocalStack的用法:和Local差不多,实例化LocalStack对象,然后调用push方法入栈的元素和

                    其他线程属性互不干扰。

        8、使用线程隔离的意义:使当前线程可以正确的引用到他自己所创建的对象,而不是引用到其他

                线程所创建的对象 

        9、flask全局只有一个app核心对象(可以多个),current_app只是对app的引用,所以对current_app隔离没有意义

             而request对象随着线程的创建都会实例化新的,所以要对request对象进行线程隔离。

            AppContext把Flask核心对象当做属性保存起来,RequestContext把Request对象封装保存起来。

            current_app指向的是(LocalStack.top = AppContext  top.app = Flask)

            request指向的是(LocalStack.top = RequestContext  top.request = Request)

六、业务开发

        1、ViewModel:由于向服务器请求api所获得的数据并不能完全符合页面所显示的数据,所以引入

            ViewModel层,在VM层中对获得的数据进行裁剪、修改、合并等操作来满足页面所需要的数据

        2、python可以直接序列化字典,但是不可以直接序列化对象

             python序列化对象:只序列化对象的变量和数据,不序列化方法

            解决:

books.fill(yushu_book,q)

            # 这里若是直接返回books会报错 return(books),因为python不可以序列化一个对象,但是可以序列化字典,

                所以想到了将对象中的内置__dict__

            # 返回,但是又有可能对象中还有对象,所以采用函数式编程思想,用函数将一个不能序列化的对象转化成   

                可以序列化的字典,再将这个函数作为参数传入dumps方法

            return json.dumps(books, default=lambda o: o.__dict__)

            在这个编写函数的时候,我们有时候可以不必考虑参数具体设置,将代码的解释权交给调用函数方

        ps:单页面和多页面网站的区别:

             单页面中的数据是在客户端处理后渲染的,多页面网站是在服务器端渲染的。

        3、用户注册

            1)使用request.form获取用户提交的表单数据,并用实现了WTForm的类来接收得到验证后的form

            2)开始校验,实例化User对象,并将表单数据填入User对象

            3)调用db.session方法,将数据存入数据库,用户密码要加密后存入数据库

            4)注册成功重定向到登录页面

        4、用户登录

            1)同上,也是获取表单数据,并且得到验证后的form

            2)通过query.filter_by()查询数据库中的数据和对应填写数据是否一致,密码要进行加密后再通数据库对比。

            3)所有数据校验通过,使用login_user插件管理登录状态,会自动将登录信息存入cookie

            ps:用户访问权限控制:未登录状态访问需要登录才能访问的视图函数(被login_required装饰),会

                    跳转至登录页面,在login_manager初始化时指定

        5、发送邮件

            使用flask提供的mail插件来发送邮件,在配置文件secure中,配置如下:

                    # Email配置

MAIL_SERVER ='smtp.qq.com'

MAIL_PORT =465

MAIL_USE_SSL =True

MAIL_USE_TSL =False

MAIL_USERNAME ='' #这是发送者邮箱

MAIL_PASSWORD =‘’ #这是授权码

                    1)实例化并且在app中注册  2)在模块中导入 3)

               def send_mail():

msg = Message('title', sender=‘填MAIL_USERNAME', body='邮件正文',

                                                  recipients=['接收邮箱'])

                    mail.send(msg)   4)调用send_mail()即可发送

        6、重置密码

            访问相应view_func,然后填写重置密码的电子邮箱,若查询到表中有,则向这个电子邮箱发送

            重置密码的邮件,通过附带token信息(将用户id序列化存入)。打开邮件,邮件是一段url,访问这个url

            可加载一个重置密码的页面,将token解序列化,获取user id,然后新密码校验成功后,修改数据库信息

        7、设计ViewModel:viewmodel主要是为了在不同场景展示不同的数据形式

8、在交易中的不同状态定义时,可以用枚举类来定义,如下:

            设计viewmodel时基本上都要涉及到单体和集合,可以设计一个单体,然后再设计一个集合,然后

            通过循环将单体存入集合中,设计单体时,可以将单体当做类处理,也可以将单体当成字典返回,

            字典的优势在于代码简单,而类的话可以将数据进行特别处理,所以推荐用类处理。遇到比较多的字段

            的viewmodel,可以结合两者的特点,单体中用字典封装。

七、Flask静态文件,模板文件访问原理

        1、静态文件默认存储位置在app/static,直接浏览器访问路径即可,若要修改static位置,则在注册app核心对象

        或者蓝图的时候就要指定static_folder或static_url_path

        2、模板文件如(html模板)默认放在app/templates中,若要修改位置,操作和app一样,在注册时,修改

            template_folder参数

八、使用Jinja2来将数据解析和展示至html模板中

       1、 在View_func()中用render_template()将数据渲染至指定模板页面,然后在模板页面中使用{{}解析和展示数据

        2、在Jinja2中读取字典和对象,操作方式都一样,都可使用.和[]的形式访问

        3、使用if 和 for in 控制语句 都需要加上{%  %} 如{% if %},而且需要闭合语句,如下:

                (可结合使用python的比较和逻辑运算符)

{%if data.age ==21 and data.name =='leaveye' %}

<span style="font-style:oblique "> {{data.name }}</span>

{%endif %}

        4、使用for遍历字典  

{%for key,value in data.items() %}

<ul>

                        <li>

                            {{key }}:{{value }}

</li>

                    </ul>

{%endfor %}

        5、模板继承

            定义一个总的基础模板,Base.html,然后让子模板继承,此时子模板可不写那些html声明

            ,只需要写和父模板不同的文件即可,然后将子模板插入到父模板对应block中既可 

{%extends 'layout.html' %}

{%block content %}

{{super() }}  //super()是为了不覆盖父模板中的内容

            {# 使用jinja2模板语言解析和展示数据 #}

            {%for key,value in data.items() %}

{{key }}:{{value }}

{%endfor %}

{%endblock %}

        6、Jinja2使用filter(类似于管道命令)

              1、default过滤器:当访问了一个不存在的值,default会将其替换成自定义的值

{{data.school |default(data.school) |default('不存在的变量')}}

            访问了data中一个不存在的school属性,则将其传递给|后的表达式,default给出

            自定义值data.school还是不存在,则继续传递给下个管道,最终显示最后一个管道赋的值,

            当这个值存在,就不会传递给下个管道

        7、反向构建url:为了在html中加载css,js,图片等静态文件,通过endpoint和url_for来完成,如:

            <head>

                <meta charset="UTF-8">    

                <link rel="stylesheet" href="{{url_for('static',filename='test.css') }}">

            </head>

            这里的{{url_for(‘endpoint名’,filename=‘要加载的文件名')

        8、Flask的消息闪现(要添加secret_key)

            可以在视图函数中使用flash(‘给模板传递的消息’,category=‘分类名’)将你自定义分类的消息

            传递给模板,模板接收方式:

{%set messages =get_flashed_messages(category_filter=[‘分类名']) %}

{{messages }}  //由flash传递的消息是一个数组(列表)

            这里用set接收的变量的作用域在整个block,而用with接收,就局限在with和 endwith中

上一篇下一篇

猜你喜欢

热点阅读