【问题标题】:Best way to subclass Flask pluggable views for extensible functionality子类化 Flask 可插拔视图以实现可扩展功能的最佳方式
【发布时间】:2019-01-11 12:24:23
【问题描述】:

我正在构建一个 web 应用程序,其中不同的视图将具有不同数量的“包装功能”(例如身份验证、日志记录/错误处理、数据库访问等),并且能够轻松地在视图之间共享此功能。

我认为 Pluggable Views 是处理这个问题的好方法,它通过重复子类化视图来构建包装视图主要操作的功能层。

但是,我正在努力找出实现这一点的最佳方法。我正在考虑链接装饰器,但继承似乎效果不佳。

例如,带有一些自定义日志记录和错误处理的简化视图:

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)

视图可以像这样使用:

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

那么,如果我想在此视图的基础上添加用户身份验证:

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)

然后我想重复这个子类化来添加更多的功能,比如数据库访问

  • 有没有更好的方法来实现我想要做的事情?
  • 将装饰器作为视图方法有意义吗?在装饰器中使用“自我”有效吗?

任何建议将不胜感激!

【问题讨论】:

    标签: python inheritance flask views decorator


    【解决方案1】:

    decorators 是一个列表,一个可变结构。您不能只在子类中附加它。名称decorators 未在子类中定义,如果您附加到LoggedView.decorators,您将附加到错误的列表中!

    您必须在子类中创建一个 new 列表对象来掩盖基类上的属性;您可以通过连接到基类序列来构造一个;我在这里使用元组来使这一点更清楚:

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

    请注意,装饰器不是方法,它们在应用时不会绑定到视图,因此没有self 参数。

    如果您需要从装饰器访问视图实例,那么不要使用View.decorators,它们会装饰一个简单的函数,当调用该函数时会在调用该视图上的View.dispatch_request() 之前创建视图;当您调用View.as_view() 时返回的正是这个简单的函数。另一方面,如果您需要能够访问装饰器在注册路由时或(在另一个方向)在查找端点的注册视图时生成的包装器,那么使用View.decorators 是完全正确的。

    您可以直接装饰方法(包括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)
    

    这是 Flask-RESTFul 项目用来允许在一行中为视图上的所有方法指定装饰器的路径。

    然后从包装器调用参数中提取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
    

    我会在视图类外部定义装饰器函数。

    【讨论】:

    • 感谢您的回复。我想也许装饰器作为方法可以访问 self 由于闭包。我不确定该调度请求机制中发生了什么,看起来它以某种方式将类方法绑定到装饰器函数,但实际的装饰发生在哪里?我可以直接用包装层装饰 dispatch_request,或者使用flask global g 来访问我在装饰器函数中需要的东西吗?
    • @FinnAndersen:装饰器应用于View.as_view()返回的新函数;这样做的好处是您也可以通过 Flask API 访问装饰器以获取端点和路由。如果您不需要,那么将装饰器应用于dispatch_request() 就可以了。
    • as_view() 不是用dispatch_request() 来做路由功能的,所以要应用任何装饰器?
    • @FinnAndersen: 不,as_view() 创建一个新函数,在调用该函数时,会创建视图类的一个实例,并在该新实例上调用dispatch_requestdispatch_request 上的装饰器工作得很好,它们只是没有在 Flask 中注册为端点。有时你需要后者。
    • 好的,感谢您的澄清。将装饰器注册为端点或查找端点的注册视图会有什么好处或用例?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-29
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    相关资源
    最近更新 更多