简体   繁体   English

子类化Flask可插入视图以获得可扩展功能的最佳方法

[英]Best way to subclass Flask pluggable views for extensible functionality

I'm building a webapp where different views will have different amounts of 'wrapped functionality' (eg authentication, logging/error handling, database access etc), and be able to easily share this functionality between views. 我正在构建一个Webapp,其中不同的视图将具有不同数量的“包装功能”(例如,身份验证,日志记录/错误处理,数据库访问等),并能够在视图之间轻松共享此功能。

I'm thinking Pluggable Views would be good way to handle this, by repeatedly subclassing views to build up layers of functionality which wraps the main operation of the view. 我认为可插拔视图将是解决此问题的好方法,方法是重复子类化视图以构建功能层,这些功能层包装了视图的主要操作。

However I'm struggling to work out the best way to implement this. 但是,我正在努力找到实现此目标的最佳方法。 I'm thinking of chaining up decorators but the inheritance doesn't seem to work well. 我正在考虑将装饰器链接起来,但是继承似乎工作得不好。

For example, a simplified view with some custom logging and error handling: 例如,带有一些自定义日志记录和错误处理的简化视图:

from flask.views import View

class LoggedView(View):
    def __init__(self,template):
         self.template=template

    #Decorator method for error handling and logging
    def log_view(self,view):
        def decorator(**kwargs):
            try:
                #Set up custom logging
                self.log = .....
                #Execute view
                return view(**kwargs)
            except CustomApplicationError as e:
                #Log and display error
                self.log.error(e)
                return render_template('error.html',error=str(e))
         return decorator

    decorators=[log_view]

    #This can be overridden for more complex views
    def dispatch_request(self):
        return render_template(self.template)

View can be used like: 视图可以像这样使用:

app.add_url_rule('/index', view_func=LoggedView.as_view('index',template='index.html'))

Then if I wanted to build upon this view to also add user authentication: 然后,如果我想在此视图基础上添加用户身份验证:

class RestrictedView(LoggedView):

    #Decorator method for user access validation
    def validate_access(self,view):
        def decorator(**kwargs):
            g.user=session.get('user')
            if g.user is None:
                return redirect(url_for('login'))
            #Execute view
            return view(**kwargs)
         return decorator

    #How to add this functionality to the decorator chain? e.g. I dont think this works: 
    decorators.append(validate_access)

Then I want to repeat this subclassing to add further functionality such as database access 然后,我想重复此子类以添加更多功能,例如数据库访问

  • Is there a better way to achieve what I'm trying to do? 有没有更好的方法来实现我要完成的任务?
  • Does having the decorators as view methods make sense? 将装饰器作为查看方法有意义吗? Does using 'self' within the decorator work? 在装饰器中使用“自我”是否有效?

Any suggestions would be much appreciated! 我们欢迎所有的建议!

decorators is a list, a mutable structure. decorators是一个列表,一个可变的结构。 You can't just append to it in a subclass. 您不能只是将其追加到子类中。 The name decorators is not defined in a subclass, and if you'd append to LoggedView.decorators you'd be appending to the wrong list! 名称decorators未在子类中定义,如果将其附加到LoggedView.decorators则会将其附加到错误的列表中!

You'd have to create a new list object in a subclass to mask the attribute on the base class; 您必须在子类中创建一个新的列表对象,以掩盖基类上的属性。 you can contruct one by concatenating to the base class sequence; 您可以通过连接基类序列来构造一个; I used tuples here to make this clearer: 我在这里使用元组来使它更清晰:

class LoggedView(View):
    decorators = (log_view,)

class RestrictedView(LoggedView):
    decorators = LoggedView.decorators + (validate_access,)

Note that decorators are not methods , they are not bound to the view when they are applied, so there is no self argument. 请注意,装饰器不是方法 ,它们在应用时未绑定到视图,因此没有self变量。

If you need access to the view instance from a decorator, then don't use View.decorators , those decorate a simple function that when called creates the view before calling View.dispatch_request() on that view; 如果您需要从装饰器访问视图实例,则不要使用View.decorators ,它们会装饰一个简单的函数,该函数在被调用时会创建视图,然后再对该视图调用View.dispatch_request() it is this simple function that is returned when you call View.as_view() . 当您调用View.as_view()时,将返回此简单函数。 On the other hand, if you need to be able to access the wrapper a decorator produces when registering the route or (in the other direction) when looking up the registered view for an endpoint, then using View.decorators is exactly correct. 另一方面,如果您需要能够访问装饰器,则装饰器在注册路线时或(在另一个方向上)在查找已注册的端点视图时生成(在另一个方向上),则使用View.decorators完全正确。

You'd either decorate the methods directly (including dispatch_request() ) or implement your own mechanism in dispatch_request() : 您可以直接修饰方法(包括dispatch_request() ),也可以在dispatch_request()实现自己的机制:

import inspect

class LoggedView(View):
    method_decorators = (log_view,)

    #This can be overridden for more complex views
    def dispatch_request(self):
        # decorate methods
        cls = type(self)
        members = vars(type(self)).items()
        for name, object in members:
            if not inspect.isfunction(object):
                continue
            if name == 'dispatch_request':
                continue
            # add bound decorated functions to the view
            for d in self.method_decorators:
                setattr(self, name, d(object).__get__(self, cls))

        # dispatch
        return render_template(self.template)

This is the path that the Flask-RESTFul project uses to allow specifying decorators for all methods on a view with one line. 这是Flask-RESTFul项目用于允许为一行上的视图上的所有方法指定装饰器的路径。

Then pull the self argument from the wrapper call arguments (but do pass it on to the wrapped function): 然后从包装器调用参数中拉出self参数(但一定要将其传递给包装的函数):

def log_view(view):
    def decorator(self, **kwargs):
        try:
            #Set up custom logging
            self.log = .....
            #Execute view
            return view(self, **kwargs)
        except CustomApplicationError as e:
            #Log and display error
            self.log.error(e)
            return render_template('error.html',error=str(e))
     return decorator

I'd define the decorator functions themselves outside of the view classes. 我将在视图类外部定义装饰器函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM