Introduction To Flask-Admin¶
Getting Started¶
Installing Flask-Admin¶
Flask-Admin provides an easy-to-use layer on top of a number of databases and file stores. Whether you use SQLAlchemy, peewee, AWS S3, or something else that Flask-Admin supports, we don’t install those things out-of-the-box. This reduces the risk of compatibility issues and means that you don’t download/install anything you don’t need.
Depending on what you use, you should install Flask-Admin with your required extras selected.
Flask-Admin has these optional extras you can select:
Extra name |
What functionality does this add to Flask-Admin? |
|---|---|
sqlalchemy |
SQLAlchemy, for accessing many database engines |
sqlalchemy-with-utils |
As above, with some additional utilities for different data types |
geoalchemy |
As with SQLAlchemy, but adding support for geographic data and maps |
pymongo |
Supports the PyMongo library |
mongoengine |
Supports the MongoEngine library |
peewee |
Supports the peewee library |
s3 |
Supports file admin using AWS S3 |
azure-blob-storage |
Supports file admin using Azure blob store |
images |
Allows working with image data |
export |
Supports downloading data in a variety of formats (eg TSV, JSON, etc) |
rediscli |
Allows Flask-Admin to display a CLI for Redis |
translation |
Supports translating Flask-Admin into a number of languages |
all |
Installs support for all features |
Once you’ve chosen the extras you need, install Flask-Admin by specifying them like so:
pip install flask-admin[sqlalchemy,s3,images,export,translation]
Initialization¶
The first step is to initialize an empty admin interface for your Flask app:
from flask import Flask
from flask_admin import Admin
app = Flask(__name__)
admin = Admin(app, name='microblog', theme=Bootstrap4Theme(swatch='cerulean'))
# Add administrative views here
app.run()
Here, both the name and theme parameters are optional, have a look to the API
Admin for more details about all other parameters.
Alternatively, you could use the init_app() method.
If you start this application and navigate to http://localhost:5000/admin/, you should see an empty page with a navigation bar on top. Customize the look by specifying one of the included Bootswatch themes (see https://bootswatch.com/4/ for a preview of the swatches). Here is list of all available themes::
all_themes = [
"default", "cerulean", "cosmo", "cyborg", "darkly",
"flatly", "journal", "litera", "lumen", "lux", "materia",
"minty", "pulse", "sandstone", "simplex", "sketchy", "slate",
"solar", "spacelab", "superhero", "united", "yeti"
]
Adding Model Views¶
Model views allow you to add a dedicated set of admin pages for managing any model in your database. Do this by creating instances of the ModelView class, which you can import from one of Flask-Admin’s built-in ORM backends. An example is the SQLAlchemy backend, which you can use as follows:
from flask_admin.contrib.sqla import ModelView
# Flask and Flask-SQLAlchemy initialization here
admin = Admin(app, name='microblog', theme=Bootstrap4Theme())
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))
Straight out of the box, this gives you a set of fully featured CRUD views for your model:
A list view, with support for searching, sorting, filtering, and deleting records.
A create view for adding new records.
An edit view for updating existing records.
An optional, read-only details view.
There are many options available for customizing the display and functionality of these built-in views. For more details on that, see Customizing Built-in Views. For more details on the other ORM backends that are available, see Using Different Database Backends.
Adding Content to the Index Page¶
The first thing you’ll notice when you visit http://localhost:5000/admin/ is that it’s just an empty page with a navigation menu. To add some content to this page, save the following text as admin/index.html in your project’s templates directory:
{% extends 'admin/master.html' %}
{% block body %}
<p>Hello world</p>
{% endblock %}
This will override the default index template, but still give you the built-in navigation menu. So, now you can add any content to the index page, while maintaining a consistent user experience.
Customizing Built-in Views¶
When inheriting from ModelView, values can be specified for numerous configuration parameters. Use these to customize the views to suit your particular models:
from flask_admin.contrib.sqla import ModelView
# Flask and Flask-SQLAlchemy initialization here
class MicroBlogModelView(ModelView):
can_delete = False # disable model deletion
page_size = 50 # the number of entries to display on the list view
admin.add_view(MicroBlogModelView(User, db.session))
admin.add_view(MicroBlogModelView(Post, db.session))
Or, in much the same way, you can specify options for a single model at a time:
class UserView(ModelView):
can_delete = False # disable model deletion
class PostView(ModelView):
page_size = 50 # the number of entries to display on the list view
admin.add_view(UserView(User, db.session))
admin.add_view(PostView(Post, db.session))
ModelView Configuration Attributes¶
For a complete list of the attributes that are defined, have a look at the
API documentation for BaseModelView(). Here are
some of the most commonly used attributes:
To disable some of the CRUD operations, set any of these boolean parameters:
can_create = False
can_edit = False
can_delete = False
Controlling Columns:¶
To include only a subset of the model’s columns in the list view, specify a list of column names for the column_list parameter:
column_list = ['name', 'email', 'country']
By default, the Primary key column is excluded from the list view, but you can include it by adding it to the column_list or by setting:
column_display_pk = True
If your model has too much data to display in the list view, you can add a read-only details view, with option to show this view in a modal. This can be done by setting:
can_view_details = True
column_details_list = ['ip_address', 'user_agent', 'created_at', 'updated_at']
details_modal = False
Removing columns from the list view or from the details view is easy, just pass a list of column names for the column_exclude_list and column_details_exclude_list parameters:
column_exclude_list = ['password', ]
column_details_exclude_list = ['password', ]
Renaming columns is also easy, just specify a dictionary mapping column names to their new labels:
column_labels = {
'name': 'Full Name',
'email': 'Email Address',
'country': 'Country of Residence'
}
And if you want more description for the columns to be presented as a tooltip, you can specify a dictionary mapping column names to their descriptions. This description will be also presented in create/edit form field:
column_descriptions = {
'name': 'The full name of the user',
'email': 'The email address of the user',
'country': 'The country where the user lives'
}
To make columns searchable, or to use them for filtering, specify a list of column names:
column_searchable_list = ['name', 'email']
column_filters = ['country']
To make columns sortable, specify a list of column names like the example below. The default sort can be specified using the column_default_sort attribute:
column_sortable_list = ['name', 'email', 'country']
See more details and examples of sortable columns in the API documentation
for column_sortable_list().
A value can presented in different ways by specifying a formatter function for the column:
column_formatters = {
"price" : lambda v, c, m, p: m.price*2,
"created_at": lambda v, c, m, p: m.created_at.strftime('%Y-%m-%d'),
"imgage": lambda v, c, m, p: Markup('<img src="%s">' % m.image_url)
}
See the API documentation for column_formatters() for more
details and examples of formatter functions. And for details view the column_formatters_detail
can be used:
column_formatters_detail = {
"created_at": lambda v, c, m, p: m.created_at.strftime('%Y-%m-%d %H:%M:%S'),
}
For generalization, you can use column_type_formatters to specify formatters for all columns of a given type. The following example demonstrates how to do this:
from flask_admin.model import typefmt
from datetime import datetime
MY_DEFAULT_FORMATTERS = dict(typefmt.BASE_FORMATTERS)
MY_DEFAULT_FORMATTERS.update({
datetime: lambda v, c, m, p: m.created_at.strftime('%Y-%m-%d %H:%M:%S'),
})
class MyModelView(ModelView):
column_type_formatters = MY_DEFAULT_FORMATTERS
Likewise, you can use column_type_formatters_detail to specify formatters for all columns of a given type in the details view:
column_type_formatters_detail = MY_DEFAULT_FORMATTERS
Controlling Rows:¶
Pagination is enabled by default, but you can disable it by setting:
page_size = 50 # the number of entries per page.
can_set_page_size = True
page_size_options = (10, 20, 50, 100)
By default, the list view includes a set of action buttons for each row, which allow you to edit, delete, or view details for that record. To disable these buttons, set:
column_display_actions = False
And to add custom action buttons to the list view, specify a list of dictionaries for the column_extra_row_actions parameter:
from flask_admin.model.template import EndpointLinkRowAction, LinkRowAction
class MyModelView(BaseModelView):
column_extra_row_actions = [
LinkRowAction(
'glyphicon glyphicon-off', 'http://direct.link/?id={row_id}'
),
EndpointLinkRowAction(
'glyphicon glyphicon-test', 'my_view.index_view'
)
]
Creating/Editing Rows:¶
For a faster editing experience, enable inline editing in the list view:
column_editable_list = ['name', 'last_name']
Editable_list is converts each column into Ajax form so that you can edit & save the new value
in same row. see the API docs ajax_update() for Ajax example.
Another way of inline editing without losing the current context, have the add & edit forms display inside a modal window on the list page, instead of the dedicated create, edit and details pages:
create_modal = True
edit_modal = True
details_modal = True
You can restrict the possible values for a text-field by specifying a list of select choices:
form_choices = {
'title': [
('MR', 'Mr'),
('MRS', 'Mrs'),
('MS', 'Ms'),
('DR', 'Dr'),
('PROF', 'Prof.')
]
}
To remove fields from the create and edit forms:
form_excluded_columns = ['last_name', 'email']
To specify WTForms field arguments:
form_args = {
'name': {
'label': 'First Name',
'validators': [required()]
}
}
Or, to specify arguments to the WTForms widgets used to render those fields:
form_widget_args = {
'description': {
'rows': 10,
'style': 'color: black'
}
}
When your forms contain foreign keys, have those related models loaded via ajax, using:
form_ajax_refs = {
'user': {
'fields': ['first_name', 'last_name', 'email'],
'page_size': 10
},
'post': {
'fields': ['title', 'body'],
'page_size': 10
}
}
To filter the results that are loaded via ajax, you can use:
form_ajax_refs = {
'active_user': QueryAjaxModelLoader('user', db.session, User,
filters=["is_active=True", "id>1000"])
}
To manage related models inline:
inline_models = ['post', ]
These inline forms can be customized. Have a look at the API documentation for
inline_models().
Exporting:¶
To enable csv export of the model view with including and excluding specific columns, data formatting options, and type-based formatters:
can_export = True
column_export_list = ['name', 'email', 'country']
column_export_exclude_list = ['password', ]
column_formatters_export = {
'created_at': lambda v, c, m, p: m.created_at.strftime('%Y-%m-%d'),
}
column_type_formatters_export = {
datetime: lambda v, c, m, p: m.created_at.strftime('%Y-%m-%d'),
}
This will add a button to the model view that exports records, truncating at export_max_rows.
you can also specify the export types that are available for the model view:
export_types = ['csv', 'json']
Adding Your Own Views¶
For situations where your requirements are really specific and you struggle to meet
them with the built-in ModelView class, Flask-Admin makes it easy for you to
take full control and add your own views to the interface.
Standalone Views¶
A set of standalone views (not tied to any particular model) can be added by extending the
BaseView class and defining your own view methods. For
example, to add a page that displays some analytics data from a 3rd-party API:
from flask_admin import BaseView, expose
class AnalyticsView(BaseView):
@expose('/')
def index(self):
return self.render('analytics_index.html')
admin.add_view(AnalyticsView(name='Analytics', endpoint='analytics'))
This will add a link to the navbar for your view. Notice that it is served at ‘/’, the root URL. This is a restriction on standalone views: at the very minimum, each view class needs at least one method to serve a view at its root.
The analytics_index.html template for the example above, could look something like:
{% extends 'admin/master.html' %}
{% block body %}
<p>Here I'm going to display some data.</p>
{% endblock %}
By extending the admin/master.html template, you can maintain a consistent user experience, even while having tight control over your page’s content.
Overriding the Built-in Views¶
There may be some scenarios where you want most of the built-in ModelView functionality, but you want to replace one of the default create, edit, or list views. For this you could override only the view in question, and all the links to it will still function as you would expect:
from flask_admin.contrib.sqla import ModelView
from flask_admin import expose
# Flask and Flask-SQLAlchemy initialization here
class UserView(ModelView):
@expose('/new/', methods=('GET', 'POST'))
def create_view(self):
"""
Custom create view.
"""
return self.render('create_user.html')
Configure Views¶
Views can be configured by changing some Environment variables.
Variable |
Desctiption |
|---|---|
Geographical Map views |
To enable use of maps:
Usually Works with:
Can be integrated with MapBox using:
And can use with Google Search with:
see more Display map widgets |
Exception handling |
To show exceptions on the output rather than flash messages:
|
Note: The FLASK_ADMIN_SWATCH, FLASK_ADMIN_FLUID_LAYOUT variables are
deprecated and moved to BootstrapTheme
Working With the Built-in Templates¶
Flask-Admin uses the Jinja2 templating engine.
Extending the Built-in Templates¶
Rather than overriding the built-in templates completely, it’s best to extend them. This will make it simpler for you to upgrade to new Flask-Admin versions in future.
Internally, the Flask-Admin templates are derived from the admin/master.html template. The three most interesting templates for you to extend are probably:
admin/model/list.html
admin/model/create.html
admin/model/edit.html
To extend the default edit template with your own functionality, create a template in templates/microblog_edit.html to look something like:
{% extends 'admin/model/edit.html' %}
{% block body %}
<h1>MicroBlog Edit View</h1>
{{ super() }}
{% endblock %}
Now, to make your view classes use this template, set the appropriate class property:
class MicroBlogModelView(ModelView):
edit_template = 'microblog_edit.html'
# create_template = 'microblog_create.html'
# list_template = 'microblog_list.html'
# details_template = 'microblog_details.html'
# edit_modal_template = 'microblog_edit_modal.html'
# create_modal_template = 'microblog_create_modal.html'
# details_modal_template = 'microblog_details_modal.html'
If you want to use your own base template, then pass the name of the template to the admin theme during initialization:
admin = Admin(app, Bootstrap4Theme(base_template='microblog_master.html'))
Overriding the Built-in Templates¶
To take full control over the style and layout of the admin interface, you can override all of the built-in templates. Just keep in mind that the templates will change slightly from one version of Flask-Admin to the next, so once you start overriding them, you need to take care when upgrading your package version.
To override any of the built-in templates, simply copy them from the Flask-Admin source into your project’s templates/admin/ directory. As long as the filenames stay the same, the templates in your project directory should automatically take precedence over the built-in ones.
Available Template Blocks¶
Flask-Admin defines one base template at admin/master.html that all other admin templates are derived from. This template is a proxy which points to admin/base.html, which defines the following blocks:
Block Name |
Description |
|---|---|
head_meta |
Page metadata in the header |
title |
Page title |
head_css |
Various CSS includes in the header |
head |
Empty block in HTML head, in case you want to put something there |
page_body |
Page layout |
brand |
Logo in the menu bar |
main_menu |
Main menu |
menu_links |
Links menu |
access_control |
Section to the right of the menu (can be used to add login/logout buttons) |
messages |
Alerts and various messages |
page_title |
Page title containing the view name and icon |
body |
Content (that’s where your view will be displayed) |
tail |
Empty area below content |
In addition to all of the blocks that are inherited from admin/master.html, the admin/model/list.html template also contains the following blocks:
Block Name |
Description |
|---|---|
model_menu_bar |
Menu bar for the model view with add/export/etc buttons |
model_list_table |
Table container for the list view |
list_header |
Table header row for the list view |
list_row_actions_header |
Actions header |
list_row |
Single row |
list_row_actions |
Row action cell with edit/remove/etc buttons |
empty_list_message |
Message that will be displayed if there are no models found |
Have a look at the layout example at https://github.com/pallets-eco/flask-admin/tree/master/examples/custom-layout to see how you can take full stylistic control over the admin interface.
Template Context Variables¶
While working in any of the templates that extend admin/master.html, you have access to a small number of context variables:
Variable Name |
Description |
|---|---|
admin_view |
Current administrative view |
admin_base_template |
Base template name |
theme |
The Theme configuration passed into Flask-Admin at instantiation |
_gettext |
Babel gettext |
_ngettext |
Babel ngettext |
h |
Helpers from |
Generating URLs¶
To generate the URL for a specific view, use url_for with a dot prefix:
from flask import url_for
from flask_admin import expose
class MyView(BaseView):
@expose('/')
def index(self):
# Get URL for the test view method
user_list_url = url_for('user.index_view')
return self.render('index.html', user_list_url=user_list_url)
A specific record can also be referenced with:
# Edit View for record #1 (redirect back to index_view)
url_for('user.edit_view', id=1, url=url_for('user.index_view'))
When referencing ModelView instances, use the lowercase name of the model as the prefix when calling url_for. Other views can be referenced by specifying a unique endpoint for each, and using that as the prefix. So, you could use:
url_for('analytics.index')
If your view endpoint was defined like:
admin.add_view(CustomView(name='Analytics', endpoint='analytics'))