Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flask-admin: How to make columns read-only according to other columns' value?

I've built a system that allows users to apply for code review and wait for manager to approve.

And now what I want to achieve is as below:

  1. If it's approved, enter image description here then all the fields become read-only(I manually set Project name as read-only 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.

like image 377
Samoth Avatar asked Apr 05 '17 05:04

Samoth


2 Answers

As you already noticed setting readonly property for a field is rather simple but making it dynamic is a bit tricky.

First of all you need a custom field class:

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)

Set form_overrides attribute for your view:

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

You need to pass custom readonly_condition function to ReadOnlyStringField instance. The easiest way I found is overriding edit_form method:

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

Happy coding!

like image 53
Sergey Shubin Avatar answered Sep 20 '22 20:09

Sergey Shubin


The previous answer I put on here had a major flaw. The following uses a different approach by analyzing the form itself and adding readonly: True to render_kw for a particular form if a certain condition is met.

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

This is a bit hacky, and it requires that approved be in the edit form. If you used this solution, you could either add approved as a readonly field or, instead of readonly, you could remove the approved field from the form in the above class method.

like image 37
Phillip Martin Avatar answered Sep 18 '22 20:09

Phillip Martin