flask-admin: как разрешить только суперпользователям просматривать указанный столбец таблицы?

Я создал приложение с таблицей Project, которая хранится в sqlite, я хочу, чтобы только суперпользователи могли просматривать столбец одобрить, когда создание, редактирование данных.

Данные Project извлекаются в "class Project", и я добавил if current_user.has_role('superuser') в "class ProjectView":

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_security import SQLAlchemyUserDatastore, current_user,UserMixin,        
     RoleMixin
from flask_admin.contrib import sqla

 # Create Flask application
 app = Flask(__name__)
 app.config.from_pyfile('config.py')
 db = SQLAlchemy(app)

 # Define models
 roles_users = db.Table(
     'roles_users',
     db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
     db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
 )

 class Role(db.Model, RoleMixin):
     id = db.Column(db.Integer(), primary_key=True)
     name = db.Column(db.String(80), unique=True)
     description = db.Column(db.String(255))

     def __str__(self):
         return self.name

 class User(db.Model, UserMixin):
     id = db.Column(db.Integer, primary_key=True)
     email = db.Column(db.String(255), unique=True)
     password = db.Column(db.String(255))
     roles = db.relationship('Role', secondary=roles_users,
                        backref=db.backref('users', lazy='dynamic'))

     def __str__(self):
         return self.email

 class Project(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    team = db.Column(db.Unicode(64))
    project_name = db.Column(db.Unicode(128))
    approve = db.Column(db.Boolean())

 # Setup Flask-Security
 user_datastore = SQLAlchemyUserDatastore(db, User, Role)
 security = Security(app, user_datastore)


  # Create customized model view class
 class MyModelView(sqla.ModelView):

     def is_accessible(self):
        if not current_user.is_active or not current_user.is_authenticated:
            return False

        if current_user.has_role('superuser'):
            return True

        return False

     def _handle_view(self, name, **kwargs):
        if not self.is_accessible():
            if current_user.is_authenticated:
                # permission denied
               abort(403)
        else:
            # login
            return redirect(url_for('security.login', next=request.url))

 class ProjectView(sqla.ModelView):

    def is_accessible(self):
        if not current_user.is_active:
            return False
        else:
            return True

    def _handle_view(self, name, **kwargs):
        if not self.is_accessible():
            if current_user.is_authenticated:
                # permission denied
               abort(403)
            else:
               if current_user.has_role('superuser'):
                 form_create_rules = [
                 rules.FieldSet(('team'), 'Personal Info'),
                 rules.Header('Project Info'),
                 rules.Field('project_name'),
                 'approve',
                 rules.Container('rule_demo.wrap', rules.Field('notes'))
                 ]

             else: 
                 form_create_rules = [
                 rules.FieldSet(('team'), 'Personal Info'),
                 rules.Header('Project Info'),
                 rules.Field('project_name'),
                 #'approve',
                 rules.Container('rule_demo.wrap', rules.Field('notes'))
                 ]
             form_edit_rules = form_create_rules

             create_template = 'rule_create.html'
             edit_template = 'rule_edit.html'

    @app.route('/')
    def index():
      return render_template('index.html')

    # Create admin
    admin = flask_admin.Admin(
          app,
          'Release Control System',
          # log in success page
          base_template='my_master.html',   
          template_mode='bootstrap3',
        )

   # Add model views
   admin.add_view(MyModelView(Role, db.session))
   admin.add_view(MyModelView(User, db.session))
   admin.add_view(ProjectView(Project, db.session))

Но это по-прежнему не работает, и все пользователи по-прежнему могут просматривать столбец одобрить. Добрый совет. Заранее спасибо.


person Samoth    schedule 01.03.2017    source источник


Ответы (4)


Вы можете использовать BaseModelView.column_list. чтобы указать динамически вычисляемый список доступных столбцов, просто сделайте его свойством. Однако различные атрибуты «поля» ModelView кэшируются при запуске приложения, поэтому вам необходимо переопределить их кэши:

from flask import has_app_context

class ProjectView(sqla.ModelView):
    @property
    def _list_columns(self):
        return self.get_list_columns()

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

    @property
    def column_list(self):
        if not has_app_context() or current_user.has_role('superuser'):
            return ['team', 'project_name', 'approve']
        else:
            return ['team', 'project_name']

Атрибут column_list используется во время инициализации приложения, когда current_user недоступен. Используйте метод flask.has_app_context(), чтобы проверить это состояние и передать приложение полный список столбцов при запуске.

Если вам нужно указать другой набор столбцов для редактирования, вам нужны атрибуты form_rules (вы уже использовали их в своем вопросе):

from flask_admin.form import rules

class ProjectView(sqla.ModelView):
    @property
    def _form_edit_rules(self):
        return rules.RuleSet(self, self.form_rules)

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

    @property
    def _form_create_rules(self):
        return rules.RuleSet(self, self.form_rules)

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

    @property
    def form_rules(self):
        form_rules = [
            rules.FieldSet(('team',), 'Personal Info'),
            rules.Header('Project Info'),
            rules.Field('project_name')
        ]
        if not has_app_context() or current_user.has_role('superuser'):
            form_rules.append('approve')
        form_rules.append(rules.Container('rule_demo.wrap', rules.Field('notes')))
        return form_rules

Также вам не нужно использовать _handle_view для перенаправления пользователя на страницу входа. Для этого BaseView.inaccessible_callback используется метод:

def inaccessible_callback(self, name, **kwargs):
    if current_user.is_authenticated:
        abort(403)
    else:
        return redirect(url_for('security.login', next=request.url))
person Sergey Shubin    schedule 01.03.2017
comment
Спасибо. Но он показывает AttributeError: 'NoneType' object has no attribute 'has_role' после проверки кода. И если я комментирую @property, то показывает TypeError: 'instancemethod' object is not iterable. - person Samoth; 02.03.2017
comment
@Samoth Я забыл, что приложение использует column_list несколько раз во время инициализации, когда current_user недоступен. Попробуйте использовать метод flask.has_app_context(), чтобы предоставить приложению полный список столбцов при запуске. Я обновил пример выше. - person Sergey Shubin; 02.03.2017
comment
спасибо, но это все еще не работает, и пользователь все еще может видеть столбец одобрить. Я разместил код class ProjectView ниже. Спасибо . - person Samoth; 02.03.2017
comment
@Samoth Спасибо за попытки! Предпринята еще одна попытка переопределить кешированные атрибуты. - person Sergey Shubin; 02.03.2017
comment
Спасибо, и на этот раз произошла ошибка: TypeError: _list_columns() takes exactly 1 argument (2 given). Я обновил код ниже. - person Samoth; 02.03.2017
comment
@Samoth Опечатка в определении _list_columns setter. Измените его на def _list_columns(self, value): - person Sergey Shubin; 02.03.2017
comment
Спасибо, на этот раз сработало как чудо! Спасибо еще раз ! - person Samoth; 02.03.2017
comment
@Самот Отлично! Я отредактирую свой ответ, чтобы он был более удобным для других пользователей. - person Sergey Shubin; 02.03.2017
comment
Я обнаружил что-то действительно странное, теперь утвердить не отображается в списке, но все еще отображается на странице создать и редактировать, я разместил скриншоты в ответе ниже. - person Samoth; 02.03.2017
comment
@Samoth Нашел решение: form_rules атрибуты, которые вы уже использовали. Обновил ответ. Спасибо за обновления! - person Sergey Shubin; 02.03.2017
comment
@Сергей Шубин, это мне надо сказать спасибо! После обновления возникает ошибка: TypeError: 'NoneType' object is not iterable, я думаю, что это может быть @property обычно вызывает ошибку. - person Samoth; 03.03.2017
comment
@Samoth О, я забыл вернуть значение из form_rules, теперь это сработает. - person Sergey Shubin; 03.03.2017
comment
@ Сергей Шубин, спасибо, но теперь при нажатии создать и редактировать отображается ValueError: Form <flask_admin.contrib.sqla.form.ProjectForm object at 0x0000000005F27898> does not have field t. Я опубликую код и фотографии в другом ответе. - person Samoth; 03.03.2017

 class ProjectView(sqla.ModelView):
    '''
    def inaccessible_callback(self, name, **kwargs):
       if current_user.is_authenticated:
          abort(403)
       else:
          return redirect(url_for('security.login', next=request.url))
    '''
    def is_accessible(self):
        if not current_user.is_active or not current_user.is_authenticated:
            return False
        else:
            return True

    @property
    def _list_columns(self):
        return self.get_list_columns()

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

    @property
    def column_list(self):
       if not has_app_context() or current_user.has_role('superuser'):
          return ['team', 'project_name', 'approve']
       else:
          return ['team', 'project_name']

    form_edit_rules = column_list
    create_template = 'rule_create.html'
    edit_template = 'rule_edit.html'

введите здесь описание изображения

введите здесь описание изображения

введите здесь описание изображения

person Samoth    schedule 02.03.2017

class ProjectView(sqla.ModelView):

  def inaccessible_callback(self, name, **kwargs):
     if current_user.is_authenticated:
        abort(403)
     else:
        return redirect(url_for('security.login', next=request.url))

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

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

  @property
  def _form_create_rules(self):
      return rules.RuleSet(self, self.form_rules)

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

  @property
  def form_rules(self):
      form_rules = [
          rules.FieldSet(('team'), 'Personal Info'),
          rules.Header('Project Info'),
          rules.Field('project_name')
      ]
      if not has_app_context() or current_user.has_role('superuser'):
          form_rules.append('approve')
      form_rules.append(rules.Container('rule_demo.wrap', 
          rules.Field('notes')))
      return form_rules

введите здесь описание изображения

person Samoth    schedule 03.03.2017
comment
Это уже другой вопрос: заменить ('team') на ('team',) :) Первый аргумент FieldSet должен быть кортежем или списком, а ('team') обрабатывается как строка. Посмотрите на этот вопрос: Почему кортежи только с один элемент преобразуется в строки? - person Sergey Shubin; 03.03.2017
comment
Да, теперь это работает как шарм, я не знаю, как выразить свою признательность! Хотя rules.Container('rule_demo.wrap', rules.Field('notes')) не работает. Самая важная часть — это одобрить , которую могут видеть и редактировать только суперпользователи, еще раз спасибо. - person Samoth; 03.03.2017
comment
И тебе спасибо! Ответ теперь выглядит более полезным для других со всеми обновлениями. - person Sergey Shubin; 03.03.2017

Я хотел бы объединить FileAdmin (пример здесь: https://github.com/flask-admin/flask-admin/tree/master/examples/file).

Это означает, что только после утверждения проекта (см. рис.) пользователи могут загрузить файл в автоматически указанный системой путь (скажем: /Reviewer1/Reviewer2/file)

введите здесь описание изображения

person Samoth    schedule 11.03.2017