Flask Admin-如何根据用户角色设置form_edit_rules或form_create_rules? [英] Flask Admin - how to set form_edit_rules or form_create_rules based on role of user?

查看:63
本文介绍了Flask Admin-如何根据用户角色设置form_edit_rules或form_create_rules?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am making simple ticketing system for medium-sized organization using Flask and sqlite together with SQLAlchemy. For backend managing of data I use Flask-Admin.

用户和票证"表如下所示:

The User and Ticket table looks like this:

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}')"

我能够根据在 is_accesible 方法中设置的User.role设置创建,编辑或删除故障单的权限.

I was able to set permission to create, edit or delete Tickets based on User.role set in 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类中时,它适用于所有人.我也尝试过这个:

But I´ve been struggling really hard to set form_edit_rules for specific user. For example I want to allow User with role == 2 to edit only two columns in Ticket. When I put form_edit_rules directly in ModelView Class it works but for everybody. I also tried this:

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

但没有成功.

任何人都可以将我推向正确的方向吗?有什么我想念的吗?是否有一些非常不好的做法?

Please can anyone push me right direction? Is there something I am missing? Is there some really bad practise used?

谢谢.

推荐答案

form_edit_rules 在调用 is_accessible 方法时已经被缓存.如果您更新规则,请刷新缓存:

form_edit_rules is already cached by the time method is_accessible is called. If you update the rules then refresh the cache:

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 ,而无需使规则缓存无效.我使用了这样的Q/A flask-admin:如何仅允许超级用户查看指定的表列?以下的依据.如果用户登录并具有'admin'角色,则他们可以查看并使用'active'字段.

It is also possible to set the form_edit_rules at runtime without resorting to invalidating the rules cache. I used this SO Q/A flask-admin: how to allow only super users can view the specified table column? as the basis for the following. If the user is logged in and is has role 'admin' they can see and use the '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示例.

Full single file Python 3 example below.

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': 'paul@example.net', 'first_name': 'Paul', 'last_name': 'Cunningham', 'password': hash_password('pa$$word'), 'role': _user_role},
        {'email': 'jane@example.net', '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 == 'paul@example.net').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 == 'jane@example.net').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数据库.

Run the following command to initialize an SQLite DB.

flask create-database --count 100

这篇关于Flask Admin-如何根据用户角色设置form_edit_rules或form_create_rules?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆