flask-admin Documentation

Simple and extensible administrative interface framework for Flask

6,068 stars Python Python 48 files · ~29,938 tokens
RAW Doc

Technical Documentation: pallets-eco/flask-admin

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`README.md`)

Flask-Admin

Flask-Admin is now part of Pallets-Eco, an open source organization managed by the
Pallets team to facilitate community maintenance of Flask extensions. Please update
your references to https://github.com/pallets-eco/flask-admin.git.

Pallets Community Ecosystem

[!IMPORTANT]
This project is part of the Pallets Community Ecosystem. Pallets is the open
source organization that maintains Flask; Pallets-Eco enables community
maintenance of related projects. If you are interested in helping maintain
this project, please reach out on the Pallets Discord server.

Introduction

Flask-Admin is a batteries-included, simple-to-use
Flask extension that lets you add admin
interfaces to Flask applications. It is inspired by the django-admin
package, but implemented in such a way that the developer has total
control over the look, feel, functionality and user experience of the resulting
application.

Out-of-the-box, Flask-Admin plays nicely with various ORM's, including

It also boasts a simple file management interface and a Redis client console.

The biggest feature of Flask-Admin is its flexibility. It aims to provide a
set of simple tools that can be used to build admin interfaces of
any complexity. To start off, you can create a very simple
application in no time, with auto-generated CRUD-views for each of your
models. Then you can further customize those views and forms as
the need arises.

Flask-Admin is an active project, well-tested and production-ready.

Examples

Several usage examples are included in the /examples folder. Please add your own, or improve on the existing examples, and submit a pull-request.

How to run an example

Clone the repository and navigate to an example (for this example we are using SQLAlchemy Example):

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/sqla

All examples use uv to manage their dependencies and the developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Check the Flask app running on http://localhost:5000.

Documentation

Flask-Admin is extensively documented, you can find all of the
documentation at https://flask-admin.readthedocs.io/en/latest/.

The docs are auto-generated from the .rst files in the /doc folder.
If you come across any errors or if you think of anything else that
should be included, feel free to make the changes and submit a pull-request.

To build the docs in your local environment, from the project directory:

shell
tox -e docs

Installation

To install Flask-Admin using pip, simply:

shell
pip install flask-admin

Contributing

If you are a developer working on and maintaining Flask-Admin, checkout the repo by doing:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin

Flask-Admin uses uv to manage its dependencies and developer environment. With
the repository checked out, to install the minimum version of Python that Flask-Admin supports, create your
virtual environment, and install the required dependencies, run:

shell
uv sync

This will install Flask-Admin but without any of the optional extra dependencies, such as those for sqlalchemy
or mongoengine support. To install all extras, run:

shell
uv sync --extra all

Finally, enable pre-commit hooks:

shell
pre-commit install

Tests

Tests are run with pytest. If you are not familiar with this package, you can find out more on their website.

Running tests inside the devcontainer (eg when using VS Code)

If you are developing with the devcontainer configuration, then you can run tests directly using either of the following commands.

To just run the test suite with the default python installation, use:

shell
uv run pytest

To run the test suite against all supported python versions, and also run other checks performed by CI, use:

shell
uv run tox

Running tests as a one-off via docker-compose run / `make test`

If you don't use devcontainers then you can run the tests using docker (you will need to install and setup docker yourself). Then you can use:

shell
make test-in-docker

This will use the devcontainer docker-compose configuration to start up postgres, azurite and mongo.

You can also run the full test suite including CI checks with:

shell
make tox-in-docker

3rd Party Stuff

Flask-Admin is built with the help of
Bootstrap,
Select2 and
Bootswatch.

If you want to localize your application, install the
Flask-Babel package.

You can help improve Flask-Admin's translations by opening a PR.

As a developer who's changed some text in Flask-Admin

bash
uv sync --group docs
cd babel
./babel.sh --update

As a translator who's updated some `.po`/`.mo` files

Run cd babel
Run ./babel.sh


Overview (`babel/README.md`)

Working with Babel translations

bash
uv sync --group docs

As a developer who's changed some text in Flask-Admin

Run ./babel.sh --update

As a translator who wants to find missing translations

Run awk '/^msgid / {msgid=substr($0, 8, length($0)-8)} /^msgstr ""$/ {print msgid}' file.po

As a translator who's updated some `.po`/`.mo` files

Run ./babel.sh


Overview (`examples/auth/README.md`)

Auth Example

This example shows how to integrate Flask-Security with Flask-Admin using the SQLAlchemy backend. It only implements the 'login' & 'register' views, but you could follow the same approach for using all of Flask-Security's builtin views (e.g. 'forgot password', 'change password', 'reset password', 'send confirmation' and 'send login').

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/auth

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/auth_flask_login/README.md`)

Auth with Flask-Login Example

This example shows how to integrate Flask-Login authentication with Flask-Admin using the SQLAlchemy backend.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/auth-flask-login

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/azure_blob_storage/README.md`)

Azure Blob Storage Example

Flask-Admin example for an Azure Blob Storage account.

If you opened this repository in GitHub Codespaces or a Dev Container with the "flask-admin tests" configuration, you can jump straight to step 4.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/azure-blob-storage

This example uses testcontainers to manage its service dependencies automatically. Make sure Docker is installed and running before starting the example.

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/babel/README.md`)

Flask-Babel Integration Example

This example show how to translate Flask-Admin into different language using customized version of the Flask-Babel <https://github.com/python-babel/flask-babel>

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/babel

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/bootstrap4/README.md`)

Bootstrap4 Customization Example

This example shows how you can customize the look & feel of the admin interface. This is done by overriding some of the built-in templates.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/bootstrap4

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/csp_nonce/README.md`)

CSP Nonce Example

This example shows how to make Flask-Admin work with a Content-Security-Policy by injecting a nonce into HTML tags.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/csp-nonce

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/custom_layout/README.md`)

Custom Layout Example

This example shows how you can customize the look & feel of the admin interface. This is done by overriding some of the built-in templates.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/custom-layout

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/datetime_timezone/README.md`)

Datetime Timezone Example

This example shows how to make Flask-Admin display all datetime fields in client's timezone. Timezone conversion is handled by the frontend in /static/js/timezone.js, but an automatic post request to /set_timezone is done so that flask session can store the client's timezone and save datetime inputs in the correct timezone.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/datetime-timezone

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/forms_files_images/README.md`)

Forms Files and Images Example

This example shows how you can:

  • define your own custom forms by using form rendering rules
  • handle generic static file uploads
  • handle image uploads
  • turn a TextArea field into a rich WYSIWYG editor using WTForms and CKEditor

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/forms-files-images

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/geo_alchemy/README.md`)

GeoAlchemy Example

GeoAlchemy Example with PostGIS.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/geo-alchemy

Install PostgreSQL on your macOS:

sh
brew install postgresql

This example uses testcontainers to manage its service dependencies automatically. Make sure Docker is installed and running before starting the example.

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

You will notice that the maps are not rendered. By default, Flask-Admin expects an integration with Mapbox. To see them, you will have to register for a free account at Mapbox and set the FLASK_ADMIN_MAPBOX_MAP_ID* and FLASK_ADMIN_MAPBOX_ACCESS_TOKEN config variables accordingly.

However, some of the maps are overridden to use Open Street Maps


Overview (`examples/host_matching/README.md`)

Host Matching Example

This example shows how to configure Flask-Admin when you're using Flask's host_matching mode. Any Flask-Admin instance can be exposed on just a specific host, or on every host.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/host-matching

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/methodview/README.md`)

MethodView Example

Example which shows how to integrate Flask MethodView with Flask-Admin.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/methodview

> This example uses [`uv`](https://docs.astral.sh/uv/) to manage its dependencies and developer environment.

Run the example using `uv`, which will manage the environment and dependencies automatically:

```shell
uv run main.py

Overview (`examples/mongoengine/README.md`)

MongoEngine Example

MongoEngine model backend integration example.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/mongoengine

This example uses testcontainers to manage its service dependencies automatically. Make sure Docker is installed and running before starting the example.

This example uses uv to manage its dependencies and developer environment.
Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/multiple_admin_instances/README.md`)

Multiple Admin Instances Example

This example shows how to create two separate instances of Flask-Admin for one Flask application.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/multiple-admin-instances

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/peewee_simple/README.md`)

Peewee Model Backend Integration Example

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/peewee

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/pymongo_simple/README.md`)

PyMongo Example

PyMongo model backend integration example.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/pymongo

This example uses testcontainers to manage its service dependencies automatically. Make sure Docker is installed and running before starting the example.

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/rediscli/README.md`)

RedisCLI Example

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/rediscli

This example uses testcontainers to manage its service dependencies automatically. Make sure Docker is installed and running before starting the example.

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/s3/README.md`)

S3 Example

Flask-Admin example for an S3 bucket.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/s3

This example uses testcontainers to manage its service dependencies automatically. Make sure Docker is installed and running before starting the example.

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/simple/README.md`)

Simple Flask-Admin Example

This example shows how to add some simple views to your admin interface. The views do not have to be associated to any of your models, and you can fill them with whatever content you want.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/simple

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/sqla/README.md`)

SQLAlchemy Model Backend Example

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/sqla

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/sqla_association_proxy/README.md`)

SQLAlchemy Association Proxy Example

Example of how to use (and filter on) an association proxy with the SQLAlchemy backend.

For information about association proxies and how to use them, please visit the docs

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/sqla-association-proxy

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Overview (`examples/sqla_custom_inline_forms/README.md`)

SQLAlchemy Custom Inline Forms Example

This example shows how to use inline forms when working with related models.

How to run this example

Clone the repository and navigate to this example:

shell
git clone https://github.com/pallets-eco/flask-admin.git
cd flask-admin/examples/sqla-custom-inline-forms

This example uses uv to manage its dependencies and developer environment.

Run the example using uv, which will manage the environment and dependencies automatically:

shell
uv run main.py

Doc Adding A New Model Backend (`doc/adding_a_new_model_backend.rst`)

.. _adding-model-backend:

Adding A Model Backend

Flask-Admin makes a few assumptions about the database models that it works with. If you want to implement your own
database backend, and still have Flask-Admin's model views work as expected, then you should take note of the following:

text
1. Each model must have one field which acts as a `primary key` to uniquely identify instances of that model.
   However, there are no restriction on the data type or the field name of the `primary key` field.
2. Models must make their data accessible as python properties.

If that is the case, then you can implement your own database backend by extending the BaseModelView class,
and implementing the set of scaffolding methods listed below.

Extending BaseModelView

text
Start off by defining a new class, which derives from from :class:`~flask_admin.model.BaseModelView`::

    class MyDbModel(BaseModelView):
        pass

This class inherits BaseModelView's `__init__` method, which accepts a model class as first argument. The model
class is stored as the attribute ``self.model`` so that other methods may access it.

Now, implement the following scaffolding methods for the new class:

1. :meth:`~flask_admin.model.BaseModelView.get_pk_value`

    This method returns a primary key value from
    the model instance. In the SQLAlchemy backend, it gets the primary key from the model
    using :meth:`~flask_admin.contrib.sqla.ModelView.scaffold_pk`, caches it
    and then returns the value from the model whenever requested.

    For example::

        class MyDbModel(BaseModelView):
            def get_pk_value(self, model):
                return self.model.id

2. :meth:`~flask_admin.model.BaseModelView.scaffold_list_columns`

    Returns a list of columns to be displayed in a list view. For example::

        class MyDbModel(BaseModelView):
            def scaffold_list_columns(self):
                columns = []

                for p in dir(self.model):
                    attr = getattr(self.model, p)
                    if isinstance(attr, MyDbColumn):
                        columns.append(p)

                return columns

3. :meth:`~flask_admin.model.BaseModelView.scaffold_sortable_columns`

    Returns a dictionary of sortable columns. The keys in the dictionary should correspond to the model's
    field names. The values should be those variables that will be used for sorting.

    For example, in the SQLAlchemy backend it is possible to sort by a foreign key field. So, if there is a
    field named `user`, which is a foreign key for the `Users` table, and the `Users` table also has a name
    field, then the key will be `user` and value will be `Users.name`.

    If your backend does not support sorting, return
    `None` or an empty dictionary.

4. :meth:`~flask_admin.model.BaseModelView.init_search`

    Initialize search functionality. If your backend supports
    full-text search, do initializations and return `True`.
    If your backend does not support full-text search, return
    `False`.

    For example, SQLAlchemy backend reads value of the `self.searchable_columns` and verifies if all fields are of
    text type, if they're local to the current model (if not,
    it will add a join, etc) and caches this information for
    future use.

5. :meth:`~flask_admin.model.BaseModelView.scaffold_form`

    Generate `WTForms` form class from the model.

    For example::

        class MyDbModel(BaseModelView):
            def scaffold_form(self):
                class MyForm(Form):
                    pass

                # Do something
                return MyForm

6. :meth:`~flask_admin.model.BaseModelView.get_list`

    This method should return list of model instances with paging,
    sorting, etc applied.

    For SQLAlchemy backend it looks like:

        1. If search was enabled and provided search value is not empty,
           generate LIKE statements for each field from `self.searchable_columns`

        2. If filter values were passed, call `apply` method
           with values::

                for flt, value in filters:
                    query = self._filters[flt].apply(query, value)

        3. Execute query to get total number of rows in the
           database (count)

        4. If `sort_column` was passed, will do something like (with some extra FK logic which is omitted in this example)::

                if sort_desc:
                    query = query.order_by(desc(sort_field))
                else:
                    query = query.order_by(sort_field)

        5. Apply paging

        6. Return count, list as a tuple

7. :meth:`~flask_admin.model.BaseModelView.get_one`

    Return a model instance by its primary key.

8. :meth:`~flask_admin.model.BaseModelView.create_model`

    Create a new instance of the model from the `Form` object.

9. :meth:`~flask_admin.model.BaseModelView.update_model`

    Update the model instance with data from the form.

10. :meth:`~flask_admin.model.BaseModelView.delete_model`

    Delete the specified model instance from the data store.

11. :meth:`~flask_admin.model.BaseModelView.is_valid_filter`

    Verify whether the given object is a valid filter.

12. :meth:`~flask_admin.model.BaseModelView.scaffold_filters`

    Return a list of filter objects for one model field.

    This method will be called once for each entry in the
    `self.column_filters` setting.

    If your backend does not know how to generate filters
    for the provided field, it should return `None`.

    For example::

        class MyDbModel(BaseModelView):
            def scaffold_filters(self, name):
                attr = getattr(self.model, name)

                if isinstance(attr, MyDbTextField):
                    return [MyEqualFilter(name, name)]

Implementing filters

text
Each model backend should have its own set of filter implementations. It is not possible to use the
filters from SQLAlchemy models in a non-SQLAlchemy backend.
This also means that different backends might have different set of available filters.

The filter is a class derived from :class:`~flask_admin.model.filters.BaseFilter` which implements at least two methods:

    1. :meth:`~flask_admin.model.filters.BaseFilter.apply`
    2. :meth:`~flask_admin.model.filters.BaseFilter.operation`

`apply` method accepts two parameters: `query` object and a value from the client. Here you can add
filtering logic for the filter type.

Lets take SQLAlchemy model backend as an example:

All SQLAlchemy filters derive from :class:`~flask_admin.contrib.sqla.filters.BaseSQLAFilter` class.

Each filter implements one simple filter SQL operation (like, not like, greater, etc) and accepts a column as
input parameter.

Whenever model view wants to apply a filter to a query
object, it will call `apply` method in a filter class
with a query and value. Filter will then apply
real filter operation.

For example::

    class MyBaseFilter(BaseFilter):
        def __init__(self, column, name, options=None, data_type=None):
            super(MyBaseFilter, self).__init__(name, options, data_type)

            self.column = column

    class MyEqualFilter(MyBaseFilter):
        def apply(self, query, value):
            return query.filter(self.column == value)

        def operation(self):
            return gettext('equals')

        # You can validate values. If value is not valid,
        # return `False`, so filter will be ignored.
        def validate(self, value):
            return True

        # You can "clean" values before they will be
        # passed to the your data access layer
        def clean(self, value):
            return value

Feel free ask questions if you have problems adding a new model backend.
Also, if you get stuck, try taking a look at the SQLAlchemy model backend and use it as a reference.


Doc Advanced (`doc/advanced.rst`)

:tocdepth: 2

Advanced Functionality

Enabling CSRF Protection

To add CSRF protection to the forms that are generated by ModelView instances, use the
SecureForm class in your ModelView subclass by specifying the form_base_class parameter::

text
from flask_admin.form import SecureForm
from flask_admin.contrib.sqla import ModelView

class CarAdmin(ModelView):
    form_base_class = SecureForm

SecureForm requires WTForms 2 or greater. It uses the WTForms SessionCSRF class
to generate and validate the tokens for you when the forms are submitted.

CSP support

To support CSP <https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html>_
in Flask-Admin, you can pass a csp_nonce_generator function through to Flask-Admin on
initialisation. This function should return a CSP nonce that will be attached to all
<script> and <style> resources. You are responsible for making sure that your Flask
responses include an appropriate 'Content-Security-Policy` header that also includes the
same nonce value.

We recommend using Flask-Talisman <https://pypi.org/project/flask-talisman/>_. Here's an example
of how to configure Flask-Admin to inject CSP nonce values::

text
app = Flask(__name__)

talisman = Talisman(
    app,
    content_security_policy={
        "default-src": "'self'",
    },
    content_security_policy_nonce_in=["script-src", "style-src"]
)
csp_nonce_generator = app.jinja_env.globals["csp_nonce"]  # this is talisman's generator function

admin = admin.Admin(app, name="Example", theme=Bootstrap4Theme(), csp_nonce_generator=csp_nonce_generator)

If you decide to use a content security policy, you should pay close attention to the policy you set to
make sure it is appropriate for your project's security needs.

If you create any of your own templates for Flask-Admin pages, you will need to inject the CSP nonces yourself as appropriate.

Adding Custom Javascript and CSS

To add custom JavaScript or CSS in your ModelView use extra_js or extra_css parameters::

text
class MyModelView(ModelView):
    extra_js = ['https://example.com/custom.js']
    extra_css = ['https://example.com/custom.css']

Localization With Flask-Babel

Flask-Admin comes with translations for several languages.
Enabling localization is simple:

#. Install Flask-Babel <https://github.com/python-babel/flask-babel/>_ to do the heavy
lifting.

text
pip install flask-babel

#. Create a locale selector function::

text
def get_locale():
        if request.args.get('lang'):
            session['lang'] = request.args.get('lang')
        return session.get('lang', 'en')

#. Initialize Flask-Babel by creating instance of Babel class::

text
from flask import Flask
    from flask_babel import Babel

    app = Flask(__name__)
    babel = Babel(app, locale_selector=get_locale)

Now, you could try a French version of the application at: http://localhost:5000/admin/?lang=fr <http://localhost:5000/admin/?lang=fr>_.

Go ahead and add your own logic to the locale selector function. The application can store locale in
a user profile, cookie, session, etc. It can also use the Accept-Language
header to make the selection automatically.

If the built-in translations are not enough, look at the Flask-Babel documentation <https://python-babel.github.io/flask-babel/>_
to see how you can add your own.

Using with Flask in `host_matching` mode

If Flask is configured with host_matching enabled, then all routes registered on the app need to know which host(s) they should be served for.

This requires some additional explicit configuration for Flask-Admin by passing the host argument to Admin() calls.

#. With your Flask app initialised::

text
from flask import Flask
    app = Flask(__name__, host='my.domain.com', static_host='static.domain.com')

Serving Flask-Admin on a single, explicit host


Construct your Admin instance(s) and pass the desired host for the admin instance::

text
class AdminView(admin.BaseView):
        @admin.expose('/')
        def index(self):
            return self.render('template.html')

    admin1 = admin.Admin(app, url='/admin', host='admin.domain.com')
    admin1.add_view(AdminView())

Flask's url_for calls will work without any additional configuration/information::

text
url_for('admin.index', _external=True) == 'http://admin.domain.com/admin')

Serving Flask-Admin on all hosts


Pass a wildcard to the host parameter to serve the admin instance on all hosts::

text
class AdminView(admin.BaseView):
        @admin.expose('/')
        def index(self):
            return self.render('template.html')

    admin1 = admin.Admin(app, url='/admin', host='*')
    admin1.add_view(AdminView())

If you need to generate URLs for a wildcard admin instance, you will need to pass admin_routes_host to the url_for call::

text
url_for('admin.index', admin_routes_host='admin.domain.com', _external=True) == 'http://admin.domain.com/admin')
    url_for('admin.index', admin_routes_host='admin2.domain.com', _external=True) == 'http://admin2.domain.com/admin')

.. _file-admin:

Managing Files & Folders

To manage static files instead of database records, Flask-Admin comes with
the FileAdmin plug-in. It gives you the ability to upload, delete, rename, etc. You
can use it by adding a FileAdmin view to your app::

text
from flask_admin.contrib.fileadmin import FileAdmin

import os.path as op

# Flask setup here

admin = Admin(app, name='microblog', theme=Bootstrap4Theme())

path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files'))

FileAdmin also has out-of-the-box support for managing files located on a Amazon Simple Storage Service
bucket using a boto3 client <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/core/session.html#boto3.session.Session.client>_. To add it to your app::

text
from flask_admin import Admin
from flask_admin.contrib.fileadmin.s3 import S3FileAdmin

admin = Admin()

admin.add_view(S3FileAdmin(boto3.client('s3'), 'files_bucket'))

Likewise, it supports Azure storage::

text
from flask_admin import Admin
from flask_admin.contrib.fileadmin.azure import AzureFileAdmin
from azure.storage.blob import BlobServiceClient

admin = Admin()

connection_string = "<your-connection-string>"

client = BlobServiceClient.from_connection_string(
    connection_string, api_version="2019-12-12"
)

admin.add_view(
    AzureFileAdmin(
        blob_service_client=client,
        container_name="<container-name>",
        on_windows=False
    )
)

Notice the on_windows parameter in the above example. This is required to handle differences in path
separators between Windows and Unix-based operating systems. Set it to True if your Storage is
running on a Windows server. However, most of Azure storage services are operating-system agnostic,
so you can usually leave it to False.

You can disable uploads, disable file deletion, restrict file uploads to certain types, etc.
Check :mod:flask_admin.contrib.fileadmin in the API documentation for more details.

Adding new file backends


You can also implement your own storage backend by creating a class that implements the same
methods defined in the LocalFileStorage class. Check :mod:flask_admin.contrib.fileadmin in the
API documentation for details on the methods.

Adding A Redis Console

Another plug-in that's available is the Redis Console. If you have a Redis
instance running on the same machine as your app, you can::

text
from redis import Redis
from flask_admin.contrib import rediscli

# Flask setup here

admin = Admin(app, name='microblog', theme=Bootstrap4Theme())

admin.add_view(rediscli.RedisCli(Redis()))

Replacing Individual Form Fields

The form_overrides attribute allows you to replace individual fields within a form.
A common use-case for this would be to add a What-You-See-Is-What-You-Get (WYSIWIG) editor, or to handle
file / image uploads that need to be tied to a field in your model.

WYSIWIG Text Fields


To handle complicated text content, you can use
CKEditor <https://ckeditor.com/>_ by subclassing some of the built-in WTForms
classes as follows::

text
from wtforms import TextAreaField
from wtforms.widgets import TextArea

class CKTextAreaWidget(TextArea):
    def __call__(self, field, **kwargs):
        if kwargs.get('class'):
            kwargs['class'] += ' ckeditor'
        else:
            kwargs.setdefault('class', 'ckeditor')
        return super(CKTextAreaWidget, self).__call__(field, **kwargs)

class CKTextAreaField(TextAreaField):
    widget = CKTextAreaWidget()

class MessageAdmin(ModelView):
    extra_js = ['//cdn.ckeditor.com/4.6.0/standard/ckeditor.js']

    form_overrides = {
        'body': CKTextAreaField
    }

File & Image Fields


Flask-Admin comes with a built-in :meth:~flask_admin.form.upload.FileUploadField
and :meth:~flask_admin.form.upload.ImageUploadField. To make use
of them, you'll need to specify an upload directory and add them to the forms in question.
Image handling also requires you to have Pillow <https://pypi.python.org/pypi/Pillow/2.8.2>_
installed if you need to do any processing on the image files.

Have a look at the example at
https://github.com/pallets-eco/flask-admin/tree/master/examples/forms-files-images.

If you just want to manage static files in a directory, without tying them to a database model, then
use the :ref:File-Admin<file-admin> plug-in.

Managing Geographical Models

If you want to store spatial information in a GIS database, Flask-Admin has
you covered. The GeoAlchemy backend extends the SQLAlchemy backend (just as
GeoAlchemy <https://geoalchemy-2.readthedocs.io/>_ extends SQLAlchemy) to give you a pretty and functional map-based
editor for your admin pages.

Some notable features include:

  • Maps are displayed using the amazing Leaflet <https://leafletjs.com/>_ Javascript library,
    with map data from Mapbox <https://www.mapbox.com>_.
  • Geographic information, including points, lines and polygons, can be edited
    interactively using Leaflet.Draw <https://github.com/Leaflet/Leaflet.draw>_.
  • Graceful fallback: GeoJSON <https://geojson.org/>_ data can be edited in a <textarea>, if the
    user has turned off Javascript.
  • Works with a Geometry <https://geoalchemy-2.readthedocs.io/en/latest/types.html#geoalchemy2.types.Geometry>_ SQL field that is integrated with Shapely <https://shapely.readthedocs.io/>_ objects.

To get started, define some fields on your model using GeoAlchemy's Geometry
field. Next, add model views to your interface using the ModelView class
from the GeoAlchemy backend, rather than the usual SQLAlchemy backend::

text
from geoalchemy2 import Geometry
from flask_admin.contrib.geoa import ModelView

# .. flask initialization
db = SQLAlchemy()
db.init_app(app)

class Location(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    point = db.Column(Geometry("POINT"))

Some of the Geometry field types that are available include:
"POINT", "MULTIPOINT", "POLYGON", "MULTIPOLYGON", "LINESTRING" and "MULTILINESTRING".

Have a look at https://github.com/pallets-eco/flask-admin/tree/master/examples/geo-alchemy
to get started.

.. _display-map-widgets:

Display map widgets


Flask-Admin uses Leaflet <https://leafletjs.com/>_ to display map widgets for
geographical data. By default, this uses MapBox <https://www.mapbox.com>_.

To have MapBox data display correctly, you'll have to sign up for an account and include
some credentials in your application's config::

text
app = Flask(__name__)
app.config['FLASK_ADMIN_MAPS'] = True

# Required: configure the default centre position for blank maps
app.config['FLASK_ADMIN_DEFAULT_CENTER_LAT'] = -33.918861
app.config['FLASK_ADMIN_DEFAULT_CENTER_LONG'] = 18.423300

# Required if using the default Mapbox integration
app.config['FLASK_ADMIN_MAPBOX_MAP_ID'] = "example.abc123"
app.config['FLASK_ADMIN_MAPBOX_ACCESS_TOKEN'] = "pk.def456"

If you want to use a map provider other than MapBox (eg OpenStreetMaps), you can override
the tile layer URLs and tile attribution attributes::

text
class CityView(ModelView):
    tile_layer_url = '{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
    tile_layer_attribution = '&copy; [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors'

If you want to include a search box on map widgets for looking up locations, you need the following additional configuration::

text
app.config['FLASK_ADMIN_MAPS_SEARCH'] = True
app.config['FLASK_ADMIN_GOOGLE_MAPS_API_KEY'] = 'secret'

Flask-Admin currently only supports Google Maps for map search.

Limitations


There's currently no way to sort, filter, or search on geometric fields
in the admin. It's not clear that there's a good way to do so.
If you have any ideas or suggestions, make a pull request!

Customising Builtin Forms Via Rendering Rules

Before version 1.0.7, all model backends were rendering the create and edit forms
using a special Jinja2 macro, which was looping over the fields of a WTForms form object and displaying
them one by one. This works well, but it is difficult to customize.

Starting from version 1.0.7, Flask-Admin supports form rendering rules, to give you fine grained control of how
the forms for your modules should be displayed.

The basic idea is pretty simple: the customizable rendering rules replace a static macro, so you can tell
Flask-Admin how each form should be rendered. As an extension, however, the rendering rules also let you do a
bit more: You can use them to output HTML, call Jinja2 macros, render fields, and so on.

Essentially, form rendering rules separate the form rendering from the form definition.
For example, it no longer matters in which sequence your form fields are defined.

To start using the form rendering rules, put a list of form field names into the form_create_rules
property one of your admin views::

text
class RuleView(sqla.ModelView):
   form_create_rules = ['email', rules.Text('Foobar'), 'first_name', 'last_name']

In this example, only three fields will be rendered and email field will be above other two fields.

Whenever Flask-Admin sees a string value in form_create_rules, it automatically assumes that it is a
form field reference and creates a :class:flask_admin.form.rules.Field class instance for that field.

Let's say we want to display some text between the email and first_name fields. This can be accomplished by
using the :class:flask_admin.form.rules.Text class::

text
from flask_admin.form import rules

class RuleView(sqla.ModelView):
    form_create_rules = ('email', rules.Text('Foobar'), 'first_name', 'last_name')

Built-in Rules


Flask-Admin comes with few built-in rules that can be found in the :mod:flask_admin.form.rules module:

================================================= ==================================================== ======================
Form Rendering Rule Description Snippet
================================================= ==================================================== ======================
:class:flask_admin.form.rules.BaseRule All rules derive from this class
rules.Text('Foobar')
:class:flask_admin.form.rules.NestedRule Allows rule nesting, useful for HTML containers
:class:flask_admin.form.rules.Text Simple text rendering rule rules.Text('Foobar')
:class:flask_admin.form.rules.HTML Same as Text rule, but does not escape the text rules.HTML('<hr>')
:class:flask_admin.form.rules.Macro Calls macro from current Jinja2 context rules.Macro('my_macro', arg1=1, arg2=2)
:class:flask_admin.form.rules.Container Wraps a child rule into container rendered by
macro, where the macro should be defined in the
current Jinja2 context containing {{caller()}}
. read more in the next section.
:class:flask_admin.form.rules.Field Renders single form field, helpful when it is used rules.Field('email')
with NestedRule
:class:flask_admin.form.rules.Header Renders form header rules.Header('Posts')
:class:flask_admin.form.rules.FieldSet Renders form header and child rules rules.FieldSet(('phone_number', 'email'), 'Contacts:')
:class:flask_admin.form.rules.Row Renders child rules in a single row rules.Row(('dialing_code', 'phone_number'), 'Contacts:')
:class:flask_admin.form.rules.Group Bootstrap Input field in a group style. It rules.Group('dialling_code', prepend='+')
renders the target field whatever its type
is (text, radio, checkbox..etc), allowing a
prepended and appended boxes to be filled
with any HTML content.
================================================= ==================================================== ======================

Rules and Macros
Each one of the Macro and Container rules needs a macros to be deifined
in advance. As an example of Container rule, Let's say we want to warp some fields winthin a
Bootstrap Card, then we can define a Jinja Macro
that includes caller() which is a functoin to be placed where we want to render the contained rule.
We can extend the create.html as the following::

text
{% extends 'admin/model/create.html' %}

{% macro wrap_in_card( ) %}


    
        <h5>Some Fields </h5>
    
    
        
        {{ caller() }}
    


{% endmacro %}

Now the wrap_in_card macro can be passed to the container rule like: ::

text
from flask_admin.form import rules

class RuleView(sqla.ModelView):
    create_template = 'admin/create.html'
    form_create_rules = [
        rules.Container('wrap_in_card',
            rules.NestedRule( separator='<hr>', rules= [
                rules.Field('field1'),
                rules.Field('field2'),
                rules.Field('field3'),
            ]),
        )
]

.. _database-backends:

Using Different Database Backends

Other than SQLAlchemy... There are five different backends for you to choose
from, depending on which database you would like to use for your application. If, however, you need
to implement your own database backend, have a look at :ref:adding-model-backend.

If you don't know where to start, but you're familiar with relational databases, then you should probably look at using
SQLAlchemy. It is a full-featured toolkit, with support for SQLite, PostgreSQL, MySQL,
Oracle and MS-SQL amongst others. It really comes into its own once you have lots of data, and a fair amount of
relations between your data models. If you want to track spatial data like latitude/longitude
points, you should look into GeoAlchemy
, as well.

SQLAlchemy


Notable features:

  • SQLAlchemy 0.6+ support
  • Paging, sorting, filters
  • Proper model relationship handling
  • Inline editing of related models

Multiple Primary Keys

Flask-Admin has limited support for models with multiple primary keys. It only covers specific case when
all but one primary keys are foreign keys to another model. For example, model inheritance following
this convention.

Let's Model a car with its tyres::

text
class Car(db.Model):
    __tablename__ = 'cars'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    desc = db.Column(db.String(50))

    def __unicode__(self):
        return self.desc

class Tyre(db.Model):
    __tablename__ = 'tyres'
    car_id = db.Column(db.Integer, db.ForeignKey('cars.id'), primary_key=True)
    tyre_id = db.Column(db.Integer, primary_key=True)
    car = db.relationship('Car', backref='tyres')
    desc = db.Column(db.String(50))

A specific tyre is identified by using the two primary key columns of the Tyre class, of which the car_id key
is itself a foreign key to the class Car.

To be able to CRUD the Tyre class, you need to enumerate columns when defining the AdminView::

text
class TyreAdmin(sqla.ModelView):
    form_columns = ['car', 'tyre_id', 'desc']

The form_columns needs to be explicit, as per default only one primary key is displayed.

When having multiple primary keys, no validation for uniqueness prior to saving of the object will be done. Saving
a model that violates a unique-constraint leads to an Sqlalchemy-Integrity-Error. In this case, Flask-Admin displays
a proper error message and you can change the data in the form. When the application has been started with debug=True
the werkzeug debugger will catch the exception and will display the stacktrace.

Peewee


Features:

  • Peewee 2.x+ support;
  • Paging, sorting, filters, etc;
  • Inline editing of related models;

In order to use peewee integration, you need to install two additional Python
packages: peewee <https://docs.peewee-orm.com/>_ and wtf-peewee <https://github.com/coleifer/wtf-peewee/>_.

Known issues:

  • Many-to-Many model relations are not supported: there's no built-in way to express M2M relation in Peewee

For more, check the :class:~flask_admin.contrib.peewee API documentation. Or look at
the Peewee example at https://github.com/pallets-eco/flask-admin/tree/master/examples/peewee.

PyMongo


The bare minimum you have to provide for Flask-Admin to work with PyMongo:

  1. A list of columns by setting column_list property
  2. Provide form to use by setting form property
  3. When instantiating :class:flask_admin.contrib.pymongo.ModelView class, you have to provide PyMongo collection object

This is minimal PyMongo view::

class UserForm(Form):
name = StringField('Name')
email = StringField('Email')

class UserView(ModelView):
column_list = ('name', 'email')
form = UserForm

if name == 'main':
admin = Admin(app)

text
# 'db' is PyMongo database object
  admin.add_view(UserView(db['users']))

On top of that you can add sortable columns, filters, text search, etc.

For more, check the :class:~flask_admin.contrib.pymongo API documentation. Or look at
the pymongo example at https://github.com/pallets-eco/flask-admin/tree/master/examples/pymongo.

MongoEngine


The bare minimum you have to provide for Flask-Admin to work with MongoEngine:

  1. A list of columns by setting column_list property
  2. Provide form to use by setting form property
  3. When instantiating :class:flask_admin.contrib.mongoengine.ModelView class, you have to provide MongoEngine Document object

This is the minimal MongoEngine view::

from mongoengine import Document
from mongoengine import StringField
from mongoengine.connection import get_db
from wtforms import fields
from wtforms import form

from flask_admin.contrib.mongoengine import ModelView

class User(Document):
name = StringField()
email = StringField()

class UserForm(Form):
name = StringField('Name')
email = StringField('Email')

class UserView(ModelView):
column_list = ('name', 'email')
form = UserForm

if name == 'main':
admin = Admin(app)
admin.add_view(UserView(User))

On top of that you can add sortable columns, filters, text search, etc.

For more, check the :class:~flask_admin.contrib.mongoengine API documentation. Or look at
the mongoengine example at https://github.com/pallets-eco/flask-admin/tree/master/examples/mongoengine.

Migrating From Django

If you are used to Django <https://www.djangoproject.com/>_ and the django-admin package, you will find
Flask-Admin to work slightly different from what you would expect.

Design Philosophy


In general, Django and django-admin strives to make life easier by implementing sensible defaults. So a developer
will be able to get an application up in no time, but it will have to conform to most of the defaults. Of course it
is possible to customize things, but this often requires a good understanding of what's going on behind the scenes,
and it can be rather tricky and time-consuming.

The design philosophy behind Flask is slightly different. It embraces the diversity that one tends to find in web
applications by not forcing design decisions onto the developer. Rather than making it very easy to build an
application that almost solves your whole problem, and then letting you figure out the last bit, Flask aims to make it
possible for you to build the whole application. It might take a little more effort to get started, but once you've
got the hang of it, the sky is the limit... Even when your application is a little different from most other
applications out there on the web.

Flask-Admin follows this same design philosophy. So even though it provides you with several tools for getting up &
running quickly, it will be up to you, as a developer, to tell Flask-Admin what should be displayed and how. Even
though it is easy to get started with a simple CRUD <https://en.wikipedia.org/wiki/Create,_read,_update_and_delete>_
interface for each model in your application, Flask-Admin doesn't fix you to this approach, and you are free to
define other ways of interacting with some, or all, of your models.

Due to Flask-Admin supporting more than one ORM (SQLAlchemy, Peewee, raw pymongo, mongoengine), the developer is even
free to mix different model types into one application by instantiating appropriate CRUD classes.

Here is a list of some of the configuration properties that are made available by Flask-Admin and the
SQLAlchemy backend. You can also see which django-admin properties they correspond to:

=========================================== ==============================================
Django Flask-Admin
=========================================== ==============================================
actions :attr:~flask_admin.actions
exclude :attr:~flask_admin.model.BaseModelView.form_excluded_columns
fields :attr:~flask_admin.model.BaseModelView.form_columns
form :attr:~flask_admin.model.BaseModelView.form
formfield_overrides :attr:~flask_admin.model.BaseModelView.form_args
inlines :attr:~flask_admin.contrib.sqla.ModelView.inline_models
list_display :attr:~flask_admin.model.BaseModelView.column_list
list_filter :attr:~flask_admin.contrib.sqla.ModelView.column_filters
list_per_page :attr:~flask_admin.model.BaseModelView.page_size
search_fields :attr:~flask_admin.model.BaseModelView.column_searchable_list
add_form_template :attr:~flask_admin.model.BaseModelView.create_template
change_form_template :attr:~flask_admin.model.BaseModelView.change_form_template
=========================================== ==============================================

You might want to check :class:~flask_admin.model.BaseModelView for basic model configuration options (reused by all model
backends) and specific backend documentation, for example
:class:~flask_admin.contrib.sqla.ModelView. There's much more
than what is displayed in this table.

Overriding the Form Scaffolding

If you don't want to the use the built-in Flask-Admin form scaffolding logic, you are free to roll your own
by simply overriding :meth:~flask_admin.model.base.scaffold_form. For example, if you use
WTForms-Alchemy <https://github.com/kvesteri/wtforms-alchemy>_, you could put your form generation code
into a scaffold_form method in your ModelView class.

For SQLAlchemy, if the synonym_property does not return a SQLAlchemy field, then Flask-Admin won't be able to figure out what to
do with it, so it won't generate a form field. In this case, you would need to manually contribute your own field::

text
class MyView(ModelView):
    def scaffold_form(self):
        form_class = super(UserView, self).scaffold_form()
        form_class.extra = StringField('Extra')
        return form_class

Customizing Batch Actions

If you want to add other batch actions to the list view, besides the default delete action,
then you can define a function that implements the desired logic and wrap it with the @action decorator.

The action decorator takes three parameters: name, text and confirmation.
While the wrapped function should accept only one parameter - ids::

text
from flask_admin.actions import action

class UserView(ModelView):
    @action('approve', 'Approve', 'Are you sure you want to approve selected users?')
    def action_approve(self, ids):
        try:
            query = User.query.filter(User.id.in_(ids))

            count = 0
            for user in query.all():
                if user.approve():
                    count += 1

            flash(ngettext('User was successfully approved.',
                           '%(count)s users were successfully approved.',
                           count,
                           count=count))
        except Exception as ex:
            if not self.handle_view_exception(ex):
                raise

            flash(gettext('Failed to approve users. %(error)s', error=str(ex)), 'error')

.. _raise-exceptions-instead-of-flash:

Raise exceptions instead of flash error messages

By default, Flask-Admin will capture most exceptions related to reading/writing models
and display a flash message instead of raising an exception. If your Flask app is running
in debug mode (ie under local development), exceptions will not be suppressed.

The flash message behaviour can be overridden with some Flask configuration.::

text
app = Flask(__name__)
app.config['FLASK_ADMIN_RAISE_ON_VIEW_EXCEPTION'] = True
app.config['FLASK_ADMIN_RAISE_ON_INTEGRITY_ERROR'] = True

FLASK_ADMIN_RAISE_ON_VIEW_EXCEPTION


Instead of turning exceptions on model create/update/delete actions into flash messages,
raise the exception as normal. You should expect the view to return a 500 to the user,
unless you add specific handling to prevent this.

FLASK_ADMIN_RAISE_ON_INTEGRITY_ERROR


This targets SQLAlchemy specifically.

Unlike the previous setting, this will specifically only affect the behaviour of
IntegrityErrors. These usually come from violations on constraints in the database,
for example trying to insert a row with a primary key that already exists.

Adding a favicon to the admin page


Adding a favicon to flask-admin is easy: just save a .ico file and add a /favicon.ico
route to your flask app.::

text
from flask import redirect, url_for
@app.route("/favicon.ico")
def favicon():
    return redirect(url_for("static", filename="favicon.ico"))

Doc Changelog (`doc/changelog.rst`)

Changelog

[unreleased]

Bugfixes:

  • SQLAlchemy backend: ItemsRequired once again validates multi-select relation fields without raising AttributeError. Inline entries marked for deletion remain excluded from the count (closes #2385).

  • Peewee backend: return None from ajax get_one() when no record matches rather than raising DoesNotExist

Dev:

  • SQLAlchemy backend: add regression coverage for form_columns ordering when form_extra_fields replaces a model field, while retaining coverage for model-attribute column specifications (#2965).

  • Support building with flit_core v4: use the PEP 621 authors key instead of the invalid singular author in pyproject.toml, and widen the build-system pin from flit_core<4 to flit_core<5 (closes #2956).

2.2.1

Bugfixes:

  • BaseTimeBetweenFilter.validate() now returns False on invalid input instead of raising an exception.
  • Fix encoding for editing file in FileAdmin. Now it uses UTF-8 and accepts non-ASCII characters.
  • SQLAlchemy backend: conv_ARRAY now infers the array element's python_type and passes it through as the Select2TagsField coerce callable. Saving a Postgres ARRAY(Integer) / ARRAY(Float) column no longer fails with column "x" is of type integer[] but expression is of type text[] (closes #1724).
  • Fix sorting arrow direction in admin list view. Now it reflects the current sorting state (closes #2933).
  • MongoEngine fileadmin backend: downloading GridFS files now preserves their name and extension instead of saving them as file (closes #2916).
  • MongoEngine backend: stop reading the deprecated GridOut.contentType property. File downloads and list/form widgets now resolve the MIME type via metadata["content_type"], the GridFS document directly (for legacy data), or filename-based guessing (closes #2920).
  • MongoEngine backend: QueryAjaxModelLoader.format now handles unresolved DBRef values gracefully instead of crashing with AttributeError: 'DBRef' object has no attribute 'pk' (which surfaced as HTTP 500 from the autocomplete endpoint when a ReferenceField target was deleted). Broken references are shown as (missing: <collection>/) so users can clear them (closes #2917).
  • Peewee backend: inline model forms now support form_extra_fields when the child form is autogenerated. Previously, inline forms ignored these fields because they bypassed get_form() and called model_form() directly.
  • SQLAlchemy backend: the Unique validator now resolves its session when it runs instead of capturing the session that was active when the form was scaffolded. With flask-sqlalchemy-lite, forms scaffolded inside a startup app_context() previously kept querying that context's Session, which was never committed, rolled back or closed, so one connection per process sat "idle in transaction" after the first save of a form with a unique column (and failed permanently with PendingRollbackError once the server dropped it). Both ModelView(Model, db) and the deprecated ModelView(Model, db.session) continue to work (relates to #2831).
  • Peewee backend: ModelView.get_one() now returns None when no record matches, instead of letting peewee's DoesNotExist propagate. Requesting the details, edit or delete view for an absent or malformed id no longer returns HTTP 500; it flashes "Record does not exist." and redirects, matching the other backends (closes #2924).

Type hints:

  • Type hints added to all functions and methods (some still use typing.Any where full typing not yet available)
  • Updated InlineBaseFormAdmin.get_form() to return type[BaseForm] | None rather than T_MODEL_VIEW | None
  • Minor type hinting improvements for peewee backend
  • Admin(index_view=...) now accepts a BaseView instance rather than AdminIndexView, allowing e.g. FileAdmin

2.2.0

New Features:

  • SQLAlchemy filter classes (FilterEqual, FilterLike, etc.) now accept a dotted-path string for the column argument (e.g. FilterEqual(column="author.email", name="Author Email")); the path is resolved against the view's model and the necessary joins are added automatically.

Bugfixes:

  • Fix a bug in v2.1.0 that caused UnboundLocalError in flask_admin.contrib.sqla.ModelView when an exception was raised during a create or update operation

2.1.0

Deprecations:

New Features:

Bugfixes:

Others:

  • Improved examples, tests, type hints

2.0.2

2.0.1

  • Fix documentation
  • Update arabic translations

2.0.0

Breaking changes:

  • Removed support for Python <3.10
  • Use of the boto library has been replaced by boto3. S3FileAdmin and S3Storage now accept an s3_client parameter taking a boto3.client('s3') instance rather than aws_access_key_id, aws_secret_access_key, and region parameters.
  • Azure Blob Storage SDK has been upgraded from the legacy version (v2) to the latest version (v12). AzureFileAdmin now accept blob_service_client rather than connection_string to give more flexibility with connection types.
  • Flask-BabelEx is no longer supported; the package is no longer maintained and Flask-Babel is recommended/active instead.
  • Mongoengine support in Flask-Admin no longer uses Flask-Mongoengine, as that library is no longer actively maintained.
  • Bootstrap2 and Bootstrap3 themes are no longer available.
  • Admin() now takes a theme parameter that encapsulates all of the configuration options for theming the admin instance. This replaces the template_mode parameter.
  • All remaining Flask-Admin config has been namespaced under FLASK_ADMIN_.

.. list-table:: Configuration Changes
:widths: 50 50
:header-rows: 1

    • Config variable name
    • What's changed
    • FLASK_ADMIN_SWATCH
    • Removed; use Theme(swatch=...) instead
    • FLASK_ADMIN_FLUID_LAYOUT
    • Removed; use Theme(fluid=...) instead
    • MAPBOX_MAP_ID
    • Renamed to FLASK_ADMIN_MAPBOX_MAP_ID
    • MAPBOX_SEARCH
    • Renamed to FLASK_ADMIN_MAPBOX_SEARCH
    • MAPBOX_ACCESS_TOKEN
    • Renamed to FLASK_ADMIN_MAPBOX_ACCESS_TOKEN
    • GOOGLE_MAPS_API_KEY
    • Renamed to FLASK_ADMIN_GOOGLE_MAPS_API_KEY
    • DEFAULT_CENTER_LAT
    • Renamed to FLASK_ADMIN_DEFAULT_CENTER_LAT
    • DEFAULT_CENTER_LONG
    • Renamed to FLASK_ADMIN_DEFAULT_CENTER_LONG
    • ADMIN_RAISE_ON_INTEGRITY_ERROR
    • Renamed to FLASK_ADMIN_RAISE_ON_INTEGRITY_ERROR
    • ADMIN_RAISE_ON_VIEW_EXCEPTION
    • Renamed to FLASK_ADMIN_RAISE_ON_VIEW_EXCEPTION

New functionality:

  • Added support for Python <=3.13
  • The default query for pymongo can be overridden
  • Flask async routes are supported
  • Flask-Admin now supports the host_matching mode of Flask apps. See documentation for how to configure this where needed.
  • Flask-Admin is now compatible with SQLAlchemy v2+, Flask v3+, WTForms v3+, and Pillow v10+.
  • Flask-Admin now declares its dependencies and supported dependency versions more cleanly, including using pip extras. If you use Flask-Admin with SQLAlchemy, for example, you should use pip install flask-admin[sqlalchemy] or list flask-admin[sqlalchemy] in your requirements.txt or pyproject.toml files.
  • Apps using content security policies to restrict the assets that can be loaded can now whitelist Flask-Admin's assets by passing a csp_nonce_generator function to the Admin instance. See examples or documentation for how to configure this where needed.
  • page_size_options can now be configured on Admin models, to restrict the page sizes that users can select. These are now enforced properly and cannot be bypassed by URL hacking.

Fixes:

  • Bootstrap menu icons should show up properly and not duplicated
  • Redis CLI commands are now case insensitive
  • SQLAlchemy boolean filters now convert "1" and "0" to Python booleans, which fixes a type coercion issue with psycopg(3).
  • Jinja templates can now be loaded in StrictUndefined mode.
  • Remove an implicit dependency on packaging
  • Fixed an error caused by the fallback implementation of gettext() (when used in templates)
  • Fixes compatibility with WTForms 3.2+.
  • The Apply button for filters will show/hide correctly again
  • Fix translations_path attribute when Flask-Admin is used with Flask-Babel
  • Some translation updates.
  • Date fields no longer override widget if set in form_args
  • “Save and Continue Editing” button no longer discards the “return URL” (allowing to retain filters when switching back to the list)

Misc:

  • Translations and docs have been updated
  • Various type hinting improvements and fixes

1.6.1

  • SQLAlchemy 2.x support
  • General updates and bug fixes
  • Dropped WTForms 1 support

1.6.0

  • Dropped Python 2 support
  • WTForms 3.0 support
  • Various fixes

1.5.8

  • SQLAlchemy 1.4.5+ compatibility fixes
  • Redis CLI fixes

1.5.7

  • Bootstrap 4 support!
  • Added hook for custom SQLAlchemy models initializers
  • SQLAlchemy 1.4/2.0 compatibility fix

1.5.6

  • SQLAlchemy 1.3.6 compatibility fix
  • Python 3.8 support

1.5.5

  • Werkzeug 1.0 compatibility fix
  • Use fa-circle-o icon for unchecked booleans
  • A few SQLAlchemy-related bug fixes

1.5.4

  • Fix display of inline x-editable boolean fields on list view
  • Add support for several SQLAlchemy-Utils data types
  • Support searching on SQLAlchemy hybrid properties
  • Extra URL paramaters are now propagated to the next page when searching / filtering
  • Add enum34 dependency when running on legacy Python version
  • Update Mapbox API v1 URL format
  • Update jQuery and moment dependencies in templates
  • Fixed a datepicker issue, where only dates up to 2015 were showing up
  • Updated Pillow dependency version

1.5.3

  • Fixed XSS vulnerability
  • Support nested categories in the navbar menu
  • SQLAlchemy
    • sort on multiple columns with column_default_sort
    • sort on related models in column_sortable_list
    • show searchable fields in search input's placeholder text
    • fix: inline model forms can now also be used for models with multiple primary keys
    • support for using mapped column_property
  • Upgrade Leaflet and Leaflet.draw plugins, used for geoalchemy integration
  • Specify minimum_input_length for ajax widget
  • Peewee: support composite keys
  • MongoEngine: when searching/filtering the input is now regarded as case-insensitive by default
  • FileAdmin
    • handle special characters in filename
    • fix a bug with listing directories on Windows
    • avoid raising an exception when unknown sort parameter is encountered
  • WTForms 3 support

1.5.2

  • Fixed XSS vulnerability
  • Fixed Peewee support
  • Added detail view column formatters
  • Updated Flask-Login example to work with the newer version of the library
  • Various SQLAlchemy-related fixes
  • Various Windows related fixes for the file admin

1.5.1

  • Dropped Python 2.6 support
  • Fixed SQLAlchemy >= 1.2 compatibility
  • Fixed Pewee 3.0 compatibility
  • Fixed max year for a combo date inline editor
  • Lots of small bug fixes

1.5.0

  • Fixed CSRF generation logic for multi-process deployments
  • Added WTForms >= 3.0 support
  • Flask-Admin would not recursively save inline models, allowing arbitrary nesting
  • Added configuration properties that allow injection of additional CSS and JS dependencies into templates without overriding them
  • SQLAlchemy backend
    • Updated hybrid property detection using new SQLAlchemy APIs
    • Added support for association proxies
    • Added support for remote hybrid properties filters
    • Added support for ARRAY column type
  • Localization-related fixes
  • MongoEngine backend is now properly formats model labels
  • Improved Google App Engine support:
    • Added TextProperty, KeyProperty and SelectField support
    • Added support for form_args, excluded_columns, page_size and after_model_update
  • Fixed URL generation with localized named filters
  • FileAdmin has Bootstrap 2 support now
  • Geoalchemy fixes
    • Use Google Places (by default) for place search
  • Updated translations
  • Bug fixes

1.4.2

  • Small bug fix release. Fixes regression that prevented usage of "virtual" columns with a custom formatter.

1.4.1

  • Official Python 3.5 support
  • Customizable row actions
  • Tablib support (exporting to XLS, XLSX, CSV, etc)
  • Updated external dependencies (jQuery, x-editable, etc)
  • Added settings that allows exceptions to be raised on view errors
  • Bug fixes

1.4.0

  • Updated and reworked documentation
  • FileAdmin went through minor refactoring and now supports remote file systems. Comes with the new, optional, AWS S3 file management interface
  • Configurable CSV export for model views
  • Added overridable URL generation logic. Allows using custom URLs with parameters for administrative views
  • Added column_display_actions to ModelView control visibility of the action column without overriding the template
  • Added support for the latest MongoEngine
  • New SecureForm base class for easier CSRF validation
  • Lots of translation-related fixes and updated translations
  • Bug fixes

1.3.0

  • New feature: Edit models in the list view in a popup
  • New feature: Read-only model details view
  • Fixed XSS in column_editable_list values
  • Improved navigation consistency in model create and edit views
  • Ability to choose page size in model list view
  • Updated client-side dependencies (jQuery, Select2, etc)
  • Updated documentation and examples
  • Updated translations
  • Bug fixes

Doc Index (`doc/index.rst`)

:tocdepth: 2

Flask-Admin
###########

Why Flask? As a micro-framework, Flask <https://flask.palletsprojects.com/>_ lets you build web services with very little overhead.
It offers freedom for you, the designer, to implement your project in a way that suits your
particular application.

Why Flask-Admin? In a world of micro-services and APIs, Flask-Admin solves
the boring problem of building an admin interface on top
of an existing data model. With little effort, it lets
you manage your web service's data through a user-friendly interface.

How does it work? The basic concept behind Flask-Admin, is that it lets you
build complicated interfaces by grouping individual views
together in classes: Each web page you see on the frontend, represents a
method on a class that has explicitly been added to the interface.

These view classes are especially helpful when they are tied to particular
database models,
because they let you group together all of the usual
Create, Read, Update, Delete (CRUD) view logic into a single, self-contained
class for each of your models.

What does it look like? Clone the GitHub repository <https://github.com/pallets-eco/flask-admin>_
and run the provided examples locally to get a feel for Flask-Admin. There are several to choose from
in the examples directory.

.. toctree::
:maxdepth: 2

introduction
advanced
adding_a_new_model_backend
api/index
changelog

Support

Python 3.10 or higher.

Indices And Tables

  • :ref:genindex
  • :ref:modindex
  • :ref:search

.. toctree::
:hidden:

api/i18n


Doc Introduction (`doc/introduction.rst`)

:tocdepth: 2

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

text
pip install flask-admin[sqlalchemy,s3,images,export,translation]

Initialization

The first step is to initialize an empty admin interface for your Flask app::

text
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
:class:~flask_admin.base.Admin for more details about all other parameters.
Alternatively, you could use the :meth:~flask_admin.base.Admin.init_app method.

If you start this application and navigate to http://localhost:5000/admin/ <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::

text
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:

text
* 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 :ref:customizing-builtin-views. For more details on the other
ORM backends that are available, see :ref:database-backends.

Adding Content to the Index Page

The first thing you'll notice when you visit http://localhost:5000/admin/ <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::

text
{% extends 'admin/master.html' %}

{% block body %}
  Hello world
{% 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.

Authorization & Permissions

When setting up an admin interface for your application, one of the first problems
you'll want to solve is how to keep unwanted users out. With Flask-Admin there
are a few different ways of approaching this.

HTTP Basic Auth

Unfortunately, there is no easy way of applying HTTP Basic Auth just to your admin
interface.

The simplest form of authentication is HTTP Basic Auth. It doesn't interfere
with your database models, and it doesn't require you to write any new view logic or
template code. So it's great for when you're deploying something that's still
under development, before you want the whole world to see it.

Have a look at Flask-BasicAuth <https://flask-basicauth.readthedocs.io/>_ to see just how
easy it is to put your whole application behind HTTP Basic Auth.

Rolling Your Own

For a more flexible solution, Flask-Admin lets you define access control rules
on each of your admin view classes by simply overriding the is_accessible method.
How you implement the logic is up to you, but if you were to use a low-level library like
Flask-Login <https://flask-login.readthedocs.io/>_, then restricting access
could be as simple as::

text
class MicroBlogModelView(sqla.ModelView):

    def is_accessible(self):
        return login.current_user.is_authenticated

    def inaccessible_callback(self, name, **kwargs):
        # redirect to login page if user doesn't have access
        return redirect(url_for('login', next=request.url))

In the navigation menu, components that are not accessible to a particular user will not be displayed
for that user. For an example of using Flask-Login with Flask-Admin, have a look
at https://github.com/pallets-eco/flask-admin/tree/master/examples/auth-flask-login.

The main drawback is that you still need to implement all of the relevant login,
registration, and account management views yourself.

Using Flask-Security

If you want a more polished solution, you could
use Flask-Security <https://flask-security-too.readthedocs.io/>_,
which is a higher-level library. It comes with lots of built-in views for doing
common things like user registration, login, email address confirmation, password resets, etc.

The only complicated bit is making the built-in Flask-Security views integrate smoothly with the
Flask-Admin templates to create a consistent user experience. To
do this, you will need to override the built-in Flask-Security templates and have them
extend the Flask-Admin base template by adding the following to the top
of each file::

text
{% extends 'admin/master.html' %}

Now, you'll need to manually pass in some context variables for the Flask-Admin
templates to render correctly when they're being called from the Flask-Security views.
Defining a security_context_processor function will take care of this for you::

text
def security_context_processor():
    return dict(
        admin_base_template=admin.theme.base_template,
        admin_view=admin.index_view,
        theme=admin.theme,
        h=admin_helpers,
    )

For a working example of using Flask-Security with Flask-Admin, have a look at
https://github.com/pallets-eco/flask-admin/tree/master/examples/auth.

The example only uses the built-in register and login views, but you could follow the same
approach for including the other views, like forgot_password, send_confirmation, etc.

.. _customizing-builtin-views:

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

text
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::

text
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 :meth:~flask_admin.model.BaseModelView. Here are
some of the most commonly used attributes:

To disable some of the CRUD operations, set any of these boolean parameters::

text
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::

text
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::

text
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::

text
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::

text
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::

text
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::

text
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::

text
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::

text
column_sortable_list = ['name', 'email', 'country']

See more details and examples of sortable columns in the API documentation
for :meth:~flask_admin.model.BaseModelView.column_sortable_list.

A value can presented in different ways by specifying a formatter function for the column::

text
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('' % m.image_url)
}

See the API documentation for :meth:~flask_admin.model.BaseModelView.column_formatters for more
details and examples of formatter functions. And for details view the column_formatters_detail
can be used::

text
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::

text
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::

text
column_type_formatters_detail = MY_DEFAULT_FORMATTERS

Controlling Rows:


Pagination is enabled by default, but you can disable it by setting::

text
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::

text
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::

text
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::

text
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 :meth:~flask_admin.model.BaseModelView.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::

text
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::

text
form_choices = {
    'title': [
        ('MR', 'Mr'),
        ('MRS', 'Mrs'),
        ('MS', 'Ms'),
        ('DR', 'Dr'),
        ('PROF', 'Prof.')
    ]
}

To remove fields from the create and edit forms::

text
form_excluded_columns = ['last_name', 'email']

To specify WTForms field arguments::

text
form_args = {
    'name': {
        'label': 'First Name',
        'validators': [required()]
    }
}

Or, to specify arguments to the WTForms widgets used to render those fields::

text
form_widget_args = {
    'description': {
        'rows': 10,
        'style': 'color: black'
    }
}

When your forms contain foreign keys, have those related models loaded via ajax, using::

text
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::

text
form_ajax_refs = {
    'active_user': QueryAjaxModelLoader('user', db.session, User,
                                 filters=["is_active=True", "id>1000"])
}

To manage related models inline::

text
inline_models = ['post', ]

These inline forms can be customized. Have a look at the API documentation for
:meth:~flask_admin.contrib.sqla.ModelView.inline_models.

Exporting:


To enable csv export of the model view with including and excluding specific columns,
data formatting options, and type-based formatters::

text
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 :attr:~flask_admin.model.BaseModelView.export_max_rows.
you can also specify the export types that are available for the model view::

text
export_types = ['csv', 'json']

Grouping Views (Menu Categories)

When adding a view, specify a value for the category parameter
to group related views together in the menu::

text
admin.add_view(UserView(User, db.session, category="Team"))
admin.add_view(ModelView(Role, db.session, category="Team"))
admin.add_view(ModelView(Permission, db.session, category="Team"))

This will create a top-level menu item named 'Team', and a drop-down containing
links to the three views.

To nest related views within these drop-downs, use the add_sub_category method::

text
admin.add_sub_category(name="Links", parent_name="Team")

To add arbitrary hyperlinks to the menu::

admin.add_link(MenuLink(name='Home Page', url='/', category='Links'))

And to add a menu divider to separate menu items in the menu::

admin.add_menu_item(MenuDivider(), target_category='Links')

Adding Your Own Views

For situations where your requirements are really specific and you struggle to meet
them with the built-in :class:~flask_admin.model.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
:class:~flask_admin.base.BaseView class and defining your own view methods. For
example, to add a page that displays some analytics data from a 3rd-party API::

text
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::

text
{% extends 'admin/master.html' %}
{% block body %}
  Here I'm going to display some data.
{% 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::

text
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:

text
``FLASK_ADMIN_MAPS``

                            Usually Works with:

                            ``FLASK_ADMIN_DEFAULT_CENTER_LAT``

                            ``FLASK_ADMIN_DEFAULT_CENTER_LONG``

                            Can be integrated with MapBox using:

                            ``FLASK_ADMIN_MAPBOX_MAP_ID``

                            ``FLASK_ADMIN_MAPBOX_ACCESS_TOKEN``

                            And can use with Google Search with:

                            ``FLASK_ADMIN_MAPS_SEARCH``

                            ``FLASK_ADMIN_GOOGLE_MAPS_API_KEY``

                            see more :ref:`display-map-widgets`

Exception handling To show exceptions on the output rather than
flash messages:

text
``FLASK_ADMIN_RAISE_ON_VIEW_EXCEPTION``

                             ``FLASK_ADMIN_RAISE_ON_INTEGRITY_ERROR``

                             ``FLASK_ADMIN_RAISE_ON_INTEGRITY_ERROR``

                             see more :ref:`raise-exceptions-instead-of-flash`

=============================== ==============================================

Note: The FLASK_ADMIN_SWATCH, FLASK_ADMIN_FLUID_LAYOUT variables are
deprecated and moved to :class:~flask_admin.theme.BootstrapTheme

Working With the Built-in Templates

Flask-Admin uses the Jinja2 <https://jinja.palletsprojects.com/>_ templating engine.

.. _extending-builtin-templates:

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

text
{% 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::

text
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::

text
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 :mod:~flask_admin.helpers module
==================== ================================

Generating URLs

To generate the URL for a specific view, use url_for with a dot prefix::

text
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::

text
# 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::

text
url_for('analytics.index')

If your view endpoint was defined like::

text
admin.add_view(CustomView(name='Analytics', endpoint='analytics'))

Doc Api I18n (`doc/api/i18n.rst`)

Internalization

See WTForms' documentation on internationalisation: https://wtforms.readthedocs.io/en/main/i18n/.


Doc Api Index (`doc/api/index.rst`)

API

.. toctree::
:maxdepth: 2

mod_base
mod_theme
mod_helpers
mod_model
mod_form
mod_form_rules
mod_form_fields
mod_form_upload
mod_tools
mod_actions

mod_contrib_sqla
mod_contrib_sqla_fields
mod_contrib_peewee
mod_contrib_pymongo
mod_contrib_mongoengine
mod_contrib_fileadmin

mod_model_template


Doc Api Mod Actions (`doc/api/mod_actions.rst`)

``flask_admin.actions``

.. automodule:: flask_admin.actions

text
.. autofunction:: action

.. autoclass:: ActionsMixin
    :members:

Doc Api Mod Base (`doc/api/mod_base.rst`)

``flask_admin.base``

.. automodule:: flask_admin.base

text
.. better to view Admin class first than BaseView class ..

Admin
-----

.. autoclass:: Admin
    :members:


Base View
---------

.. autofunction:: expose
.. autofunction:: expose_plugview

.. autoclass:: BaseView
    :members:

Default (Index) view
--------------------

.. autoclass:: AdminIndexView
    :members:

Doc Api Mod Contrib Fileadmin (`doc/api/mod_contrib_fileadmin.rst`)

``flask_admin.contrib.fileadmin``

.. automodule:: flask_admin.contrib.fileadmin

text
.. autoclass:: FileAdmin
    :members:
    :exclude-members: can_upload, can_delete, can_delete_dirs, can_mkdir, can_rename,
                      allowed_extensions, list_template, upload_template, mkdir_template,
                      rename_template, editable_extensions, edit_templat

    .. autoattribute:: can_upload
    .. autoattribute:: can_delete
    .. autoattribute:: can_delete_dirs
    .. autoattribute:: can_mkdir
    .. autoattribute:: can_rename
    .. autoattribute:: allowed_extensions
    .. autoattribute:: editable_extensions
    .. autoattribute:: list_template
    .. autoattribute:: upload_template
    .. autoattribute:: mkdir_template
    .. autoattribute:: rename_template
    .. autoattribute:: edit_template

Doc Api Mod Contrib Mongoengine (`doc/api/mod_contrib_mongoengine.rst`)

``flask_admin.contrib.mongoengine``

MongoEngine model backend implementation.

.. automodule:: flask_admin.contrib.mongoengine

text
.. autoclass:: ModelView
    :members:
    :inherited-members:
    :exclude-members: column_filters

	Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.

    .. autoattribute:: column_filters

Doc Api Mod Contrib Peewee (`doc/api/mod_contrib_peewee.rst`)

``flask_admin.contrib.peewee``

Peewee model backend implementation.

.. automodule:: flask_admin.contrib.peewee

text
.. autoclass:: ModelView
    :members:
    :inherited-members:
    :exclude-members: column_filters, filter_converter, model_form_converter,
                      inline_model_form_converter, fast_mass_delete, inline_models

    Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.

    .. autoattribute:: column_filters
    .. autoattribute:: filter_converter
    .. autoattribute:: model_form_converter
    .. autoattribute:: inline_model_form_converter
    .. autoattribute:: fast_mass_delete
    .. autoattribute:: inline_models

Doc Api Mod Contrib Pymongo (`doc/api/mod_contrib_pymongo.rst`)

``flask_admin.contrib.pymongo``

PyMongo model backend implementation.

.. automodule:: flask_admin.contrib.pymongo

text
.. autoclass:: ModelView
    :members:
    :inherited-members:
    :exclude-members: column_filters

	Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.

    .. autoattribute:: column_filters

Doc Api Mod Contrib Sqla (`doc/api/mod_contrib_sqla.rst`)

``flask_admin.contrib.sqla``

SQLAlchemy model backend implementation.

.. automodule:: flask_admin.contrib.sqla

text
.. autoclass:: ModelView
    :members:
    :inherited-members:
    :exclude-members: column_auto_select_related,
                      column_select_related_list, column_searchable_list,
                      column_filters, filter_converter, model_form_converter,
                      inline_model_form_converter, fast_mass_delete,
                      inline_models, form_choices,
                      form_optional_types

    Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.

    .. autoattribute:: column_auto_select_related
    .. autoattribute:: column_select_related_list
    .. autoattribute:: column_searchable_list
    .. autoattribute:: column_filters
    .. autoattribute:: filter_converter
    .. autoattribute:: model_form_converter
    .. autoattribute:: inline_model_form_converter
    .. autoattribute:: fast_mass_delete
    .. autoattribute:: inline_models
    .. autoattribute:: form_choices
    .. autoattribute:: form_optional_types

Doc Api Mod Contrib Sqla Fields (`doc/api/mod_contrib_sqla_fields.rst`)

``flask_admin.contrib.sqla.fields``

.. automodule:: flask_admin.contrib.sqla.fields

text
.. autoclass:: QuerySelectField
	:members:

.. autoclass:: QuerySelectMultipleField
	:members:

.. autoclass:: CheckboxListField
	:members:

Doc Api Mod Form (`doc/api/mod_form.rst`)

``flask_admin.form``

.. automodule:: flask_admin.form

text
.. autoclass:: BaseForm
    :members:

Doc Api Mod Form Fields (`doc/api/mod_form_fields.rst`)

``flask_admin.form.fields``

.. automodule:: flask_admin.form.fields

text
.. autoclass:: TimeField
	:members:

.. autoclass:: Select2Field
	:members:

.. autoclass:: Select2TagsField
	:members:

Doc Api Mod Form Rules (`doc/api/mod_form_rules.rst`)

``flask_admin.form.rules``

.. automodule:: flask_admin.form.rules

text
.. autoclass:: BaseRule

.. autoclass:: NestedRule

.. autoclass:: Text

.. autoclass:: HTML

.. autoclass:: Macro

.. autoclass:: Container

.. autoclass:: Field

.. autoclass:: Header

.. autoclass:: FieldSet

Doc Api Mod Form Upload (`doc/api/mod_form_upload.rst`)

``flask_admin.form.upload``

.. automodule:: flask_admin.form.upload

text
.. autoclass:: FileUploadField
	:class-doc-from: class
	:members: __init__

.. autoclass:: ImageUploadField
	:class-doc-from: class
	:members: __init__

.. autoclass:: FileUploadInput

.. autoclass:: ImageUploadInput

Doc Api Mod Helpers (`doc/api/mod_helpers.rst`)

``flask_admin.helpers``

.. automodule:: flask_admin.helpers

text
.. autofunction:: get_current_view

Forms

.. autofunction:: is_required_form_field
.. autofunction:: is_form_submitted
.. autofunction:: validate_form_on_submit
.. autofunction:: get_form_data
.. autofunction:: is_field_error

Jinja2 helpers

.. autofunction:: resolve_ctx
.. autofunction:: get_render_ctx

Doc Api Mod Model (`doc/api/mod_model.rst`)

``flask_admin.model``

.. automodule:: flask_admin.model

text
.. autoclass:: BaseModelView
    :members:
    :exclude-members: can_create, can_edit, can_delete, list_template, edit_template,
                      create_template, column_list, column_exclude_list, column_labels,
                      column_formatters, column_type_formatters, column_display_pk,
                      column_descriptions, column_default_sort,
                      column_sortable_list, column_searchable_list, column_filters,
                      column_choices,
                      form, form_columns, form_excluded_columns, form_args,
                      form_base_class,
                      form_overrides, action_disallowed_list,
                      form_widget_args, form_extra_fields,
                      form_ajax_refs, form_create_rules,
                      form_edit_rules,
                      page_size, can_set_page_size

    .. autoattribute:: can_create
    .. autoattribute:: can_edit
    .. autoattribute:: can_delete

    .. autoattribute:: list_template
    .. autoattribute:: edit_template
    .. autoattribute:: create_template

    .. autoattribute:: column_list
    .. autoattribute:: column_exclude_list
    .. autoattribute:: column_labels
    .. autoattribute:: column_descriptions
    .. autoattribute:: column_formatters
    .. autoattribute:: column_type_formatters
    .. autoattribute:: column_display_pk

    .. autoattribute:: column_sortable_list
    .. autoattribute:: column_searchable_list
    .. autoattribute:: column_default_sort
    .. autoattribute:: column_choices

    .. autoattribute:: column_filters

    .. autoattribute:: form
    .. autoattribute:: form_base_class
    .. autoattribute:: form_columns
    .. autoattribute:: form_excluded_columns
    .. autoattribute:: form_args
    .. autoattribute:: form_overrides
    .. autoattribute:: form_widget_args
    .. autoattribute:: form_extra_fields
    .. autoattribute:: form_ajax_refs

    .. autoattribute:: form_create_rules
    .. autoattribute:: form_edit_rules

    .. autoattribute:: action_disallowed_list

    .. autoattribute:: page_size
    .. autoattribute:: can_set_page_size

Doc Api Mod Model Template (`doc/api/mod_model_template.rst`)

``flask_admin.model.template``

.. automodule:: flask_admin.model.template

text
.. autofunction:: macro

Doc Api Mod Theme (`doc/api/mod_theme.rst`)

``flask_admin.theme``

.. automodule:: flask_admin.theme

text
BootstrapTheme
--------------

.. autoclass:: BootstrapTheme
    :members:

Doc Api Mod Tools (`doc/api/mod_tools.rst`)

``flask_admin.tools``

.. automodule:: flask_admin.tools

text
.. autofunction:: import_module
.. autofunction:: import_attribute
.. autofunction:: module_not_found
.. autofunction:: rec_getattr

--- METRICS ---