Flask Admin - how to set form_edit_rules or form_create_rules based on role of user?

这一生的挚爱 提交于 2021-01-20 12:10:27

问题


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

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

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

But no success.

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

Thanks in advance.


回答1:


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

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

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()

Run the following command to initialize an SQLite DB.

flask create-database --count 100


来源:https://stackoverflow.com/questions/63337371/flask-admin-how-to-set-form-edit-rules-or-form-create-rules-based-on-role-of-u

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!