Flask-Admin:如何根据其他列的值使列只读?

7
我已经建立了一个系统,允许用户申请代码审查并等待经理批准。
现在我想要实现以下目标:
1. 如果被批准,enter image description here 那么所有字段都变为只读(这里我手动将项目名称设置为只读):
enter image description here
  1. If it's rejected,

    enter image description here

    then all the fields become editable. Of course, when creating a new project, all the fields should be editable.
    enter image description here

    The code of class Project and ProjectView are as below:

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

    In my opinion, since approve is a boolean variable, there should be a condition judgement to tell if it is 0 or 1 and then the field become read-only or editable accordingly.

    Thanks for any advise in advance.


我不熟悉这个库,但也许这个链接可以解决问题。 - IcyTv
是的,我已经检查了您提供的链接,但我想要的是动态设置列为只读,而不是一直固定为只读。 - Samoth
3个回答

6

如您已经注意到的,为字段设置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

愉快的编码!


您好!我按照您的第一部分和第二部分:class ReadOnlyStringFieldform_overrides 进行了操作,但是 项目名称 并没有变成只读状态。 - Samoth
@Samoth 谢谢,不用客气!按钮文本不能轻易更改,您需要覆盖“edit.html”和“create.html”模板中的多个宏和块以更改render_form_buttons宏。您可能想要创建新问题。我在SO上没有找到类似的问题 - 这对社区可能有用。 - Sergey Shubin
@ Sergey Shubin:谢谢,那么在我的应用程序中我需要替换的变量是什么?您能否解释得更详细一些,因为我不太确定这里需要限制用户只能看到自己的项目的变量是什么。 - Samoth
@ Sergey Shubin:不好意思,我遇到了一个问题,无法解决。你能否看一下这个链接(http://stackoverflow.com/questions/43908969/flask-admin-sqlalchemy-exc-interfaceerrorerror-binding-parameter-8),我尝试使用`__str__`和`__repr__`都没有起作用。查询可以正常工作并正确返回用户名,但数据类型仍然是内存位置,因此无法存储在数据库中。 - Samoth
@ Sergey Shubin:是的,我之前看过这篇帖子,但情况似乎不同。我在“角色和用户”以及“团队和用户”之间定义了多对多的关系,并且它运行良好。但是当我尝试通过用户ID和团队进行查询时,出现了错误。 - Samoth
显示剩余24条评论

3

我之前在这里发布的答案有一个重大缺陷。以下使用不同的方法通过分析表单本身,并在特定条件满足时向特定表单的render_kw添加readonly: True来解决问题。

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字段添加,或者在上述类方法中移除表单中的approved字段,而不是使用readonly


谢谢,我根据以下方式修改了我的代码: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' - Samoth
我发现了两种情况:not self.model.approve:这时字段始终可编辑;而self.model.approve:这时字段始终只读。它不是基于“approve”值的动态变化。 - Samoth
很不幸,也没有起作用:self.model.approve 是模型的 属性而不是 实例 属性,因此它总是强制转换为 True - Sergey Shubin
@SergeyShubin,感谢您指出这一点。这是我的疏忽。我改变了我的答案,根据表格的内容直接编辑表格。 - Phillip Martin

2

对我来说,这个技巧是最简单的方法:

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
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接