flask-admin: Как сделать столбцы доступными только для чтения в соответствии со значением других столбцов?

Я создал систему, которая позволяет пользователям подавать заявку на проверку кода и ждать одобрения менеджера.

И теперь я хочу добиться следующего:

  1. Если он одобрен, введите здесь описание изображения, после чего все поля станут доступными только для чтения (я вручную установил Имя проекта здесь только для чтения):

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

  1. Если оно отклонено,

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

    тогда все поля станут редактируемыми. Разумеется, при создании нового проекта все поля должны быть доступны для редактирования.
    введите здесь описание изображения

    Код класса Project и ProjectView приведен ниже:

     from flask_sqlalchemy import SQLAlchemy
     from flask_admin.contrib import sqla
     from flask_security import current_user
    
     # Create Flask application
     app = Flask(__name__)
     app.config.from_pyfile('config.py')
     db = SQLAlchemy(app)
    
     class Project(db.Model):
    
            id = db.Column(db.Integer, primary_key=True)
            project_name = db.Column(db.Unicode(128))
            version = db.Column(db.Unicode(128))
            SVN = db.Column(db.UnicodeText)
            approve = db.Column(db.Boolean())
    
            def __unicode__(self):
                return self.name
    
     class ProjectView(sqla.ModelView):
         def is_accessible(self):
             if not current_user.is_active or not current_user.is_authenticated:
                 return False
             return False
    
         @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.Field('project_name'),
             rules.Field('version'),
             rules.Field('SVN'),
         ]
         if not has_app_context() or current_user.has_role('superuser'):
             form_rules.append('approve')
    

    На мой взгляд, поскольку одобрить является логической переменной, должно быть суждение по условию, чтобы определить, равно ли оно 0 или 1, а затем поле становится доступным только для чтения или редактируемым соответственно. .

    Спасибо за любой совет заранее.


person Samoth    schedule 05.04.2017    source источник
comment
Я не знаю эту библиотеку, но, возможно, это может быть решение   -  person IcyTv    schedule 07.04.2017
comment
Да, я проверил предоставленную вами ссылку, но я хочу, чтобы столбцы были доступны только для чтения динамически, а не постоянно фиксировались только для чтения.   -  person Samoth    schedule 07.04.2017


Ответы (3)


Как вы уже заметили, установка свойства readonly для поля довольно проста, но сделать его динамическим немного сложно.

Прежде всего вам нужен класс пользовательского поля:

from wtforms.fields import StringField

class ReadOnlyStringField(StringField):
    @staticmethod
    def readonly_condition():
        # Dummy readonly condition
        return False

    def __call__(self, *args, **kwargs):
        # Adding `readonly` property to `input` field
        if self.readonly_condition():
            kwargs.setdefault('readonly', True)
        return super(ReadOnlyStringField, self).__call__(*args, **kwargs)

    def populate_obj(self, obj, name):
        # Preventing application from updating field value
        # (user can modify web page and update the field)
        if not self.readonly_condition():
            super(ReadOnlyStringField, self).populate_obj(obj, name)

Установите атрибут form_overrides для вашего Посмотреть:

class ProjectView(sqla.ModelView):
    form_overrides = {
        'project_name': ReadOnlyStringField
    }

Вам нужно передать пользовательскую функцию readonly_condition экземпляру ReadOnlyStringField. Самый простой способ, который я нашел, это переопределить edit_form< /а> метод:

class ProjectView(sqla.ModelView):
    def edit_form(self, obj=None):
        def readonly_condition():
            if obj is None:
                return False
            return obj.approve
        form = super(ProjectView, self).edit_form(obj)
        form.project_name.readonly_condition = readonly_condition
        return form

Удачного кодирования!

person Sergey Shubin    schedule 07.04.2017
comment
Добрый день! Я следил за вашими 1-й и 2-й частями: class ReadOnlyStringField и form_overrides; однако project name не становится доступным только для чтения. - person Samoth; 08.04.2017
comment
@Samoth Приветствую! Без третьей части статус readonly будет статически основан на фиктивном методе readonly_condition, который всегда возвращает False. Вы можете изменить его на True, чтобы проверить, действительно ли он устанавливает статус readonly. Я просто подумал, что поле должно быть читаемым по умолчанию :) - person Sergey Shubin; 08.04.2017
comment
спасибо, но я установил как readonly_condition, так и True и False, он project name не становится доступным только для чтения. - person Samoth; 10.04.2017
comment
@Samoth Не могли бы вы опубликовать обновленный код? Может github действительно больше подходит. - person Sergey Shubin; 12.04.2017
comment
@ Сергей Шубин, Да, я пытался выложить свой код на github в эти дни, но тщетно. Кажется, я должен использовать командную строку для фиксации кода и не могу загружать напрямую. - person Samoth; 12.04.2017
comment
@Самот Отлично! Но на данный момент приложение использует решение Philip Martin, которое, к сожалению, не работает (см. мой комментарий под его постом). - person Sergey Shubin; 12.04.2017
comment
Решение Мартина у меня не работает, поля динамически не становятся доступными только для чтения на основе значения approve. - person Samoth; 12.04.2017
comment
@ Сергей Шубин, пожалуйста, дайте мне знать, можно ли запустить приложение на вашей машине, так как в моем часовом поясе пора отдохнуть, ха-ха. - person Samoth; 12.04.2017
comment
@Samoth Думаю, будет лучше переключить приложение на мое решение. Затем я могу сделать обзор кода и найти возможные проблемы, почему он не работает в вашем случае :) Приятного отдыха! - person Sergey Shubin; 12.04.2017
comment
@ Сергей Шубин, Да, конечно, я готов это сделать, но на самом деле я не совсем уверен, как интегрировать вашу третью часть (class ProjectView(sqla.ModelView)) в мое приложение. - person Samoth; 12.04.2017
comment
Я уже добавил вашу 1-ю часть в свое приложение ( class ReadOnlyStringField(StringField)) , и, насколько я понимаю, 2-я часть предназначена только для демонстрации, поэтому я их не добавлял. - person Samoth; 12.04.2017
comment
@Samoth Просто добавьте метод edit_form к вашему ProjectView (SWProjectView), он не должен конфликтовать с другим кодом. Без него readonly состояние не будет динамическим. - person Sergey Shubin; 12.04.2017
comment
@ Сергей Шуби, я не уверен, что правильно понял то, что вы только что предложили. Я написал еще один класс ProjectView, чтобы наследовать SWProjectView, а затем добавить в него edit form. И я добавляю представление, используя admin.add_view(ProjectView(Project, db.session)). Хотя до сих пор не работает. - person Samoth; 12.04.2017
comment
@Samoth Нет, добавьте метод edit_form и атрибут form_overrides к SWProjectView так же, как они опубликованы в моем примере. - person Sergey Shubin; 12.04.2017
comment
@ Сергей Шуб, решение работает хорошо и будет принято как лучший ответ. Кстати, я пытался изменить кнопку сохранить в форме редактирования и создать на другую строку, что-то вроде подтвердить или отправить, но не нашел файл для редактирования, подскажите? - person Samoth; 13.04.2017
comment
@Samoth Спасибо, добро пожаловать! Текст кнопки не может быть легко изменен, вам нужно переопределить несколько макросов и блоков в шаблонах edit.html и create.html, чтобы изменить render_form_buttons. Вы можете создать новый вопрос. Я не нашел подобных вопросов на SO — это может быть полезно для сообщества. - person Sergey Shubin; 13.04.2017
comment
@ Сергей Шубин: Если поле представляет собой выпадающую кнопку для выбора, кажется, оно не может стать доступным только для чтения? Спасибо. - person Samoth; 18.04.2017
comment
@ Samoth Не нашел способ сделать это, извините. QuerySelectField, который используется в раскрывающихся списках, не может быть подклассом из-за какой-то ошибки администратора фляги. - person Sergey Shubin; 18.04.2017
comment
@ Сергей Шубин: Я думаю, что в моем приложении есть большой недостаток: каждый пользователь может видеть весь список проектов. Можно ли ограничить, чтобы пользователь мог видеть только свой собственный проект? Поскольку все приведенные примеры flask-admin основаны на ролях до прошлых выходных, я внезапно заметил дефект. Большое спасибо за любой совет заранее. - person Samoth; 19.04.2017
comment
@Samoth Взгляните на этот ответ - вам нужно будет переопределить методы get_query и get_count_query в вашем ProjectView. Я уже пробовал это решение в своем проекте, и оно сработало. - person Sergey Shubin; 19.04.2017
comment
@ Сергей Шубин: Спасибо, а какой переменной мне нужно заменить paid в моем приложении? Не могли бы вы объяснить больше, потому что я не очень уверен, какая здесь переменная, чтобы ограничить, что пользователь может видеть только свой собственный проект. - person Samoth; 19.04.2017
comment
@Samoth Вам нужно пройти фильтр SQLAlchemy. В вашем случае возвращаемое значение может выглядеть так self.session.query(self.model).filter(self.model.reviewers.user_id == current_user.id). Не уверен, как это будет работать: ваши отношения User.projects закомментированы, и вы не знаете, является ли current_user.id вашим фактическим идентификатором пользователя. - person Sergey Shubin; 19.04.2017
comment
@ Сергей Шубин: Спасибо, похоже, я должен установить связь между class project и class user, поскольку вы упомянули User.projects.. - person Samoth; 20.04.2017
comment
@ Сергей Шубин: Есть еще один вопрос по ответу. Если я установлю поле как ReadOnlyStringField, то высота поля станет фиксированной (один вкладыш) и не может быть расширена; это неудобно, когда пользователь редактирует его. Спасибо. - person Samoth; 26.04.2017
comment
@Самот Привет! Это довольно легко исправить: объявите ReadOnlyTextAreaField(TextAreaField) так же, как ReadOnlyStringField, и используйте его для многострочного ввода. - person Sergey Shubin; 26.04.2017
comment
@ Сергей Шубин: извините, я застрял с проблемой и не могу ее решить, не могли бы вы взглянуть на это, независимо от того, использую ли я __str__ и __repr__, оба не работали. Запрос работал и возвращал имена пользователей правильно, но тип данных по-прежнему является ячейкой памяти, поэтому его нельзя сохранить в базе данных. - person Samoth; 12.05.2017
comment
@Самот Привет! Похоже, ваш последний вопрос не относится к вашему последнему вопросу. Эта ошибка упоминается в wtforms вопросах, попробуйте посмотреть здесь. - person Sergey Shubin; 12.05.2017
comment
@ Сергей Шубин: Да, я видел этот пост раньше, но ситуация кажется другой. Я определяю отношение "многие ко многим" между роль&пользователь и команда и пользователь. И это работает нормально. Но когда я пытаюсь запросить его (по идентификатору пользователя и командам), произошла ошибка. - person Samoth; 12.05.2017

Предыдущий ответ, который я здесь поставил, имел серьезный недостаток. Далее используется другой подход: анализируется сама форма и добавляется readonly: True к render_kw для конкретной формы, если выполняется определенное условие.

class ProjectView(sqla.ModelView):
    # ... other class code

    def edit_form(self, obj=None):
        # grab form from super
        form = super(ProjectView, self).edit_form(obj)

        # form.approved.data should be the same as approved
        # if approved is included in the form
        if form.approved.data:
            if form.project_name.render_kw:
                form.project_name.render_kw.update({
                    'readonly': True
                })
            else:
                form.project_name.render_kw = {'readonly': True}
        return form

Это немного хакерски, и для этого требуется, чтобы approved было в форме редактирования. Если бы вы использовали это решение, вы могли бы либо добавить approved в качестве поля readonly, либо вместо readonly вы могли бы удалить поле approved из формы в приведенном выше методе класса.

person Phillip Martin    schedule 07.04.2017
comment
спасибо, и я изменил свой код следующим образом: def _form_edit_rules(self): return { CustomizableField('project_name', field_args={ 'readonly': not self.model.approve }), rules.RuleSet(self, self.edit_form_rules) } но возникает ошибка: AttributeError: 'set' object has no attribute 'visible_fields' - person Samoth; 08.04.2017
comment
Нашел 2 ситуации: not self.model.approve: то поля редактируются постоянно; и self.model.approve: тогда поля все время доступны только для чтения. Это не динамически основано на значении approve. - person Samoth; 08.04.2017
comment
К сожалению, также не сработало: self.model.approve является атрибутом модели class, а не атрибутом instance, поэтому он всегда приводится к типу True. - person Sergey Shubin; 12.04.2017
comment
@SergeyShubin, спасибо, что указали на это. Это было упущением с моей стороны. Я изменил свой ответ, чтобы отредактировать форму непосредственно на основе содержимого формы. - person Phillip Martin; 12.04.2017

Для меня этот трюк был самым простым способом сделать это:

from flask_sqlalchemy import SQLAlchemy
from flask_admin.contrib.sqla import ModelView
from flask_admin.form.rules import Field


class Example(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    not_editable = db.Column(db.Unicode(128))
    editable = db.Column(db.Unicode(128))


class ReadonlyFiledRule(Field):
    def __call__(self, form, form_opts=None, field_args={}):
        field_args['readonly'] = True
        return super(ReadonlyFiledRule, self).__call__(form, form_opts, field_args)


class ExampleView(ModelView):
    form_edit_rules = (ReadonlyFiledRule('not_editable'), 'editable', )

Обновить (самый простой способ):

class ExampleView(ModelView):
    form_widget_args = {
        'not_editable': {
            'readonly': True
        }
    }
person Vladyslav    schedule 19.02.2018