Flask权限装饰器报错:current_user为NoneType,无can属性
运行Flask应用时触发以下错误:
AttributeError: 'NoneType' object has no attribute 'can'
错误出现在这行代码:
if not current_user.can(permission):
已在Models.py中定义User类、AnonymousUser类并配置login_manager,在Decorators.py中实现permission_required和admin_required装饰器,视图函数admin_hub同时使用@login_required与@admin_required装饰。移除权限装饰器后代码可正常运行,推测未登录时current_user不存在导致装饰器执行报错。
相关代码
Models.py
from datetime import datetime import hashlib from werkzeug.security import generate_password_hash, check_password_hash from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from markdown import markdown import bleach from flask import current_app, request, url_for from flask_login import UserMixin, AnonymousUserMixin from app.exceptions import ValidationError from . import db, login_manager class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(64), unique=True, index=True) username = db.Column(db.String(64), unique=True, index=True) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) password_hash = db.Column(db.String(128)) confirmed = db.Column(db.Boolean, default=False) name = db.Column(db.String(64)) location = db.Column(db.String(64)) about_me = db.Column(db.Text()) member_since = db.Column(db.DateTime(), default=datetime.utcnow) last_seen = db.Column(db.DateTime(), default=datetime.utcnow) avatar_hash = db.Column(db.String(32)) posts = db.relationship('Post', backref='author', lazy='dynamic') followed = db.relationship('Follow', foreign_keys=[Follow.follower_id], backref=db.backref('follower', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], backref=db.backref('followed', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') comments = db.relationship('Comment', backref='author', lazy='dynamic') def can(self, perm): return self.role is not None and self.role.has_permission(perm) def is_administrator(self): return self.can(Permission.ADMIN) class AnonymousUser(AnonymousUserMixin): def can(self, permissions): return False def is_administrator(self): return False login_manager.anonymous_user = AnonymousUser @login_manager.user_loader def load_user(user_id): return User.query.get(int(user_id))
Decorators.py
from functools import wraps from flask import abort from flask_login import current_user from .models import Permission def permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if not current_user.can(permission): abort(403) return f(*args, **kwargs) return decorated_function return decorator def admin_required(f): return permission_required(Permission.ADMIN)(f)
视图代码
@main.route('/admin_hub') @login_required @admin_required def admin_hub(): return render_template('admin_hub.html')
解决方案
1. 调整装饰器执行顺序
Python装饰器是从下往上应用的,当前代码中@login_required在@admin_required上方,导致权限检查先于登录检查执行。未登录时,权限装饰器先访问current_user,此时可能还未被Flask-Login初始化,从而出现NoneType错误。
修改视图装饰器顺序,让登录检查先执行:
@main.route('/admin_hub') @admin_required @login_required def admin_hub(): return render_template('admin_hub.html')
这样未登录用户会被@login_required直接重定向到登录页,不会触发权限检查逻辑。
2. 确保AnonymousUser正确生效
检查项目初始化逻辑,确保Models.py在应用启动时被导入(比如在__init__.py中导入from . import models),保证login_manager.anonymous_user = AnonymousUser代码被执行,让未登录用户自动被设置为AnonymousUser实例,而非None。
3. 给装饰器增加非空兜底判断
如果调整顺序后仍有问题,可以在权限装饰器中先判断current_user是否为空,避免报错:
def permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if current_user is None or not current_user.can(permission): abort(403) return f(*args, **kwargs) return decorated_function return decorator
内容的提问来源于stack exchange,提问作者Charlesu49

