使用装饰器时,视图函数映射会覆盖现有端点函数

View function mapping is overwriting an existing endpoint function when using a decorator

我正在使用下面的装饰器来验证我的应用程序中的端点。

from google.appengine.api import users
from flask import redirect, render_template, request
from google.appengine.ext import ndb


def authenticate_admin(func):
    def authenticate_and_call(*args, **kwargs):
        user = users.get_current_user()
        if user is None:
            return redirect(users.create_login_url(request.url))
        else:
            email = user.email()
            register_user_if_required(email, user)
            if not users.is_current_user_admin():
                return redirect_to_unauthorized(email)
            return func(*args, **kwargs)

    def redirect_to_unauthorized(email):
        return render_template('xxxx/vvvv.html',
                               email=email,
                               users=users)

    return authenticate_and_call


def register_user_if_required(email, user):

我有以下端点,它只允许管理员访问它。

@admin_routes.route('/xxxx')
@authenticate_admin
def xxxxx():
    return render_template('xxxx/xxxxx.html',
                           user=user,
                           logout=users.create_logout_url('/'))

它的工作原理是只有管理员才能访问上述端点。但是,当我尝试添加具有相同注释但花式不同 url 的新端点时,出现错误。这是端点的代码。

@admin_routes.route('/xxxx/bbbbbb')
@authenticate_admin
def abc():
    .....
    return render_template('xxxx/xxxx/zzzzz.html',
                           user=user,
                           breadcrumb=breadcrumb)

这是我在 运行 我的应用程序时遇到的错误。

Traceback (most recent call last):
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 240, in Handle
    handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 299, in _LoadHandler
    handler, path, err = LoadObject(self._handler)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 85, in LoadObject
    obj = __import__(path[0])
  File "/Users/vinay/App-Engine/xxxxx/main.py", line 61, in <module>
    app.register_blueprint(admin_routes)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
    return f(self, *args, **kwargs)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 889, in register_blueprint
    blueprint.register(self, options, first_registration)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 153, in register
    deferred(state)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 172, in <lambda>
    s.add_url_rule(rule, endpoint, view_func, **options))
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 76, in add_url_rule
    view_func, defaults=defaults, **options)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
    return f(self, *args, **kwargs)
  File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 984, in add_url_rule
    'existing endpoint function: %s' % endpoint)
AssertionError: View function mapping is overwriting an existing endpoint function: admin_routes.authenticate_and_call
Traceback (most recent call last):
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 240, in emit
    record.exc_info)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2520, in RunInTransactionCustomRetries
    return RunInTransactionOptions(options, function, *args, **kwargs)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2630, in RunInTransactionOptions
    ok, result = _DoOneTry(function, args, kwargs)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2650, in _DoOneTry
    result = function(*args, **kwargs)
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 270, in __EmitTx
    handler=self.__RelativePath(os.environ['PATH_TRANSLATED']))
  File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/request_environment.py", line 113, in __getitem__
    return self._request.environ[key]
KeyError: 'PATH_TRANSLATED'
Logged from file wsgi.py, line 263
INFO     2015-08-09 03:19:14,731 module.py:812] default: "GET / HTTP/1.1" 500 -

您需要确保您的装饰器包装器与包装的视图函数同名,否则您的所有视图看起来都像同一个端点 (authenticate_and_call)。

您可以使用 @functool.wraps() utility:

from functools import wraps

def authenticate_admin(func):
    @wraps(func)
    def authenticate_and_call(*args, **kwargs):
        user = users.get_current_user()
        if user is None:
            return redirect(users.create_login_url(request.url))
        else:
            email = user.email()
            register_user_if_required(email, user)
            if not users.is_current_user_admin():
                return redirect_to_unauthorized(email)
            return func(*args, **kwargs)

    def redirect_to_unauthorized(email):
        return render_template('Admin/UnauthorizedAdmin.html',
                               email=email,
                               users=users)

    return authenticate_and_call

这确保元数据(例如函数名称)从 func 复制到 authenticate_and_call 包装器。从那以后 @Flask.route() 可以选择该名称用作端点名称。