Flask Admin - 如何根据用户角色设置 form_edit_rules 或 form_create_rules?

2023-12-05

我正在使用 Flask 和 sqlite 以及 SQLAlchemy 为中型组织制作简单的票务系统。对于数据的后端管理,我使用 Flask-Admin。

用户和票证表如下所示:

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    role = db.Column(db.Integer, default=0)
    vmc_kom = db.Column(db.String(20))
    name = db.Column(db.String(30), nullable=False)
    phone = db.Column(db.String, default="not")
    email = db.Column(db.String(40), nullable=False)
    password = db.Column(db.String(60), nullable=False)
    tickets = db.relationship('Ticket', cascade="all,delete", backref='author', lazy=True)

    def __repr__(self):
        return f"('{self.name}')"

class Ticket(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key = True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    povod_vmc_kom = db.Column(db.String(20))
    osoba = db.Column(db.String(20), default="XYZ")
    dateVMC = db.Column(db.Date, nullable=False)
    deadline = db.Column(db.Date, nullable=False)
    is_finished = db.Column(db.Boolean, default = False)
    images = db.relationship('Image_ticket', cascade="all,delete", backref='home_ticket', lazy=True)
    solution = db.Column(db.Text)
    date_solution = db.Column(db.DateTime)
    zodpovedni = db.relationship("Zodpovedny", secondary="ticketutvary")
    sprava = db.Column(db.String(100))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"Ticket('{self.id}', '{self.title}', '{self.dateVMC}')"

我能够根据设置的 User.role 设置创建、编辑或删除票证的权限is_accesible method.

class TicketModelView(ModelView):
    column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'solution']
      def is_accessible(self):
        
        if current_user.is_authenticated and current_user.role == 0:
            self.can_export=True
            self.can_delete = False
            self.can_edit = False
            self.can_create = False
            return True
        
        if current_user.is_authenticated and current_user.role == 1:
            self.can_export=True
            self.can_delete=True
            return True
       
        if current_user.is_authenticated and current_user.role == 2:
            self.can_delete = False
            self.can_export=True
            return True
        
        if current_user.is_authenticated and current_user.role == 3:
            self.can_delete = False
            self.can_export=True

            return True
        return False

但我一直在努力设定form_edit_rules对于特定用户。例如,我想允许角色 == 2 的用户仅编辑工单中的两列。当我将 form_edit_rules 直接放在 ModelView 类中时,它可以工作,但对每个人来说都是如此。我也尝试过这个:

class TicketModelView(ModelView):
        column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'solution']
          def is_accessible(self):
            
            if current_user.is_authenticated and current_user.role == 2:
                self.can_export=True
                self.can_delete = False
                self.can_edit = False
                self.can_create = False
                self.form_edit_rules = ('zodpovedni','dateVMC')
                return True

但没有成功。

请问有人能推动我正确的方向吗?我有什么遗漏的吗?是否使用了一些非常糟糕的做法?

提前致谢。


form_edit_rules已经被 time 方法缓存了is_accessible叫做。如果更新规则则刷新缓存:

class TicketModelView(ModelView):

    column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'solution']

    def is_accessible(self):

        if current_user.is_authenticated and current_user.role == 2:
            self.can_export=True
            self.can_delete = False
            self.can_edit = False
            self.can_create = False
            self.form_edit_rules = ('zodpovedni','dateVMC')
            # Refresh form rules cache
            self._refresh_form_rules_cache()
            return True

        return False

也可以设置form_edit_rules在运行时无需使规则缓存失效。我用过这个SO Q/AFlask-admin:如何只允许超级用户可以查看指定表列?作为以下的基础。如果用户已登录并且具有角色'admin'他们可以看到并使用'active' field.

class AuthorView(sqla.ModelView):
    column_default_sort = ('last_name', False)
    column_searchable_list = ('first_name', 'last_name')

    @property
    def _form_edit_rules(self):
        return rules.RuleSet(self, self.form_edit_rules)

    @_form_edit_rules.setter
    def _form_edit_rules(self, value):
        pass

    @property
    def form_edit_rules(self):

        if not has_app_context() or current_user.has_role('admin'):
            return ('first_name', 'last_name', rules.Text(f'Authenticated User has Admin role'), 'active')

        return ('first_name', 'last_name', rules.Text('Not Authenticated and/or not Admin role'))

下面是完整的单文件 Python 3 示例。

requirements.txt

Babel==2.8.0
blinker==1.4
click==7.1.2
dnspython==2.0.0
email-validator==1.1.1
Faker==4.1.1
Flask==1.1.2
Flask-Admin==1.5.6
Flask-BabelEx==0.9.4
Flask-Login==0.5.0
Flask-Mail==0.9.1
Flask-Principal==0.4.0
Flask-Security==3.0.0
Flask-SQLAlchemy==2.4.4
Flask-WTF==0.14.3
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
passlib==1.7.2
python-dateutil==2.8.1
pytz==2020.1
six==1.15.0
speaklater==1.3
SQLAlchemy==1.3.18
text-unidecode==1.3
Werkzeug==1.0.1
WTForms==2.3.3

app.py

from datetime import datetime
from faker import Faker
import click
from flask import Flask, has_app_context, current_app
from flask_admin.form import rules
from flask_login import login_user, logout_user
from flask_security import UserMixin, RoleMixin, current_user, SQLAlchemyUserDatastore, Security
from flask_security.utils import hash_password
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib import sqla

db = SQLAlchemy()


user_to_role = db.Table('user_to_role',
    db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
    db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))


class User(db.Model, UserMixin):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)

    first_name = db.Column(db.Unicode(length=255), nullable=False)
    last_name = db.Column(db.Unicode(length=255), nullable=False, index=True)

    # Identification Data: email & password
    email = db.Column(db.Unicode(length=254), nullable=False, unique=True)
    password = db.Column(db.Unicode(length=255), nullable=False)
    active = db.Column(db.Boolean(), default=False)

    roles = db.relationship('Role', secondary=user_to_role, backref=db.backref('users', lazy='select'))


class Role(db.Model, RoleMixin):

    __tablename__ = 'roles'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(length=64), unique=True)
    description = db.Column(db.Unicode(length=255), nullable=True)

    def __str__(self):
        return self.name


class Author(db.Model):
    __tablename__ = 'authors'

    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.Text(length=255), nullable=False)
    last_name = db.Column(db.Text(length=255), nullable=False)
    active = db.Column(db.Boolean(), default=False)

    def __str__(self):
        return f"ID: {self.id}; First Name: {self.first_name}; Last Name: {self.last_name}"


app = Flask(__name__)

app.config['SECRET_KEY'] = '123456790'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample.sqlite'
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
app.config['SECURITY_PASSWORD_SALT'] = 'c1b4797ffb4783bb4aed7e14a1494a01390eacf94ee324b9'

db.init_app(app)
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)


@app.cli.command('create-database', short_help='Create sample database')
@click.option('--count', default=100, help='Number of authors (default 100)')
def create_database(count):

    """
        Create database
    """

    db.drop_all()
    db.create_all()
    _faker = Faker()

    security = current_app.extensions.get('security')
    _admin_role = security.datastore.find_or_create_role(name="admin", description='Administers the system')
    _user_role = security.datastore.find_or_create_role(name="user", description='Uses the system')

    users = [
        {'email': '[email protected]', 'first_name': 'Paul', 'last_name': 'Cunningham', 'password': hash_password('pa$$word'), 'role': _user_role},
        {'email': '[email protected]', 'first_name': 'Jane', 'last_name': 'Smith', 'password': hash_password('pa$$word'), 'role': _admin_role},
    ]

    for user in users:
        _role = user.pop('role')
        _user_db = security.datastore.create_user(**user)
        if _role:
            security.datastore.add_role_to_user(_user_db, _role)
            security.datastore.activate_user(_user_db)
            _user_db.confirmed_at = datetime.utcnow()

    security.datastore.commit()

    for _ in range(0, count):
        _author = Author(
            first_name=_faker.first_name(),
            last_name=_faker.last_name(),
            active=_faker.boolean()
        )
        db.session.add(_author)

    db.session.commit()


class AuthorView(sqla.ModelView):
    column_default_sort = ('last_name', False)
    column_searchable_list = ('first_name', 'last_name')

    @property
    def _form_edit_rules(self):
        return rules.RuleSet(self, self.form_edit_rules)

    @_form_edit_rules.setter
    def _form_edit_rules(self, value):
        pass

    @property
    def form_edit_rules(self):

        if not has_app_context() or current_user.has_role('admin'):
            return ('first_name', 'last_name', rules.Text(f'Authenticated User has Admin role'), 'active')

        return ('first_name', 'last_name', rules.Text('Not Authenticated and/or not Admin role'))


# Flask views
@app.route('/')
def index():
    _html = [
        '<a href="/impersonate-paul">Click me to get to impersonate Paul (user)!</a>',
        '<a href="/impersonate-jane">Click me to get to impersonate Jane (admin)!</a>'
    ]
    return '<br>'.join(_html)


@app.route('/impersonate-paul')
def impersonate_paul():
    _impersonate_user = User.query.filter(User.email == '[email protected]').first()
    logout_user()
    login_user(_impersonate_user)
    return '<a href="/admin/">Click me to get to Admin logged in as Paul (user)!</a>'


@app.route('/impersonate-jane')
def impersonate_jane():
    _impersonate_user = User.query.filter(User.email == '[email protected]').first()
    logout_user()
    login_user(_impersonate_user)
    return '<a href="/admin/">Click me to get to Admin logged in as Jane (admin)!</a>'


admin = Admin(app, template_mode="bootstrap3")
admin.add_view(AuthorView(Author, db.session))


if __name__ == '__main__':
    app.run()

运行以下命令来初始化 SQLite 数据库。

flask create-database --count 100
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flask Admin - 如何根据用户角色设置 form_edit_rules 或 form_create_rules? 的相关文章

随机推荐