0

I want to have some rows editable, and some view-only, based on some condition, how do I achieve that?

zardos
  • 5
  • 3

1 Answers1

1

You can accomplish this by overriding the list_row_actions block of the Flask-Admin list template. This block will call a method defined in the view passing the action and row (i.e. the model) as parameters for each row action defined in the view. The method returns True or False depending if the action should be allowed. A simple example follows.

In templates/admin directory create list.html:

{% extends 'admin/model/list.html' %}

{% block list_row_actions scoped %}

    {% for action in list_row_actions %}
        {% if admin_view.allow_row_action(action, row) %}
            {{ action.render_ctx(get_pk_value(row), row) }}
        {% endif %}
    {% endfor %}

{% endblock %}

Note admin_view is the current view and we have created a method allow_row_action taking parameters action and row (the model) in the view.

In the view code define a mixin:

class RowActionListMixin(object):

    list_template = 'admin/list.html'

    def allow_row_action(self, action, model):
        return True

Any view implementing this mixin will use the overridden list template defined above. It also defines the method allow_row_action that returns True.

Now define any view that you want to control the row actions as follows:

class Student1View(RowActionListMixin, sqla.ModelView):
    column_default_sort = ('last_name', False)
    column_searchable_list = ('first_name', 'last_name')
    column_filters = ('allow_edit', 'allow_delete')

    def _can_edit(self, model):
        # Put your logic here to allow edit per model
        # return True to allow edit
        return model.allow_edit

    def _can_delete(self, model):
        # Put your logic here to allow delete per model
        # return True to allow delete
        return model.allow_delete

    def allow_row_action(self, action, model):

        # # Deal with Edit Action
        if isinstance(action, EditRowAction):
            return self._can_edit(model)

        # # Deal with Delete Action
        if isinstance(action, DeleteRowAction):
            return self._can_delete(model)

        # # Deal with other actions etc

        # otherwise whatever the inherited method returns
        return super().allow_row_action()

Note the overridden allow_row_method. It checks what the action is and passes the decision making on to an appropriate local method.

Here is a self contained single file example, you'll need the list template defined above too. The Student model has two fields allow_edit and allow_delete along with first_name and last_name fields. The allow_edit and allow_delete allow you to dynamically switch if a Student can be edited and/or deleted'. Student1View allows editing/deleting based on a student's allow_edit and allow_delete values. Student2View doesn't override allow_row_action so row actions are allowed to happen because the base method defined in RowActionListMixin class returns True.

enter image description here

from faker import Faker
import click
from flask import Flask
from flask_admin.model.template import EditRowAction, DeleteRowAction
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib import sqla

db = SQLAlchemy()


class Student(db.Model):
    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)
    allow_edit = db.Column(db.Boolean(), default=False, nullable=False)
    allow_delete = db.Column(db.Boolean(), default=False, nullable=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'

db.init_app(app)


@app.cli.command('create-database', short_help='Create student database')
@click.option('--count', default=100, help='Number of students (default 100)')
def create_database(count):

    """
        Create database with "count" students
    """

    db.drop_all()
    db.create_all()
    _faker = Faker()
    for _ in range(0, count):
        _student = Student(
            first_name=_faker.first_name(),
            last_name=_faker.last_name(),
            allow_edit=_faker.boolean(),
            allow_delete=_faker.boolean()
        )
        db.session.add(_student)

    db.session.commit()


class RowActionListMixin(object):

    list_template = 'admin/list.html'

    def allow_row_action(self, action, model):
        return True


class Student1View(RowActionListMixin, sqla.ModelView):
    column_default_sort = ('last_name', False)
    column_searchable_list = ('first_name', 'last_name')
    column_filters = ('allow_edit', 'allow_delete')

    def _can_edit(self, model):
        # Put your logic here to allow edit per model
        # return True to allow edit
        return model.allow_edit

    def _can_delete(self, model):
        # Put your logic here to allow delete per model
        # return True to allow delete
        return model.allow_delete

    def allow_row_action(self, action, model):

        # # Deal with Edit Action
        if isinstance(action, EditRowAction):
            return self._can_edit(model)

        # # Deal with Delete Action
        if isinstance(action, DeleteRowAction):
            return self._can_delete(model)

        # # Deal with other actions etc

        # otherwise whatever the inherited method returns
        return super().allow_row_action()


class Student2View(RowActionListMixin, sqla.ModelView):
    column_default_sort = ('last_name', False)
    column_searchable_list = ('first_name', 'last_name')
    column_filters = ('allow_edit', 'allow_delete')


# Flask views
@app.route('/')
def index():
    return '<a href="/admin/">Click me to get to Admin!</a>'


admin = Admin(app, template_mode="bootstrap3")
admin.add_view(Student1View(Student, db.session, name='Student 1', category='Students', endpoint='student-1'))
admin.add_view(Student2View(Student, db.session, name='Student 2', category='Students', endpoint='student-2'))


if __name__ == '__main__':
    app.run()
pjcunningham
  • 7,676
  • 1
  • 36
  • 49