在哪里放置公共视图代码以避免重复?

2024-09-30 12:22:06 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一系列view callable,它们都需要执行一系列执行验证的函数,如果其中一个验证失败,它将返回view callable的输出。如果所有这些验证都通过了,那么视图callable中的其余逻辑应该执行并最终生成适当的输出。在伪代码中如下所示:

@view_config(...)
def my_view(request):
   # perform a validation and return an error dictionary if there was a problem
   o = validate_thingy_a(request)
   if o: return o

   # perform a validation and return an error dictionary if there was a problem
   o = validate_thingy_b(request)
   if o: return o

   # validations were all good, go on to produce sunny day output
   return { "result" : "normal view results"}

所以,虽然这不是像一个优雅的,喜欢,它的工作。但是我的问题是:如果你有一系列相关的视图调用,它们都需要预先进行相同的验证,那么有没有一个好的方法对它们进行编码,这样它们就不必列出那些相同的验证块?你知道吗

我考虑过decorators,但问题是如果我创建了多个decorator(每个验证一个),那么我需要做的是,如果给定的验证器decorator失败,它应该代表视图callable发出一个错误字典,其他decorator就不应该运行。但我不认为你可以连接装饰器来轻松跳过应用于函数的“剩余装饰器”。你知道吗

然后,我继续考虑在课堂上这样做,就像这样,也许:

class ApiStandard(object):
   def __init__(self, request)
     self.request = request

     # would like to do validations here that precede all view callables below
     # but can't figure out how to "return" output for the callable if a
     # validation fails - then we don't want the view callable to be called.

   @view_config(route=...)
   def view_callable1(self):
      ...
   @view_config(route=...)
   def view_callable2(self):
      ...

但是我不认为这是可行的,因为我不认为init可以代表一个视图发出一个结果,并导致视图不被调用。你知道吗

类的最后一种安排是向类添加validate方法,并让每个视图都可以调用它。这比在每个可调用视图中放置所有单独的检查稍微好一点,但不是很多,在添加另一个可调用视图时,您仍然需要记住调用此方法。你知道吗

class ApiStandard(object):
   def __init__(self, request)
     self.request = request

   def common_validations():
      # perform common validations and return an error dict if there was a problem

   @view_config(route=...)
   def view_callable1(self):
      o = common_validations()
      if o: return o
      ...
enter code here
   @view_config(route=...)
   def view_callable2(self):
      o = common_validations()
      if o: return o
      ...

我觉得上面的解决方案都不是很优雅。有没有一个好的方法来处理相关视图可调用项的公共代码位??你知道吗


Tags: to方法selfview视图configreturnif
2条回答

您可以说,在view函数之外提取代码是非常好的。你知道吗

就我个人而言,我非常喜欢檐口为restapi所做的工作:https://cornice.readthedocs.io/en/latest/validation.html

例如,从上面的链接:

from cornice import Service

foo = Service(name='foo', path='/foo')


def has_paid(request, **kwargs):
    if not 'X-Verified' in request.headers:
        request.errors.add('header', 'X-Verified', 'You need to provide a token')

@foo.get(validators=has_paid)
def get_value(request):
    """Returns the value.
    """
    return 'Hello'

我在一个应用程序上工作,其中既有檐口端点,也有用@view_config声明的常规视图,因此我编写了一个decorator,可以用来包装Colander模式类(https://docs.pylonsproject.org/projects/colander/en/latest/)。你知道吗

装饰师看起来像这样:

from collections import defaultdict
from pyramid.renderers import render_to_response
import colander
from translationstring import TranslationString


class PreValidator:
    def __init__(self, schema_class, renderer, on_invalid=None, extra_vars=None):
        self.schema_class = schema_class
        self.renderer = renderer
        self.on_invalid = on_invalid
        self.extra_vars = extra_vars

    def __call__(self, wrapped):
        def wrapper(context, request):
            schema = self.schema_class().bind(request=request)
            try:
                values = schema.deserialize(request.POST.mixed())
                request.validated = values
                return wrapped(context, request)
            except colander.Invalid as e:
                if hasattr(self.on_invalid, '__call__'):
                    self.on_invalid(request)
                errors = dict([(c.node.name, c.messages()) for c in e.children])
                general_errors = e.messages()
                if len(general_errors) > 0:
                    errors['_general'] = general_errors
                for _, msgs in errors.items():
                    for i, msg in enumerate(msgs):
                        if type(msg) == TranslationString:
                            msgs[i] = request.localizer.translate(msg)
                for_renderer = dict(
                    values=defaultdict(lambda: '', request.POST.items()),
                    errors=errors,
                )
                if hasattr(self.extra_vars, '__call__'):
                    self.extra_vars(request, for_renderer)
                return render_to_response(
                    self.renderer, for_renderer, request, response=request.response)

        return wrapper

它的用法类似于:

from colander import (
    Schema,
    SchemaNode,
    String,
    Invalid,
    deferred,
)


class LoginSchema(Schema):
    # here you would do more complex validation
    email = SchemaNode(String())
    password = SchemaNode(String())


def extra_vars(request, templ_vars):
    # in case you need to set again some value
    pass


@view_config(
    route_name='login',
    request_method='POST',
    renderer='/website/login.jinja2',
    decorator=PreValidator(
        LoginSchema,
        '/website/login.jinja2',
        on_invalid=lambda r: r.POST.pop('password', None),
        extra_vars=extra_vars),
)
def login_post(request):
    # here you know the request is valid
    do_something(request.validated['email'], request.validated['password'])
    return HTTPFound('/')

我不是说你应该按原样使用它,但我想看看别人是怎么做的会有所帮助。你知道吗

另外,请确保使用工厂/上下文(即使不使用遍历路由)和金字塔ACL,以确保从视图函数中提取尽可能多的内容。你知道吗

也许我遗漏了一些东西,但是如果您将视图作为类并使用继承和混合,那么这些看起来都非常简单。您还可以创建可调用的ViewClassFactory,从params返回视图类。除此之外,有时最好将一些视图代码从视图中取出,并将其推入该视图的RootFactory中。你可以制造制造根工厂的工厂。如果您还没有尝试过将协作视图作为类和根工厂类相结合,我建议您使用它。金字塔有很多代码重用的选项。你知道吗

相关问题 更多 >

    热门问题