I want to have some rows editable, and some view-only, based on some condition, how do I achieve that?
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
.
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()