Flask基础

Flask基础

对于一个web框架而言,一定要有的就是路由,视图,模板语法

对于一个没有见到的框架,从路由入口,然后走视图

1.谈谈你对django和flask的认识?
	1.django是一个大而全的框架,内置了很多的组件,比如分页,缓存,orm...
    2.flask轻量级的,可扩展性强,可定制性强
    3.两个框架你选择哪个?因人而异
2.flask和django最大的不同点:
	1.request/session(django中的session是依附在request对象中传递进来的,但是flask是导入的)
3.flask知识点
	- 可以设置静态文件和模板(实例化flask对象的时候配置的...)
    - 路由装饰器,对象点route  @app.route('/index',methods=['GET','POST'])
    - 请求相关的
    	request.form
        request.args
        request.method
    - 响应
    	render
        redirect
    -session 
    	# 放值
        session['xx'] = 123
        # 取值
        session.get('xx')

内容详细

知识点:
	给你一个路径,如'xx.xx.ClassFoo',找到这个类获取里面的静态字段
    
    import importlib
    # 如何找到这个类
    path = 'settings.Foo'
    # 先拿到这个路径,然后利用importlib
    p, c = path.rsplit('.',maxsplit=1)
    m    = importlib.import_module(p)
    cls  = getattr(m,c)  # 路径, 类名
    # print(cls)
    print(dir(cls))  # 拿到的是这个类所有的方法
    for k in dir(cls):
        if k.isupper():  
            print(k)  # DEBUG
            v = getattr(cls,k)  # 第一个参数是你要对谁操作,第二个是你要拿谁的值
            print(v)  # True

配置文件

	print(app.config)   # 配置文件的查看
    
    # 修改配置文件的方法一
    	app.config['DEBUG'] = True
    	app.config['DEBUG'] = True
    	app.config['DEBUG'] = True
    	app.config['DEBUG'] = True
        
    # 修改配置文件的方法二
    app.config.from_object('settings.Foo')
    
    # 去看settings中的Foo这个类
    class Foo:
        DEBUG = True
        
   	# 方法二修改配置文件的源码
    
        def from_object(self, obj):
            if isinstance(obj, string_types):  # obj就是'settings.Foo'
                obj = import_string(obj)  # 根据字符串的形式去导入他
                # obj 就是那个传进来的类,在这里就是我们的那个Foo
            for key in dir(obj):
                if key.isupper():
                    # self就是配置文件对象,封装了所有的配置文件
                    self[key] = getattr(obj, key)  # 在这一步进行了修改
                    
        def import_string(import_name, silent=False):  # import_name='settings.Foo'
            import_name = str(import_name).replace(":", ".")  
            try:
                try:
                    __import__(import_name)
                except ImportError:
                    if "." not in import_name:
                        raise
                else:
                    return sys.modules[import_name]

                module_name, obj_name = import_name.rsplit(".", 1)  # 获取路径和类
                module = __import__(module_name, globals(), locals(), [obj_name])
                # __import__类似于importlib.import_moudle(module)  
                try:
                    # 在这一步将获取到的类传了出去,拿到的是路径和类名
                    return getattr(module, obj_name)  
                except AttributeError as e:
                    raise ImportError(e)

            except ImportError as e:
                if not silent:
                    reraise(
                        ImportStringError, ImportStringError(import_name, e), 									sys.exc_info()[2]
                    )
                    
   	# 配置分为开发环境和线上环境
        class Base(object):
            xxx = '123'
        class Pro(Base):  # 线上环境
            DEBUG = False
        class Dev(Base):  # 开发环境
            DEBUG = True
    # 后面我们上线了,只需要更改app.config.from_object('settings.Dev')就好,后面如果还有公用的,只需要来一个继承就好

路由系统

	- endpoint   # 反向生成url,默认是函数名
	- url_for('endpoint')  # 就是反向解析,类似django的reverse
    # 如果没有参数,反向生成路由
    url_for('endpoint')/url_for('index',nid=999)
  
    # 动态路由,得有参数接收一下
    @app.route('/index/<int:nid>', methods=['GET', 'POST'])
    def index(nid):
        print(nid)
        return 'Index'
  # 注意
	'''
		endpoint就是反向解析的name,不定义就是函数名
		路由中可以跟参数,这个参数中间不要留空格,这个值是一个动态的,int表示的是什么类型
		什么都不写就是字符串,但是不允许正则表达式
	'''

视图

FBV和CBV
FBV
@app.route('/index/<int:nid>', methods=['GET', 'POST'])
def index(nid):
    print(nid)
    # 反向生成url
    print(url_for('/index',nid=999)) 
    return 'Index'

请求相关的数据

'''
    flask是直接引用,django是作为一个参数,内部处理机制完全不同
    django请求数据是一个个的传递(门口有个人给了一塌子钱,同学一个个的传递过来)
    flask(直接扔到桌子上,自己导入去取)
'''
	@app.route('/index/<int:nid>', methods=['GET', 'POST'])
    def index(nid):
        print(nid)
        # 请求相关信息
        '''
        request.method  
        request.args
        request.form
        request.value
        request.cookies
        request.headers
        request.path
        request.full_path
        request.script_root
        request.url
        request.base_url
        request.host
        rrequest.files  # 上传文件
        obj = request.files['the_file_name']
        obj.save('/var/www/uploads/' + secure_filenaem(f.filename))  # 将上传的文件保存起来
        '''
      
        return 'Index'  
    

响应

  	# 响应相关的数据
     	   # Json格式也可以返回
           # dic = {'k': v1, 'k2': v2}
           # 返回json格式的有这几种
           # 1.json.dumps(dic)
           # 2.jsonify(dic)
        '''
            return 'Index' 
            return render_template()
            return redict()
            return json.dumps(dic)
            return jsonify(dic)   和django的JsonResponse类似
          
        '''
    定制响应头/cookie
    # 响应头在哪写
    # return 'Index'
	# 对于上面的这种数据,我们如果想要设置响应头,那么我们可以导入make_response,然后进行封装
    obj = make_response('Index')
    obj.headers['xxx'] = '123'
    return obj
	# 对于剩下的几种格式都一样,我们都可以设置响应头
    obj = make_response(jsonify(dic))
    obj.headers['xxx'] = '123'
    return obj
	# 我们可以设置响应头,那么也可以设置cookie
    obj = make_response('Index')
    obj.headers['xxx'] = '123'
    obj.set_cookie('key','value')
    return obj

示例程序一

from flask import Flask,render_template,redirect,session,url_for

app = Flask(__name__)
app.config.from_object('settings.DevelopmentConfig')

STUDENT_DICT = {
    1 : {'name': 'mm', 'age': 2, 'gender': 'male'},
    2 : {'name': 'cc', 'age': 18, 'gender': 'female'},
    3 : {'name': 'yy', 'age': 18, 'gender': 'female'},
}

@app.route('/index')
def index():
    return render_template('index.html',stu_dic=STUDENT_DICT)

@app.route('/info/<int:nid>')
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html',info=info)


@app.route('/delete/<int:nid>')  # 传过来是字符串,int还有个作用转成int
def remove(nid):  # 只需要保证路由一致就好,具体函数名可以不用管
    STUDENT_DICT.pop(nid)
    print(STUDENT_DICT)
    return redirect(url_for('index'))  # 直接跟函数名就好


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

html页面

<!--index页面-->
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    	<h1>学生列表</h1>
        <table border="1">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>性别</th>
                    <th>选项</th>
                </tr>
            </thead>
            <tbody>
                {% for key,value in stu_dic.items() %}
                <tr>
                    <td>{{ key }}</td>
                    <td>{{ value['name'] }}</td>
                    <td>{{ value.get('ae','默认') }}</td>
                    <td>{{ value.gender }}</td>
                    <td>
                        <a href="/info/{{key}}">详细信息</a>
                        <a href="/add/{{key}}">添加</a>
                        <a href="/delete/{{key}}">删除</a>
                    </td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </body>
</html>

<!--详细信息页面-->
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
            <h1>学生详细</h1>
            <ul>
                <!--和python语法一模一样-->
                {% for item in info.values() %}
                <li>{{item}}</li>
                {% endfor %}

            </ul>
        </body>
    </html>
<!--和python的使用一模一样,python怎么用他就怎么用-->

安全登录版本一

@app.route('/login',methods=['GET', 'POST'])  # method默认是GET
def login():
    if request.method == 'GET':
        return render_template('login.html')
    user = request.form.get('user')
    pwd = request.form.get('password')
    if user == 'mcc' and pwd == '123':
        session['user'] = user  # 将东西存到了cookie中
        return redirect('/index')
    return render_template('login.html', **{'error': '用户名或者密码错误'})


@app.route('/index')
def index():
    if session.get('user'):
        return render_template('index.html',stu_dic=STUDENT_DICT)
    return redirect(url_for('login'))
# 方式一:类似上述模式实现登录认证, 但是上述方式每一个函数都要写,我们用装饰器来做

知识点

# 知识点一
import functools

def auth(func):
    # @functools.wraps(func)  # django是wraps(func)
    @functools.wraps(func)  
    def inner(*args, **kwargs):  # 其实最后执行的是inner
        ret = func(*args, **kwargs)
        return ret
    return inner

@auth
def login():
    print('login')

@auth
def detail():
    print('detail')

print(login.__name__)  # 不加 @functools.wraps(func)的时候是inner
print(detail.__name__)  # 加 @functools.wraps(func)的时候是detail

# 知识点二
endpoint默认是函数名,那么如果同名了,怎么解决
def auth(func):
    def inner(*args, **kwargs):  # 其实最后执行的是inner
        ret = func(*args, **kwargs)
        return ret
    return inner
@auth
def login():
    print('login')
@auth
def detail():
    print('detail')
# 如果我们不加@functools.wraps(func)程序报错了
AssertionError: View function mapping is overwriting an existing endpoint function: inner
# 这时候我们打印这个的函数发现我们的名字都是inner,没有指定endpoint,此时所有的都是inner
print(login.__name__)  # inner
print(detail.__name__)  # inner

# 看看源码如何实现的?
   def route(self, rule, **options):  # 入口  rule='/index'
        def decorator(f):  # f就是我们传进来的func
            endpoint = options.pop("endpoint", None)  # 别名
            self.add_url_rule(rule, endpoint, f, **options)  # 走这一步添加url
            return f
        return decorator
	@setupmethod
    def add_url_rule(
        self,
        rule,
        endpoint=None,
        view_func=None,
        provide_automatic_options=None,
        **options
    ):
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)  # 不写默认是函数名
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)  # GET/POST
        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)
        if isinstance(methods, string_types):
            raise TypeError(
                "Allowed methods have to be iterables of strings, "
                'for example: @app.route(..., methods=["POST"])'
            )
        methods = set(item.upper() for item in methods)

        # Methods that should always be added
        required_methods = set(getattr(view_func, "required_methods", ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        if provide_automatic_options is None:
            provide_automatic_options = getattr(
                view_func, "provide_automatic_options", None
            )

        if provide_automatic_options is None:
            if "OPTIONS" not in methods:
                provide_automatic_options = True
                required_methods.add("OPTIONS")
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
        '''
        {
            endpoint:函数
            ‘endpoint':inner函数        }
        '''
        self.url_map.add(rule)
        if view_func is not None:  # view_func我们自己的函数,就是inner
            # view_functions认为他就是一个字典,上面看
            old_func = self.view_functions.get(endpoint) 
            if old_func is not None and old_func != view_func:    # 不加functools.wrap(func)的报错信息
                raise AssertionError(
                    "View function mapping is overwriting an "
                    "existing endpoint function: %s" % endpoint
                )
            self.view_functions[endpoint] = view_func  # 第一次进来‘函数名’=inner
        
 # 装饰器的先后顺序
 # 装饰器需要放在路由的下面,这样子一进来先做的是路由的匹配,之后才会走装饰器,如果装饰器放在路由的上面,那么一进来就会连同下面的函数一起去进行装饰,不可理,所以会将装饰器放在视图函数的下面

装饰器适用--版本二

# 方式二:类似上述模式实现登录认证, 但是上述方式每一个函数都要写,我们用装饰器来做
def auth(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):  # 其实最后执行的是inner
        if session.get('user'):
            ret = func(*args, **kwargs)
            return ret
        return redict(url_for('login'))
    return inner

@app.route('/index')
@auth
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)
# 应用场景:比较少的函数中需要额外添加功能

版本三****实现权限管理

基于before_request
# 记住,当before_request返回的是None,是正常往下走,返回其他都是阻拦程序的运行
@app.before_request
def mmmmrrrr():
    # print('before_request')
    if request.path == '/login':
        return None
    return 'gong'  # 当请求不是login的时候就会直接返回

# 所以我们直接使用这种方式,给函数批量的加入登录认证
@app.before_request
def mmmmrrrr():
    # print('before_request')
    if request.path == '/login':
        return None
    if session.get('user'):
        return None
    return redirect(url_for('login'))

1566713023131

模板的渲染

--基本数据类型:可以执行python的语法,如:dict.get() list['xx']
--传入函数
-django,自动执行
-flask,不自动执行
--全局定义函数,如下
--模板继承
同django一模一样,{% block %}
--静态模板的导入
include
--宏定义,就是自定义一个函数,后面可以多次使用
--安全方式的展示
前端: {{ u|safe }}
后端: Makeup()

<!--模板支持类型-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--两种方式都可以支持-->
{{ users.0 }}
{{ users[0] }}
{{ txt }}
{{ func() }}  # 和django的区别是django会自动加括号调用,而flask不会
{{ sb(6,3) }}  # 模板全局设置函数的方式一
{{ 1|aa(2, 4) }}  # 模板全局设置函数的方式二
{% if 1|aa(2, 4) %}  # 这个可以放在if的后面作为条件
    <div>999</div>
{% else %}
    <div>7777</div>
{% endif %}
</body>
</html>

<!--模板继承-->
<!--母版-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>模板</h1>
{% block content %}
{% endblock %}
</body>
</html>

<!--继承母版-->
{% extends "base.html"%}
<!--两种方式都可以支持-->
{% block content %}
{{ users.0 }}
{{ users[0] }}
{{ txt }}
{{ sb(6,3) }}
{{ 1|aa(2, 4) }}
{% endblock %}

<!--宏-->
{% extends "base.html"%}
<!--两种方式都可以支持-->
{% block content %}
{% macro aaa(name, type='text', value='') %}
<h1>宏</h1>
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
<input type="submit" value="提交">
{% endmacro %}
{{aaa('n1')}}    第一次调用
<!--可以将这种函数放在一个单独的页面中,在使用的时候直接加括号调用就好,类似django的inclution_tag-->
{{aaa('n2')}}    第二次调用
{% endblock %}

<!--静态文件的导入-->
{% include 'form.html %}

模板后端的书写

from flask import Markup   # 这个就是django中的mark_safe
@app.template_global()
def sb(a, c):
    # {{sb(6, 3)}}
    return a+c

@app.template_filter()
def aa(a, b, c):
    # {{1 | aa(2, 4)}}  区别就是这个放在if后面做条件
    return a+b+c

@app.route('/tpl')
def tpl():
    contex = {
        'users': ['longya', 'yyy', 'mcc'],
        'txt': Markup('<input type="text" />')
    }
    return render_template('tpl.html', **contex)

session

1566820542991

flask是将这个session对数据进行加密,保存到用户的浏览器的cookie中,其实session就是一个字典
'''
当请求刚进来的时候,flask会帮我们读取cookie中session对应的值,将该值解密并反序列成为一个字典,放入内存,以便视图函数使用。
当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户的cookie中
'''
# 使用 
@app.route('/sess')
def sess():
    session['k1'] = '123'
    session['k2'] = '234'
    del session['k1']
    return 'session'


闪现

# 在sesson种存储一个数据,读取时通过通过pop将数据移除,只能存储取值一次
# 普通的session设置值,然后发现通过这种方式,session中的值会永久存在,如果我们设置了好多的session的话,那么就会一直存储下来
@app.route('/page1')
def page1():
    session['k1'] = '123'
    return 'session'


@app.route('/page2')
def page2():
    print(session.get('k1'))
    return 'ok'
# 基于上面的这种机制,flask引入了闪现,也就是flash,他会将session设置的值变成一个临时存储的值

from flask import session,get_flashed_messages

@app.route('/page1')
def page1():
    flash('临时存储的数据','error')  # 分类是error
    return 'session'

@app.route('/page2')
def page2():
    print(get_flashed_messages(category_filter=['error']))  # 取分类是category_filter
    return 'ok'

# flash内部源码实现原理
def flash(message, category="message"):
    # 从session中取值,取不到就给一个默认的空列表[]
    flashes = session.get("_flashes", [])  [1,2,3]
    # 支持分类,所以将分类和分类信息加入到列表中
    flashes.append((category, message))
    # 通过session设置从session中取到的值,所以设置的就是一个列表的值
    session["_flashes"] = flashes  # {'_flashes':[1,2,3]}
    # 信号暂时不用看
    message_flashed.send(
        current_app._get_current_object(), message=message, category=category
    )
    
# get_flashed_messages内部源码实现原理
def get_flashed_messages(with_categories=False, category_filter=()):
    # 
    flashes = _request_ctx_stack.top.flashes
    if flashes is None:
        _request_ctx_stack.top.flashes = flashes = (
            session.pop("_flashes") if "_flashes" in session else []
        )   # 通过session给pop出来了,出来的要么是个空,要么是一个空列表
    if category_filter:
        flashes = list(filter(lambda f: f[0] in category_filter, flashes))
    if not with_categories:
        return [x[1] for x in flashes]
    return flashes

中间件

# 需求,想在程序启动之前做一些操作,启动之后做一些操作,通过中间件的形式
'''
我们知道的是,当一个请求开始执行的时候,基于wsgi,走的是run_simple(localhost,port,执行的函数),
那么flask内部是怎么实现?

'''
from flask import Flask
app = Flask(__name__)

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()  # 开始启动flask,开始看执行流程

# 内部源码开始啦
    def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
        ......# 感兴趣的可以看看,我们只看这下面的这一行
        from werkzeug.serving import run_simple
        try:
            # 走的是run_simple方法,第三个参数就是当前对象,就是app,传的是对象的时候,会自动加括号调用,那么对象加括号调用走的是类的__call__方法。
            run_simple(host, port, self, **options) 
        finally:
            self._got_first_request = False
    # 直接搜索当前的__call__方法
     def __call__(self, environ, start_response):
        # 执行的就是对象自己的wsgi_app方法
        return self.wsgi_app(environ, start_response)
# 所以我们想要实现在请求开始之前就做一些操作,我们可以考虑从这几个方面下手
'''
1.直接修改源码      ---不好点就是同事也会拉取到你的代码,所以淘汰
2.重写__call__方法   --可以尝试
3.提供一种新的方法,具体请看下方
'''
# 第三种方法书写
class MiddleWare(object):
    def __init__(self, old):
        self.old = old

    def __call__(self, *args, **kwargs):
        print('来人了')
        return self.old(*args, **kwargs)


if __name__ == '__main__':
    # 将对象的wsgi_app变成了自定义类的对象,此时的old就是原来的wsgi_app
    app.wsgi_app = MiddleWare(app.wsgi_app)
    # 那么后面在执行wsgi_app的时候,发现这个方法已经被我们给覆盖了,就会走我们书写的这个方法,这个时候他已经是一个对象了,对象+()走的就是call方法,那么此时我们在call方法里面做的操作就达到了在不修改源码的情况下增加了新功能。
    app.run()
# 这就是flask的中间件的执行,但是用的不多,主要用的还是before_request这些 

特殊的装饰器****

before_request  ***
after_request   ***
template_global
template_filter
before_first_request   # 第一次请求的时候执行的,之后的时候都不会执行这个装饰器
errorhandler(404)  # 用来返回错误日志的

# django1.9版本之前,中间件的执行顺序也是和flask一样,都是在request的时候返回一个return,就会在返回的时候从最后一个response返回这个请求,在1.9版本之后变成了从当前返回
@app.before_request
def re2():
    print('re2')
    
@app.before_request
def re3():
    print('re3')

@app.after_request
def res1(response):
    print('res1')
    return response

@app.after_request
def res3(response):
    print('res3')
# TypeError: after_request() takes 0 positional arguments but 1 was given  是因为没有接收response
# TypeError: 'NoneType' object is not callable  是因为没有返回response
    return response

# 定制错误页面
@app.errorhandler(404)  # 用来捕获404错误的,返回一个自定义的错误页面
def error(response):
    print('303')
    return 'not found'

@app.route('/index')
def index():
    return 'index'
if __name__ == '__main__':
    app.run()
# 如果有多个request和response,那么执行的顺序分为request和response
# request,按照书写顺序从上到下依次执行
# response,按照书写顺序从下到上依次执行,进行了一个反转
posted @ 2019-08-28 19:52  mcc61  阅读(179)  评论(0编辑  收藏  举报