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

前端 未结 1 1529
囚心锁ツ
囚心锁ツ 2021-01-28 07:57

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 Use

1条回答
  •  执笔经年
    2021-01-28 08:45

    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 = [
            'Click me to get to impersonate Paul (user)!',
            'Click me to get to impersonate Jane (admin)!'
        ]
        return '
    '.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 'Click me to get to Admin logged in as Paul (user)!' @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 'Click me to get to Admin logged in as Jane (admin)!' 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
    

    0 讨论(0)
提交回复
热议问题