Flask - 路由系统

目录

一、基本使用

1-1 默认转换器

二、add_url_rule - 装饰器本质分析及使用

2-1 route装饰器 源码分析

2-1-1 add_url_rule使用效果测试

2-2 基于add_url_rule的CBV使用方式

2-2-1 方式一、继承view.View类

2-2-2 方式二、继承 views.MethodView 类 - 常用方式

2-3  app.add_url_rule参数

 三、 路由的正则支持 - 基于BaseConverter实现正则规则


一、基本使用

@app.route('/detail/<int:nid>',methods=['GET'],endpoint='detail')

1-1 默认转换器

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

二、add_url_rule - 装饰器本质分析及使用

2-1 route装饰器 源码分析

route的装饰器本质是 add_url_rule 函数的使用

@app.route("/login", methods=['GET',"POST"],endpoint='loginname') 
def login():
    return '登录'

# 效果同上装饰器的使用
app.add_url_rule('/login', 'loginname', login, methods=['GET',"POST"])
'''
def add_url_rule(self, rule, endpoint=None, view_func=None,
                     provide_automatic_options=None, **options):
'''

2-1-1 add_url_rule使用效果测试

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

app = Flask(__name__)
app.debug = True
app.secret_key = 'test'
# 模拟数据库内信息
USER_LIST = {
    1: {'name': '张三', 'age': 18, 'gender': '男', 'text': "道路千万条"},
    2: {'name': '李四', 'age': 28, 'gender': '男', 'text': "安全第一条"},
    3: {'name': '王五', 'age': 18, 'gender': '女', 'text': "行车不规范"},
}

from functools import wraps


def login_decorator(func, ):
    # @wraps(func)
    def wrapper(*args, **kwargs):
        if 'user' in session:
            res = func(*args, **kwargs)
            return res
        return redirect('/login')

    return wrapper


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


# @app.route('/login', methods=['GET', 'POST'],endpoint=loginname)
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        # 取出表单提交的name数据
        name = request.form.get('name')
        pwd = request.form.get('pwd')
        if name == 'test' and pwd == '123':
            # 登录成功 写session
            session['user'] = name
            return 'login success'
        else:
            # django中传字典,这里传**字典
            # return render_template('login.html',**{'error':'用户名或密码错误'})
            return render_template('login.html', error='用户名或密码错误')


app.add_url_rule('/login', 'loginname', login, methods=['GET', 'POST'])

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

错误信息分析:单独使用add_url_rule时,必须手动指定endpoint参数

错误信息分析:单独使用add_url_rule时,必须手动基于函数内存地址

2-2 基于add_url_rule的CBV使用方式

注意!!基于flask库下的views,切记导入

2-2-1 方式一、继承view.View类

使用思路总结

  1. 创建继承 views.View 的类
  2. 必须在该类内重写 dispatch_request 方法
  3. 类外app.add_url_rule('/路由', view_func=类名.as_view(name='别名,即同endpoint'))

View基类总结

  • 类内
    • methods = ['GET', 'POST'] - 指定该类视图支持的请求类别
    • decorators = [login_auth, ] - 指定该类视图外包裹的装饰器,注意!前置名作为里层包裹。

实例分析

from flask import Flask, views

app = Flask(__name__)
app.debug = True

# 装饰器,用于类内decorators,包裹类视图
def auth(func):
    def inner(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result

    return inner

class IndexView(views.View):
    methods = ['GET']
    decorators = [auth, ]

    def dispatch_request(self):
        print('Index')
        return 'Index!'

app.add_url_rule('/index', view_func=IndexView.as_view(name='index'))  # name=endpoint

源码分析

class View(object):
    """Alternative way to use view functions.  A subclass has to implement
    :meth:`dispatch_request` which is called with the view arguments from
    the URL routing system.  If :attr:`methods` is provided the methods
    do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
    使用视图函数的另一种方法。
    子类必须实现:meth: ' dispatch_request ',它使用来自URL路由系统的视图参数调用。
    如果提供:attr: 'methods',则不必传递给:meth:' ~flask.Flask.add_url_rule '

    method explicitly::
    明确的使用方法

        class MyView(View):
            methods = ['GET']

            def dispatch_request(self, name):
                return 'Hello %s!' % name

        app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
    
    When you want to decorate a pluggable view you will have to either do that
    when the view function is created (by wrapping the return value of
    :meth:`as_view`) or you can use the :attr:`decorators` attribute::
    当你想装饰一个可插入视图时,你必须在视图函数创建时(通过包装返回值:meth: ' as_view ')
    或者你可以使用:attr: ' decorators '属性::

        class SecretView(View):
            methods = ['GET']
            decorators = [superuser_required]

            def dispatch_request(self):
                ...

    The decorators stored in the decorators list are applied one after another
    when the view function is created.  Note that you can *not* use the class
    based decorators since those would decorate the view class and not the
    generated view function!
    在创建视图函数时,存储在装饰器列表中的装饰器将一个接一个地应用。
    注意,您不能使用基于类的装饰器,因为它们将装饰视图类而不是生成的视图函数!
    """

    #: A list of methods this view can handle.
    methods = None

    #: Setting this disables or force-enables the automatic options handling.
    provide_automatic_options = None

    #: The canonical way to decorate class-based views is to decorate the
    #: return value of as_view().  However since this moves parts of the
    #: logic from the class declaration to the place where it's hooked
    #: into the routing system.
    #:
    #: You can place one or more decorators in this list and whenever the
    #: view function is created the result is automatically decorated.
    #:
    #: .. versionadded:: 0.8
    decorators = ()

    def dispatch_request(self):
        """Subclasses have to override this method to implement the
        actual view function code.  This method is called with all
        the arguments from the URL rule.
        子类必须重写此方法以实现实际的视图函数代码。
        使用URL规则中的所有参数调用此方法。
        """
        raise NotImplementedError()

    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        """Converts the class into an actual view function that can be used
        with the routing system.  Internally this generates a function on the
        fly which will instantiate the :class:`View` on each request and call
        the :meth:`dispatch_request` method on it.

        The arguments passed to :meth:`as_view` are forwarded to the
        constructor of the class.
        """
        def view(*args, **kwargs):
            self = view.view_class(*class_args, **class_kwargs)
            return self.dispatch_request(*args, **kwargs)

        if cls.decorators:
            view.__name__ = name
            view.__module__ = cls.__module__
            for decorator in cls.decorators:
                view = decorator(view)

        # We attach the view class to the view function for two reasons:
        # first of all it allows us to easily figure out what class-based
        # view this thing came from, secondly it's also used for instantiating
        # the view class so you can actually replace it with something else
        # for testing purposes and debugging.
        view.view_class = cls
        view.__name__ = name
        view.__doc__ = cls.__doc__
        view.__module__ = cls.__module__
        view.methods = cls.methods
        view.provide_automatic_options = cls.provide_automatic_options
        return view

2-2-2 方式二、继承 views.MethodView 类 - 常用方式

使用思路总结

  1. 创建继承 views.MethodView 的类 (该类继承于View类)
  2. 必须在该类内重写 get 和 post方法
  3. 类外app.add_url_rule('/路由', view_func=类名.as_view(name='别名,即同endpoint'))

View基类总结

  • 类内
    • methods = ['GET', 'POST'] - 指定该类视图支持的请求类别
    • decorators = [login_auth, ] - 指定该类视图外包裹的装饰器,注意!前置名作为里层包裹。

实例分析

from flask import Flask, views

app = Flask(__name__)
app.debug = True

# 装饰器,用于类内decorators,包裹类视图
def auth(func):
    def inner(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result

    return inner

class IndexView(views.MethodView):
    methods = ['GET']
    decorators = [auth, ]

    def get(self):
        return 'Index.GET'

    def post(self):
        return 'Index.POST'

app.add_url_rule('/index', view_func=IndexView.as_view(name='index'))  # name=endpoint

源码分析

class MethodView(with_metaclass(MethodViewType, View)):
    """A class-based view that dispatches request methods to the corresponding
    class methods. For example, if you implement a ``get`` method, it will be
    used to handle ``GET`` requests. ::

        class CounterAPI(MethodView):
            def get(self):
                return session.get('counter', 0)

            def post(self):
                session['counter'] = session.get('counter', 0) + 1
                return 'OK'

        app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
    """

    def dispatch_request(self, *args, **kwargs):
        meth = getattr(self, request.method.lower(), None)

        # If the request method is HEAD and we don't have a handler for it
        # retry with GET.
        if meth is None and request.method == 'HEAD':
            meth = getattr(self, 'get', None)

        assert meth is not None, 'Unimplemented method %r' % request.method
        return meth(*args, **kwargs)

2-3  app.add_url_rule参数

  • rule - URL规则
  • view_func - 视图函数名称
  • defaults = None - 默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}
    为函数提供参数
  • endpoint = None -  名称,用于反向生成URL,即: url_for('名称')
  • methods = None - 允许的请求方式,如:["GET", "POST"]
  • strict_slashes = None - 对URL最后的 / 符号是否严格要求
    @app.route('/index', strict_slashes=False)
    # 访问 http://www.xx.com/index/ 或 http://www.xx.com/index 均可
            
    @app.route('/index', strict_slashes=True)
    # 仅访问 http://www.xx.com/index
  • redirect_to = None -  重定向到指定地址
    • @app.route('/index/<int:nid>', redirect_to='/home/<nid>')
  • subdomain = None - 子域名访问
    # 修改主机内host文件,确保域名能够DNS解析
    # C:\Windows\System32\drivers\etc\hosts
        127.0.0.1       www.test.com
    	127.0.0.1       admin.test.com
    	127.0.0.1       buy.test.com
        
        from flask import Flask, views, url_for
    
        app = Flask(import_name=__name__)
        app.config['SERVER_NAME'] = 'test.com:5000'
    
        @app.route("/", subdomain="admin")
        def static_index():
            """Flask supports static subdomains
            This is available at static.your-domain.tld"""
            return "static.your-domain.tld"
    
        # 可以传入任意的字符串,如传入的字符串为aa,显示为 aa.test.com
        @app.route("/dynamic", subdomain="<username>")
        def username_index(username):
            """Dynamic subdomains are also supported
            Try going to user1.your-domain.tld/dynamic"""
            return username + ".your-domain.tld"
    
        if __name__ == '__main__':
            app.run()
            
        访问方式:
        http://www.test.com:5000/dynamic
        http://admin.test.com:5000/dynamic
        http://buy.test.com:5000/dynamic

 三、 路由的正则支持 - 基于BaseConverter实现正则规则

思路总结

  1. 创建继承 BaseConverter 的类
  2. 注册:app.url_map.converters['regex'] = RegexConverter
  3. 使用:@app.route('/index/<regex("\d+"):nid>')  正则表达式会当作第二个参数传递到类中

实例分析

from flask import Flask, views, url_for
from werkzeug.routing import BaseConverter

app = Flask(import_name=__name__)

class RegexConverter(BaseConverter):
    """
    自定义URL匹配正则表达式
    """
    def __init__(self, map, regex):
        super(RegexConverter, self).__init__(map)
        self.regex = regex

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

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

# 添加到flask中
app.url_map.converters['regex'] = RegexConverter

@app.route('/index/<regex("\d+"):nid>')
def index(nid):
    print(url_for('index', nid='888'))
    return 'Index'

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

源码分析

class BaseConverter(object):

    """Base class for all converters."""
    regex = '[^/]+'
    weight = 100

    def __init__(self, map):
        self.map = map

    def to_python(self, value):
        return value

    def to_url(self, value):
        return url_quote(value, charset=self.map.charset)

猜你喜欢

转载自blog.csdn.net/qq_33961117/article/details/87369835