Skip to content

Commit

Permalink
Change how Flask-Admin themes are configured
Browse files Browse the repository at this point in the history
Currently, Flask-Admin themes can be changed through a combination of
configuration modes:

* Through Admin() arguments; Admin(template_mode='bootstrap2')
* Through environment variables, eg FLASK_ADMIN_SWATCH='cerulean'.

These two modes work together to decide the UI framework/version used,
and then the colour/styling applied within that framework.

This patch switches us over to a single `theme` configuration object
passed to the Admin instance.

This brings all of the configuration for a specific visual style into
one place, and also means that now separate Admin() instances can
contain all of their styling information in an isolated way.

This should also make it easier for end users to provide their own Admin
theme, as the theme is also exposed to all of the `render_template`
calls under a `theme` variable.
  • Loading branch information
samuelhwilliams committed Jul 20, 2024
1 parent a3971b4 commit bbc989f
Show file tree
Hide file tree
Showing 21 changed files with 79 additions and 53 deletions.
4 changes: 2 additions & 2 deletions doc/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ can use it by adding a FileAdmin view to your app::

# Flask setup here

admin = Admin(app, name='microblog', template_mode='bootstrap4')
admin = Admin(app, name='microblog', theme=Bootstrap2Theme())

path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files'))
Expand Down Expand Up @@ -173,7 +173,7 @@ instance running on the same machine as your app, you can::

# Flask setup here

admin = Admin(app, name='microblog', template_mode='bootstrap4')
admin = Admin(app, name='microblog', theme=Bootstrap2Theme())

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

Expand Down
9 changes: 3 additions & 6 deletions doc/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,12 @@ The first step is to initialize an empty admin interface for your Flask app::

app = Flask(__name__)

# set optional bootswatch theme
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean'

admin = Admin(app, name='microblog', template_mode='bootstrap4')
admin = Admin(app, name='microblog', theme=Bootstrap4Theme(swatch='cerulean'))
# Add administrative views here

app.run()

Here, both the *name* and *template_mode* parameters are optional. Alternatively,
Here, both the *name* and *theme* parameters are optional. 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/>`_,
Expand All @@ -44,7 +41,7 @@ is the SQLAlchemy backend, which you can use as follows::

# Flask and Flask-SQLAlchemy initialization here

admin = Admin(app, name='microblog', template_mode='bootstrap4')
admin = Admin(app, name='microblog', theme=Bootstrap4Theme())
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))

Expand Down
4 changes: 3 additions & 1 deletion examples/auth-flask-login/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from wtforms import form, fields, validators
import flask_admin as admin
import flask_login as login

from flask_admin.theme import Bootstrap4Theme
from flask_admin.contrib import sqla
from flask_admin import helpers, expose
from werkzeug.security import generate_password_hash, check_password_hash
Expand Down Expand Up @@ -164,7 +166,7 @@ def index():
init_login()

# Create admin
admin = admin.Admin(app, 'Example: Auth', index_view=MyAdminIndexView(), base_template='my_master.html', template_mode='bootstrap4')
admin = admin.Admin(app, 'Example: Auth', index_view=MyAdminIndexView(), base_template='my_master.html', theme=Bootstrap4Theme())

# Add view
admin.add_view(MyModelView(User, db.session))
Expand Down
3 changes: 2 additions & 1 deletion examples/auth/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
UserMixin, RoleMixin, current_user
from flask_security.utils import hash_password
import flask_admin
from flask_admin.theme import Bootstrap4Theme
from flask_admin.contrib import sqla
from flask_admin import helpers as admin_helpers

Expand Down Expand Up @@ -83,7 +84,7 @@ def index():
app,
'Example: Auth',
base_template='my_master.html',
template_mode='bootstrap4',
theme=Bootstrap4Theme(),
)

# Add model views
Expand Down
6 changes: 2 additions & 4 deletions examples/bootstrap4/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from flask_sqlalchemy import SQLAlchemy

import flask_admin as admin
from flask_admin.theme import Bootstrap4Theme
from flask_admin.contrib.sqla import ModelView


Expand All @@ -13,7 +14,6 @@

# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
app.config['FLASK_ADMIN_SWATCH'] = 'flatly'

# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
Expand Down Expand Up @@ -61,10 +61,8 @@ def index():
return '<a href="/admin/">Click me to get to Admin!</a>'


# app.config['FLASK_ADMIN_SWATCH'] = 'Minty'

# Create admin with custom base template
admin = admin.Admin(app, 'Example: Bootstrap4', template_mode='bootstrap4')
admin = admin.Admin(app, 'Example: Bootstrap4', theme=Bootstrap4Theme(swatch='flatly'))

# Add views
admin.add_view(UserAdmin(User, db.session, category='Menu'))
Expand Down
3 changes: 2 additions & 1 deletion examples/custom-layout/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from flask_sqlalchemy import SQLAlchemy

import flask_admin as admin
from flask_admin.theme import Bootstrap3Theme
from flask_admin.contrib.sqla import ModelView


Expand Down Expand Up @@ -58,7 +59,7 @@ def index():


# Create admin with custom base template
admin = admin.Admin(app, 'Example: Layout-BS3', base_template='layout.html', template_mode='bootstrap3')
admin = admin.Admin(app, 'Example: Layout-BS3', base_template='layout.html', theme=Bootstrap3Theme())

# Add views
admin.add_view(UserAdmin(User, db.session))
Expand Down
9 changes: 3 additions & 6 deletions examples/forms-files-images/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@
from markupsafe import Markup

from flask_admin import Admin, form
from flask_admin.theme import Bootstrap4Theme
from flask_admin.form import rules
from flask_admin.contrib import sqla, rediscli


# Create application
app = Flask(__name__, static_folder='files')

# set optional bootswatch theme
# see http://bootswatch.com/3/ for available swatches
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean'

# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'

Expand Down Expand Up @@ -198,7 +195,7 @@ def index():
return '<a href="/admin/">Click me to get to Admin!</a>'

# Create admin
admin = Admin(app, 'Example: Forms', template_mode='bootstrap4')
admin = Admin(app, 'Example: Forms', theme=Bootstrap4Theme(swatch='cerulean'))

# Add views
admin.add_view(FileView(File, db.session))
Expand Down Expand Up @@ -296,6 +293,6 @@ def build_sample_db():
if not os.path.exists(database_path):
with app.app_context():
build_sample_db()

# Start app
app.run(debug=True)
4 changes: 3 additions & 1 deletion examples/geo_alchemy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import flask_admin as admin
from geoalchemy2.types import Geometry

from flask_admin.theme import Bootstrap4Theme
from flask_admin.contrib.geoa import ModelView


Expand Down Expand Up @@ -54,7 +56,7 @@ def index():
return '<a href="/admin/">Click me to get to Admin!</a>'

# Create admin
admin = admin.Admin(app, name='Example: GeoAlchemy', template_mode='bootstrap4')
admin = admin.Admin(app, name='Example: GeoAlchemy', theme=Bootstrap4Theme())

# Add views
admin.add_view(ModelView(Point, db.session, category='Points'))
Expand Down
5 changes: 4 additions & 1 deletion examples/simple/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@


# Create custom admin view
from flask_admin.theme import Bootstrap4Theme


class MyAdminView(admin.BaseView):
@admin.expose('/')
def index(self):
Expand All @@ -30,7 +33,7 @@ def index():
return '<a href="/admin/">Click me to get to Admin!</a>'

# Create admin interface
admin = admin.Admin(name="Example: Simple Views", template_mode='bootstrap4')
admin = admin.Admin(name="Example: Simple Views", theme=Bootstrap4Theme())
admin.add_view(MyAdminView(name="view1", category='Test'))
admin.add_view(AnotherAdminView(name="view2", category='Test'))
admin.init_app(app)
Expand Down
3 changes: 2 additions & 1 deletion examples/sqla-association_proxy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy.orm import relationship, backref

import flask_admin as admin
from flask_admin.theme import Bootstrap4Theme
from flask_admin.contrib import sqla

# Create application
Expand Down Expand Up @@ -87,7 +88,7 @@ class KeywordAdmin(sqla.ModelView):


# Create admin
admin = admin.Admin(app, name='Example: SQLAlchemy Association Proxy', template_mode='bootstrap4')
admin = admin.Admin(app, name='Example: SQLAlchemy Association Proxy', theme=Bootstrap4Theme())
admin.add_view(UserAdmin(User, db.session))
admin.add_view(KeywordAdmin(Keyword, db.session))

Expand Down
3 changes: 3 additions & 0 deletions examples/sqla/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def get_locale():

return session.get('lang', 'en')

# Initialize babel
babel = Babel(app, locale_selector=get_locale)


# Initialize babel
babel = Babel(app, locale_selector=get_locale)
Expand Down
4 changes: 0 additions & 4 deletions examples/sqla/admin/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# set optional bootswatch theme
# see http://bootswatch.com/3/ for available swatches
FLASK_ADMIN_SWATCH = 'cerulean'

# Create dummy secrey key so we can use sessions
SECRET_KEY = '123456790'

Expand Down
3 changes: 2 additions & 1 deletion examples/sqla/admin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import flask_admin as admin
from flask_admin.base import MenuLink
from flask_admin.theme import BootstrapTheme, Bootstrap4Theme
from flask_admin.contrib import sqla
from flask_admin.contrib.sqla import filters
from flask_admin.contrib.sqla.filters import BaseSQLAFilter, FilterEqual
Expand Down Expand Up @@ -242,7 +243,7 @@ def render(self, template, **kwargs):


# Create admin
admin = admin.Admin(app, name='Example: SQLAlchemy', template_mode='bootstrap4')
admin = admin.Admin(app, name='Example: SQLAlchemy', theme=Bootstrap4Theme(swatch='default'))

# Add views
admin.add_view(UserAdmin(User, db.session))
Expand Down
23 changes: 13 additions & 10 deletions flask_admin/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from functools import wraps

from flask import current_app, render_template, abort, g, url_for, request
from flask import Blueprint, current_app, render_template, abort, g, url_for
from flask_admin import babel
from flask_admin._compat import as_unicode
from flask_admin import helpers as h
Expand All @@ -13,6 +14,7 @@
from flask_admin.blueprints import _BlueprintWithHostSupport as Blueprint
from flask_admin.consts import ADMIN_ROUTES_HOST_VARIABLE
from flask_admin.menu import MenuCategory, MenuView, MenuLink, SubMenuCategory # noqa: F401
from flask_admin.theme import Theme, Bootstrap2Theme


def expose(url='/', methods=('GET',)):
Expand Down Expand Up @@ -268,7 +270,7 @@ def create_blueprint(self, admin):
self.blueprint = Blueprint(self.endpoint, __name__,
url_prefix=self.url,
subdomain=self.admin.subdomain,
template_folder=op.join('templates', self.admin.template_mode),
template_folder=op.join('templates', self.admin.theme.folder),
static_folder=self.static_folder,
static_url_path=self.static_url_path)
self.blueprint.attach_url_defaults_and_value_preprocessor(
Expand Down Expand Up @@ -308,6 +310,7 @@ def render(self, template, **kwargs):

# Expose config info
kwargs['config'] = current_app.config
kwargs['theme'] = self.admin.theme

# Contribute extra arguments
kwargs.update(self._template_args)
Expand Down Expand Up @@ -473,7 +476,7 @@ def __init__(self, app=None, name=None,
endpoint=None,
static_url_path=None,
base_template=None,
template_mode=None,
theme: t.Optional[Theme] = None,
category_icon_classes=None,
host=None):
"""
Expand All @@ -500,9 +503,9 @@ def __init__(self, app=None, name=None,
all its views. Can be overridden in view configuration.
:param base_template:
Override base HTML template for all static views. Defaults to `admin/base.html`.
:param template_mode:
Base template path. Defaults to `bootstrap2`. If you want to use
Bootstrap 3 or 4 integration, change it to `bootstrap3` or `bootstrap4`.
:param theme:
Base theme. Defaults to `Bootstrap2Theme()`. If you want to use
Bootstrap 3 or 4 integration, change it to `Bootstrap3Theme()` or `Bootstrap4Theme()`.
:param category_icon_classes:
A dict of category names as keys and html classes as values to be added to menu category icons.
Example: {'Favorites': 'glyphicon glyphicon-star'}
Expand All @@ -513,10 +516,10 @@ def __init__(self, app=None, name=None,

self.translations_path = translations_path

self._views = []
self._menu = []
self._menu_categories = dict()
self._menu_links = []
self._views = [] # type: ignore[var-annotated]
self._menu = [] # type: ignore[var-annotated]
self._menu_categories = dict() # type: ignore[var-annotated]
self._menu_links = [] # type: ignore[var-annotated]

if name is None:
name = 'Admin'
Expand All @@ -529,7 +532,7 @@ def __init__(self, app=None, name=None,
self.subdomain = subdomain
self.host = host
self.base_template = base_template or 'admin/base.html'
self.template_mode = template_mode or 'bootstrap2'
self.theme = theme or Bootstrap2Theme()
self.category_icon_classes = category_icon_classes or dict()

self._validate_admin_host_and_subdomain()
Expand Down
2 changes: 1 addition & 1 deletion flask_admin/form/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

def _is_bootstrap3():
view = h.get_current_view()
return view and view.admin.template_mode == 'bootstrap3'
return view and view.admin.theme.folder == 'bootstrap3'


class Select2Widget(widgets.Select):
Expand Down
4 changes: 2 additions & 2 deletions flask_admin/templates/bootstrap2/admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<meta name="author" content="">
{% endblock %}
{% block head_css %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap2/swatch/{swatch}/bootstrap.min.css'.format(swatch=config.get('FLASK_ADMIN_SWATCH', 'default')), v='2.3.2') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap2/swatch/{swatch}/bootstrap.min.css'.format(swatch=theme.swatch), v='2.3.2') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='bootstrap/bootstrap2/css/bootstrap-responsive.css', v='2.3.2') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='admin/css/bootstrap2/admin.css', v='1.1.1') }}" rel="stylesheet">
{% if admin_view.extra_css %}
Expand All @@ -33,7 +33,7 @@
</head>
<body>
{% block page_body %}
<div class="container{%if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
<div class="container{%if theme.fluid %}-fluid{% endif %}">
<div class="navbar">
<div class="navbar-inner">
{% block brand %}
Expand Down
6 changes: 3 additions & 3 deletions flask_admin/templates/bootstrap3/admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<meta name="author" content="">
{% endblock %}
{% block head_css %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/swatch/{swatch}/bootstrap.min.css'.format(swatch=config.get('FLASK_ADMIN_SWATCH', 'default')), v='3.3.5') }}" rel="stylesheet">
{%if config.get('FLASK_ADMIN_SWATCH', 'default') == 'default' %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/swatch/{swatch}/bootstrap.min.css'.format(swatch=theme.swatch), v='3.3.5') }}" rel="stylesheet">
{%if theme.swatch == 'default' %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap3/css/bootstrap-theme.min.css', v='3.3.5') }}" rel="stylesheet">
{%endif%}
<link href="{{ admin_static.url(filename='admin/css/bootstrap3/admin.css', v='1.1.1') }}" rel="stylesheet">
Expand All @@ -36,7 +36,7 @@
</head>
<body>
{% block page_body %}
<div class="container{%if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
<div class="container{%if theme.fluid %}-fluid{% endif %}">
<nav class="navbar navbar-default" role="navigation">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
Expand Down
6 changes: 3 additions & 3 deletions flask_admin/templates/bootstrap4/admin/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
<meta name="author" content="">
{% endblock %}
{% block head_css %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/swatch/{swatch}/bootstrap.min.css'.format(swatch=config.get('FLASK_ADMIN_SWATCH', 'default')), v='4.2.1') }}"
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/swatch/{swatch}/bootstrap.min.css'.format(swatch=theme.swatch), v='4.2.1') }}"
rel="stylesheet">
{% if config.get('FLASK_ADMIN_SWATCH', 'default') == 'default' %}
{% if theme.swatch == 'default' %}
<link href="{{ admin_static.url(filename='bootstrap/bootstrap4/css/bootstrap.min.css', v='4.2.1') }}" rel="stylesheet">
{% endif %}
<link href="{{ admin_static.url(filename='admin/css/bootstrap4/admin.css', v='1.1.1') }}" rel="stylesheet">
Expand All @@ -37,7 +37,7 @@
</head>
<body>
{% block page_body %}
<div class="container{% if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}">
<div class="container{% if theme.fluid %}-fluid{% endif %}">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-2" role="navigation">
<!-- Brand and toggle get grouped for better mobile display -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#admin-navbar-collapse"
Expand Down
Loading

0 comments on commit bbc989f

Please sign in to comment.