Flask路由以及视图

 

url->views,实现原理

Flask中的 路由设置与django的有很大的区别,因为,Flask的轻量级,所以,Flask的用法会更简单:

在views上面加上装饰器,app.route() 即可。url的命名,反向解析,接受的请求的格式的处理,这一切都只需要填写参数即可。

route()的源码:

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

"""
可以看见,这个装饰器,本身执行的是 decorator方法,
在decorator中,调用了 add_url_rule 方法。
从方法的字面意思上理解,就是添加 url 到rule去。
下面看一下 add_url_rule的代码:
"""

    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)

        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)

        required_methods = set(getattr(view_func, 'required_methods', ()))

        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

        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

看到上面的代码可以知道,Flask 建立url——》view 路由视图关系 正真的执行的是 add_url_rule() 函数,

并且,将路由(“/index”)这个封装了一个 Rule类,最后全部封装到了 url_map 里面。

所以,如果想要添加一个路由关系, 除了加装饰器之外还可以直接调用这个方法:

示例:

from flask import Flask

app = Flask(__name__)
app.config["DEBUG"] = True


def index():
    
    return "index"

app.add_url_rule("/index", f=index)


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

 

视图函数详解

- app.route()参数解析:

  - rule: 路径; 字符串形式的例如: "/index"

    - 路径添加参数:@app.route("/index/<int:id>"): 

      表示 从将id 传入视图函数, 且必须为int类型;

      当有参数的时候, 在反向解析的时候, 需要将参数传入 例:url_for("index", nid=999 )

示例:

from flask import Flask

app = Flask(__name__)
app.config["DEBUG"] = True

@app.route("/index/<int:id>")
def index(id):
    print(id, type(id))
    return "index"

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

 

    - 自定义路由中的参数类型:

      - 自定义一个继承 BaseConverter 的类 从werkzeug.routing 中导入。

      - app.url_map.converters["reg"] = 自定义的类
      - 在app.route("/index/<reg(\d+):nid>")中使用。
      - 当请求进来时会先实例化自定义的类,并且将nid的值传入到类中的to_python 函数中并调用
      - 当在反向生成url的时候会执行 to_url 的函数,也就是说在CBV中的 url_for() 会先执行to_url 函数。

      - 示例:

from flask import Flask
from werkzeug.routing import BaseConverter


class MyConverter(BaseConverter):
    def __init__(self, map, regex):
        super(MyConverter, self).__init__(map)
        self.regex = regex

    def to_python(self, value):
        """
        路由匹配时,匹配成功后传递给视图函数中参数的值
        :param value:
        :return:
        """
        return int(value)

    def to_url(self, value):
        """
        使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
        :param value:
        :return:
        """
        val = super(MyConverter, self).to_url(value)
        return val


app = Flask(__name__)
app.url_map.converters['reg'] = MyConverter


@app.route('/index/<reg("\d+"):nid>')
def index(nid):
    print(nid, type(nid))
    print(url_for('index', nid=987))
    return "index"


if __name__ == '__main__':
    app.run()
自定义路由中的参数类型

 

  - methods:请求方式; 列表中放字符串 例如: ["POST", "GET"]

  - endpoint:别名;用于反向解析, 如果没有定义的话,默认为被装饰的view函数的名字;

    - 注意:不用让endpoint重名;如果重名函数也一定要相同

  - redirect_to:重定向;当访问原来的url的时候,重定向到新的视图函数中; 实例中用到的地方,就是当项目跟新后,旧的页面不想在被访问后,可以在用户访问的时候,直接跳转到新的页面。

  - strict_slashes:对URL最后的 / 符号是否严格要求; 默认值为None

  - view_func:视图函数的名字

  - ... 还有几个其他的参数,具体参数,可以去Rule类中查看

class Rule(RuleFactory):

    def __init__(self, string, defaults=None, subdomain=None, methods=None,
                 build_only=False, endpoint=None, strict_slashes=None,
                 redirect_to=None, alias=False, host=None):
        if not string.startswith('/'):
            raise ValueError('urls must start with a leading slash')
        self.rule = string
        self.is_leaf = not string.endswith('/')

        self.map = None
        self.strict_slashes = strict_slashes
        self.subdomain = subdomain
        self.host = host
        self.defaults = defaults
        self.build_only = build_only
        self.alias = alias
        if methods is None:
            self.methods = None
        else:
            if isinstance(methods, str):
                raise TypeError('param `methods` should be `Iterable[str]`, not `str`')
            self.methods = set([x.upper() for x in methods])
            if 'HEAD' not in self.methods and 'GET' in self.methods:
                self.methods.add('HEAD')
        self.endpoint = endpoint
        self.redirect_to = redirect_to

        if defaults:
            self.arguments = set(map(str, defaults))
        else:
            self.arguments = set()
        self._trace = self._converters = self._regex = self._argument_weights = None

    def empty(self):
        """
        Return an unbound copy of this rule.

        This can be useful if want to reuse an already bound URL for another
        map.  See ``get_empty_kwargs`` to override what keyword arguments are
        provided to the new copy.
        """
        return type(self)(self.rule, **self.get_empty_kwargs())

    def get_empty_kwargs(self):
        """
        Provides kwargs for instantiating empty copy with empty()

        Use this method to provide custom keyword arguments to the subclass of
        ``Rule`` when calling ``some_rule.empty()``.  Helpful when the subclass
        has custom keyword arguments that are needed at instantiation.

        Must return a ``dict`` that will be provided as kwargs to the new
        instance of ``Rule``, following the initial ``self.rule`` value which
        is always provided as the first, required positional argument.
        """
        defaults = None
        if self.defaults:
            defaults = dict(self.defaults)
        return dict(defaults=defaults, subdomain=self.subdomain,
                    methods=self.methods, build_only=self.build_only,
                    endpoint=self.endpoint, strict_slashes=self.strict_slashes,
                    redirect_to=self.redirect_to, alias=self.alias,
                    host=self.host)

    def get_rules(self, map):
        yield self

    def refresh(self):
        """Rebinds and refreshes the URL.  Call this if you modified the
        rule in place.

        :internal:
        """
        self.bind(self.map, rebind=True)

    def bind(self, map, rebind=False):
        """Bind the url to a map and create a regular expression based on
        the information from the rule itself and the defaults from the map.

        :internal:
        """
        if self.map is not None and not rebind:
            raise RuntimeError('url rule %r already bound to map %r' %
                               (self, self.map))
        self.map = map
        if self.strict_slashes is None:
            self.strict_slashes = map.strict_slashes
        if self.subdomain is None:
            self.subdomain = map.default_subdomain
        self.compile()

    def get_converter(self, variable_name, converter_name, args, kwargs):
        """Looks up the converter for the given parameter.

        .. versionadded:: 0.9
        """
        if converter_name not in self.map.converters:
            raise LookupError('the converter %r does not exist' % converter_name)
        return self.map.converters[converter_name](self.map, *args, **kwargs)

    def compile(self):
        """Compiles the regular expression and stores it."""
        assert self.map is not None, 'rule not bound'

        if self.map.host_matching:
            domain_rule = self.host or ''
        else:
            domain_rule = self.subdomain or ''

        self._trace = []
        self._converters = {}
        self._static_weights = []
        self._argument_weights = []
        regex_parts = []

        def _build_regex(rule):
            index = 0
            for converter, arguments, variable in parse_rule(rule):
                if converter is None:
                    regex_parts.append(re.escape(variable))
                    self._trace.append((False, variable))
                    for part in variable.split('/'):
                        if part:
                            self._static_weights.append((index, -len(part)))
                else:
                    if arguments:
                        c_args, c_kwargs = parse_converter_args(arguments)
                    else:
                        c_args = ()
                        c_kwargs = {}
                    convobj = self.get_converter(
                        variable, converter, c_args, c_kwargs)
                    regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
                    self._converters[variable] = convobj
                    self._trace.append((True, variable))
                    self._argument_weights.append(convobj.weight)
                    self.arguments.add(str(variable))
                index = index + 1

        _build_regex(domain_rule)
        regex_parts.append('\\|')
        self._trace.append((False, '|'))
        _build_regex(self.is_leaf and self.rule or self.rule.rstrip('/'))
        if not self.is_leaf:
            self._trace.append((False, '/'))

        if self.build_only:
            return
        regex = r'^%s%s$' % (
            u''.join(regex_parts),
            (not self.is_leaf or not self.strict_slashes) and
            '(?<!/)(?P<__suffix__>/?)' or ''
        )
        self._regex = re.compile(regex, re.UNICODE)

    def match(self, path, method=None):
        """Check if the rule matches a given path. Path is a string in the
        form ``"subdomain|/path"`` and is assembled by the map.  If
        the map is doing host matching the subdomain part will be the host
        instead.

        If the rule matches a dict with the converted values is returned,
        otherwise the return value is `None`.

        :internal:
        """
        if not self.build_only:
            m = self._regex.search(path)
            if m is not None:
                groups = m.groupdict()
                # we have a folder like part of the url without a trailing
                # slash and strict slashes enabled. raise an exception that
                # tells the map to redirect to the same url but with a
                # trailing slash
                if self.strict_slashes and not self.is_leaf and \
                        not groups.pop('__suffix__') and \
                        (method is None or self.methods is None or
                         method in self.methods):
                    raise RequestSlash()
                # if we are not in strict slashes mode we have to remove
                # a __suffix__
                elif not self.strict_slashes:
                    del groups['__suffix__']

                result = {}
                for name, value in iteritems(groups):
                    try:
                        value = self._converters[name].to_python(value)
                    except ValidationError:
                        return
                    result[str(name)] = value
                if self.defaults:
                    result.update(self.defaults)

                if self.alias and self.map.redirect_defaults:
                    raise RequestAliasRedirect(result)

                return result

    def build(self, values, append_unknown=True):
        """Assembles the relative url for that rule and the subdomain.
        If building doesn't work for some reasons `None` is returned.

        :internal:
        """
        tmp = []
        add = tmp.append
        processed = set(self.arguments)
        for is_dynamic, data in self._trace:
            if is_dynamic:
                try:
                    add(self._converters[data].to_url(values[data]))
                except ValidationError:
                    return
                processed.add(data)
            else:
                add(url_quote(to_bytes(data, self.map.charset), safe='/:|+'))
        domain_part, url = (u''.join(tmp)).split(u'|', 1)

        if append_unknown:
            query_vars = MultiDict(values)
            for key in processed:
                if key in query_vars:
                    del query_vars[key]

            if query_vars:
                url += u'?' + url_encode(query_vars, charset=self.map.charset,
                                         sort=self.map.sort_parameters,
                                         key=self.map.sort_key)

        return domain_part, url

    def provides_defaults_for(self, rule):
        """Check if this rule has defaults for a given rule.

        :internal:
        """
        return not self.build_only and self.defaults and \
            self.endpoint == rule.endpoint and self != rule and \
            self.arguments == rule.arguments

    def suitable_for(self, values, method=None):
        """Check if the dict of values has enough data for url generation.

        :internal:
        """
        # if a method was given explicitly and that method is not supported
        # by this rule, this rule is not suitable.
        if method is not None and self.methods is not None \
           and method not in self.methods:
            return False

        defaults = self.defaults or ()

        # all arguments required must be either in the defaults dict or
        # the value dictionary otherwise it's not suitable
        for key in self.arguments:
            if key not in defaults and key not in values:
                return False

        # in case defaults are given we ensure taht either the value was
        # skipped or the value is the same as the default value.
        if defaults:
            for key, value in iteritems(defaults):
                if key in values and value != values[key]:
                    return False

        return True

    def match_compare_key(self):
        """The match compare key for sorting.

        Current implementation:

        1.  rules without any arguments come first for performance
            reasons only as we expect them to match faster and some
            common ones usually don't have any arguments (index pages etc.)
        2.  rules with more static parts come first so the second argument
            is the negative length of the number of the static weights.
        3.  we order by static weights, which is a combination of index
            and length
        4.  The more complex rules come first so the next argument is the
            negative length of the number of argument weights.
        5.  lastly we order by the actual argument weights.

        :internal:
        """
        return bool(self.arguments), -len(self._static_weights), self._static_weights,\
            -len(self._argument_weights), self._argument_weights

    def build_compare_key(self):
        """The build compare key for sorting.

        :internal:
        """
        return self.alias and 1 or 0, -len(self.arguments), \
            -len(self.defaults or ())

    def __eq__(self, other):
        return self.__class__ is other.__class__ and \
            self._trace == other._trace

    __hash__ = None

    def __ne__(self, other):
        return not self.__eq__(other)

    def __str__(self):
        return self.rule

    @native_string_result
    def __repr__(self):
        if self.map is None:
            return u'<%s (unbound)>' % self.__class__.__name__
        tmp = []
        for is_dynamic, data in self._trace:
            if is_dynamic:
                tmp.append(u'<%s>' % data)
            else:
                tmp.append(data)
        return u'<%s %s%s -> %s>' % (
            self.__class__.__name__,
            repr((u''.join(tmp)).lstrip(u'|')).lstrip(u'u'),
            self.methods is not None
            and u' (%s)' % u', '.join(self.methods)
            or u'',
            self.endpoint
        )
Rule类

 

- 视图中的FBV(function base views):

  - 这里的FBV的用法和Django基本一样,直接写函数里面的逻辑就行了,不过需要注意的一点是,因为Flask的前后文管理,这里不需要每个函数都接收一个request;

  - 使用request,只需要从Flask中导入即可,

  - render,redirect 也只需从Flask导入即可,只不过,Flask中render叫做 render_template;

 

- 视图中的CBV(class base views):

  - 自定义一个继承 views.MethodView 的类。

  - 绑定url: app.add_url_rule("url", None, Cls.as_view() )

  - 限制接受的请求:通过设置静态字段method 就可以限制 例如:methods = ["GET", "POST"]

  - 增加装饰器:通过设置静态字段 decorators = [装饰器的函数名字]; 这样会自动在执行视图的时候,提前执行装饰器。

  - 示例解析:

import functools
from flask import Flask,views
app = Flask(__name__)


def wrapper(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        return func(*args,**kwargs)

    return inner



class UserView(views.MethodView):
    methods = ['GET'"POST"]
    decorators = [wrapper,]      # 这里会让每次请求进来后都去执行装饰器,不论是什么请求

    def get(self,*args,**kwargs):
        return 'GET'

    def post(self,*args,**kwargs):
        return 'POST'

app.add_url_rule('/user',None,UserView.as_view('uuuu'))

if __name__ == '__main__':
    app.run()
CBV+装饰器,示例

 

反向解析

 在app.route中的参数,endpoint 是用来给路由起别名的,

在函数中,如果想用到这个别名,需要用 url_for() 来反向解析。

 

- 当url中有动态传参的时候,在用url_for 的时候,需要先将参数传入

- 示例:

from flask import Flask, url_for, redirect

app = Flask(__name__)
app.config["DEBUG"] = True

@app.route("/index/<int:id>", endpoint="index")
def index(id):
    print(id, type(id))
    url_for("index", id=999)
    return redirect(url_for("index", id=999))

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

 

视图中特殊的装饰器

 

类似Django中的中间件:

before_request:
  - 被该装饰器装饰,会在执行视图函数之前执行
  - 当有多个被该装饰器装饰时, 按照位置 谁在前,谁先执行
  - before_first_request: 该装饰器不常用,等于上面的装饰器, 只不过会第一个走他。

- 示例:

from flask import Flask
app = Flask(__name__)


@app.before_request
def x1():
    print('before:x1')
    return ''

@app.before_request
def xx1():
    print('before:xx1')


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


@app.route('/order')
def order():
    print('order')
    return "order"


if __name__ == '__main__':

    app.run()

 

after_request:
  - 被该装饰器装饰的时候 需要接收response 并且返回response, 且在执行视图函数之后执行。
  - 当有多个被该装饰器装饰时, 按照位置 谁在前,谁后执行
  - errorhandler (定制错误页面):该装饰器是在报404错误时返回response 会走被这个装饰器装饰的函数。

- 示例:

from flask import Flask
app = Flask(__name__)


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

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


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


@app.route('/order')
def order():
    print('order')
    return "order"


if __name__ == '__main__':

    app.run()

 

 

模板HTML用到的两个装饰器:

template_global:

  - 在模板中用到

  - 定义全局函数,定义完此函数后,在任何的模板中都可以调用被此装饰器装饰的函数

- 示例:

@app.template_global()
def sb(a1, a2):
    # {{sb(1,9)}}
    return a1 + a2


# 模板中的用法: {{sb(1,9)}}
# 实际页面为: 10

 

template_filter:

  - 在模板中用到

  - 也是全局函数, 不过此函数在调用的时候,需要用到管道符 |  

  - 示例:

"""
py文件
"""

@app.template_filter()
def db(a1, a2, a3):
    # {{ 1|db(2,3) }}
    return a1 + a2 + a3

"""
HTML
"""

<p>{{1|db(2,3)}}</p>


"""
实际页面:
6
"""

 

posted @ 2018-10-20 16:19  浮生凉年  阅读(369)  评论(0编辑  收藏  举报