Flask-Admin user guide for setting up the Flask-admin Gui
Flask-Admin is an extension of the Flask framework. It can be used to quickly create a Web management interface. It provides common management functions such as adding, deleting, modifying, and querying users and files; if you do not like its default interface, you can modify the template file to customize it;
Flask-Admin regards each menu (Hyperlink) as a view, which can be displayed only after registration. The view itself also has attributes to control whether it is visible. Therefore, you can use this mechanism to customize your own modular interface. For example, users with different permissions can see different menus after logon;
Project address: https://flask-admin.readthedocs.io/en/latest/
Example/simple
This is the simplest example. It helps us quickly and intuitively understand basic concepts and learn to customize the Flask-Admin interface.
Simple. py:
from flask import Flaskfrom flask.ext import admin# Create custom admin viewclass MyAdminView(admin.BaseView): @admin.expose('/') def index(self): return self.render('myadmin.html')class AnotherAdminView(admin.BaseView): @admin.expose('/') def index(self): return self.render('anotheradmin.html') @admin.expose('/test/') def test(self): return self.render('test.html')# Create flask appapp = Flask(__name__, template_folder='templates')app.debug = True# Flask views@app.route('/')def index(): return '<a href="/admin/">Click me to get to Admin!</a>'# Create admin interfaceadmin = admin.Admin()admin.add_view(MyAdminView(category='Test'))admin.add_view(AnotherAdminView(category='Test'))admin.init_app(app)if __name__ == '__main__': # Start app app.run()
Here you can see the Running Effect
BaseView
All views must be inherited from BaseView:
Copy codeThe Code is as follows:
Class BaseView (name = None, category = None, endpoint = None, url = None, static_folder = None, static_url_path = None)
Name: view is displayed as a menu (Hyperlink) on the page. menu name = 'name'. The lower-case class name is used by default.
Category: if multiple views share the same category, they are all put into one dropdown (dropdown name = 'category ')
Endpoint: If the endpoint is 'xxx', you can use url_for (xxx. index) or change the page URL (/admin/xxx)
Url: page URL, priority url> endpoint> class name
Static_folder: Path of the static directory
Static_url_path: URL of the static directory
Anotheradmin.html:
{% extends 'admin/master.html' %}{% block body %} Hello World from AnotherMyAdmin!<br/> <a href="{{ url_for('.test') }}">Click me to go to test view</a>{% endblock %}
If AnotherAdminView adds the parameter endpoint = 'xxx', you can write it as url_for ('xxx. text'). Then, the page URL changes from/admin/anotheradminview/to/admin/xxx.
If the parameter url = 'aaa' is specified at the same time, the page URL will be/admin/aaa, And the url priority is higher than the endpoint
Admin
Copy codeThe Code is as follows:
Class Admin (app = None, name = None, url = None, subdomain = None, index_view = None, translations_path = None, endpoint = None, static_url_path = None, base_template = None)
App: Flask Application Object. In this example, admin. init_app (app) can be left empty, and admin = admin. Admin (app = app) is the same.
Name: Application name. The default value is 'admin'. It is displayed as the main menu name ('admin' on the left of 'home') and page title.
Subdomain :???
Index_view: The menu corresponding to 'home' is called index view. The default value is AdminIndexView.
Base_template: basic template. The default template is admin/base.html. The template is in the source code directory of Flask-Admin.
Some Admin code is as follows:
class MenuItem(object): """ Simple menu tree hierarchy. """ def __init__(self, name, view=None): self.name = name self._view = view self._children = [] self._children_urls = set() self._cached_url = None self.url = None if view is not None: self.url = view.url def add_child(self, view): self._children.append(view) self._children_urls.add(view.url)class Admin(object): def __init__(self, app=None, name=None, url=None, subdomain=None, index_view=None, translations_path=None, endpoint=None, static_url_path=None, base_template=None): self.app = app self.translations_path = translations_path self._views = [] self._menu = [] self._menu_categories = dict() self._menu_links = [] if name is None: name = 'Admin' self.name = name self.index_view = index_view or AdminIndexView(endpoint=endpoint, url=url) self.endpoint = endpoint or self.index_view.endpoint self.url = url or self.index_view.url self.static_url_path = static_url_path self.subdomain = subdomain self.base_template = base_template or 'admin/base.html' # Add predefined index view self.add_view(self.index_view) # Register with application if app is not None: self._init_extension() def add_view(self, view): # Add to views self._views.append(view) # If app was provided in constructor, register view with Flask app if self.app is not None: self.app.register_blueprint(view.create_blueprint(self)) self._add_view_to_menu(view) def _add_view_to_menu(self, view): if view.category: category = self._menu_categories.get(view.category) if category is None: category = MenuItem(view.category) self._menu_categories[view.category] = category self._menu.append(category) category.add_child(MenuItem(view.name, view)) else: self._menu.append(MenuItem(view.name, view)) def init_app(self, app): self.app = app self._init_extension() # Register views for view in self._views: app.register_blueprint(view.create_blueprint(self)) self._add_view_to_menu(view)
From the code above, we can see that init_app (app) and Admin (app = app) are the same:
Register each view as blueprint (the concept in Flask can be simply understood as a module)
Record all views and their category and url
AdminIndexView
Copy codeThe Code is as follows:
Class AdminIndexView (name = None, category = None, endpoint = None, url = None, template = 'admin/index.html ')
Name: 'home' by default'
Endpoint: 'admin' by default'
Url: The default value is '/admin'
If you want to encapsulate your own view, follow the instructions in AdminIndexView:
class AdminIndexView(BaseView): def __init__(self, name=None, category=None, endpoint=None, url=None, template='admin/index.html'): super(AdminIndexView, self).__init__(name or babel.lazy_gettext('Home'), category, endpoint or 'admin', url or '/admin', 'static') self._template = template @expose() def index(self): return self.render(self._template)base_template
Base_template is/admin/base.html by default, and is the main code of the page (based on bootstrap). It also imports admin/layout.html;
Layout is a macro used to expand and display menus;
Use some variables in the template to retrieve the information saved during previous view registration (such as menu name and url ):
# Admin/layout.html (Part)
{% macro menu() %} {% for item in admin_view.admin.menu() %} {% if item.is_category() %} {% set children = item.get_children() %} {% if children %} {% if item.is_active(admin_view) %}<li class="active dropdown">{% else %}<li class="dropdown">{% endif %} <a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">{{ item.name }}<b class="caret"></b></a> <ul class="dropdown-menu"> {% for child in children %} {% if child.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %} <a href="{{ child.get_url() }}">{{ child.name }}</a> </li> {% endfor %} </ul> </li> {% endif %} {% else %} {% if item.is_accessible() and item.is_visible() %} {% if item.is_active(admin_view) %}<li class="active">{% else %}<li>{% endif %} <a href="{{ item.get_url() }}">{{ item.name }}</a> </li> {% endif %} {% endif %} {% endfor %}{% endmacro %}
Example/file
This example can help us quickly build a file management interface, but our focus is on learning to use the ActionsMixin module.
File. py:
import osimport os.path as opfrom flask import Flaskfrom flask.ext import adminfrom flask.ext.admin.contrib import fileadmin# Create flask appapp = Flask(__name__, template_folder='templates', static_folder='files')# Create dummy secrey key so we can use flashapp.config['SECRET_KEY'] = '123456790'# Flask views@app.route('/')def index(): return '<a href="/admin/">Click me to get to Admin!</a>'if __name__ == '__main__': # Create directory path = op.join(op.dirname(__file__), 'files') try: os.mkdir(path) except OSError: pass # Create admin interface admin = admin.Admin(app) admin.add_view(fileadmin.FileAdmin(path, '/files/', name='Files')) # Start app app.run(debug=True)
FileAdmin is a written view, which can be used directly:
Copy codeThe Code is as follows:
Class FileAdmin (base_path, base_url, name = None, category = None, endpoint = None, url = None, verify_path = True)
Base_path: relative path of the file
Base_url: URL of the file directory
The code in FileAdmin and ActionsMixin is as follows:
Class FileAdmin (BaseView, ActionsMixin ):
Def _ init _ (self, base_path, base_url, name = None, category = None, endpoint = None, url = None, verify_path = True): self. init_actions () @ expose ('/action/', methods = ('post',) def action_view (self): return self. handle_action () # Actions @ action ('delete', lazy_gettext ('delete'), lazy_gettext ('Are you sure you want to delete these files? ') Def action_delete (self, items): if not self. can_delete: flash (gettext ('file deletion is disabled. '), 'error') return for path in items: base_path, full_path, path = self. _ normalize_path (path) if self. is_accessible_path (path): try: OS. remove (full_path) flash (gettext ('file "% (name) s" was successfully deleted. ', name = path) Failed t Exception as ex: flash (gettext ('failed to delete file: % (name) s', name = ex), 'error ') @ action ('edit', lazy_gettext ('edit') def action_edit (self, items): return redirect (url_for ('. edit', path = items) @ action () is used for the function after wrap. The function here is to save the parameter: def action (name, text, confirmation = None) def wrap (f): f. _ action = (name, text, confirmation) return f return wrap
Name: action name
Text: The name of a button.
Confirmation: confirmation information in the pop-up box
Init_actions () saves all action information to ActionsMixin:
# Debugging information _ actions = [('delete', lu 'delete'), ('edit', lu 'edit')] _ actions_data = {'edit ': (<bound method FileAdmin. action_edit of <flask_admin.contrib.fileadmin.FileAdmin object at 0x1aafc50>, lu 'edit', None), 'delete': (<bound method FileAdmin. action_delete of <flask_admin.contrib.fileadmin.FileAdmin object at 0x1aafc50>, lu 'delete', lu 'Are you sure you want to Delete these files? ')}
Action_view () is used to process the POST request to/action/, and then calls handle_action (). It then calls different actions for processing, and finally returns the current page:
# Omitting irrelevant code def handle_action (self, return_view = None): action = request. form. get ('action') ids = request. form. getlist ('rowid') handler = self. _ actions_data.get (action) if handler and self. is_action_allowed (action): response = handler [0] (ids) if response is not None: return response if not return_view: url = url_for ('. '+ self. _ default_view) else: url = url_for ('. '+ return_view) return redirect (url)
Ids is a file list that is passed as a parameter to the action Handler (parameter items ):
# Debugging information ids: [u'1.png ', u'2.png']
Analyze the Page code. The file corresponding to the Files page is admin/file/list.html. Focus on the Code related to the With selected drop-down menu:
{% Import 'admin/actions.html 'as actionslib with context %}
{% if actions %} <div class="btn-group"> {{ actionslib.dropdown(actions, 'dropdown-toggle btn btn-large') }} </div>{% endif %}{% block actions %} {{ actionslib.form(actions, url_for('.action_view')) }}{% endblock %}{% block tail %} {{ actionslib.script(_gettext('Please select at least one file.'), actions, actions_confirmation) }}{% endblock %}
The following three macros are used in actions.html:
{% macro dropdown(actions, btn_class='dropdown-toggle') -%} <a class="{{ btn_class }}" data-toggle="dropdown" href="javascript:void(0)">{{ _gettext('With selected') }}<b class="caret"></b></a> <ul class="dropdown-menu"> {% for p in actions %} <li> <a href="javascript:void(0)" onclick="return modelActions.execute('{{ p[0] }}');">{{ _gettext(p[1]) }}</a> </li> {% endfor %} </ul>{% endmacro %}{% macro form(actions, url) %} {% if actions %} <form id="action_form" action="{{ url }}" method="POST" style="display: none"> {% if csrf_token %} <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> {% endif %} <input type="hidden" id="action" name="action" /> </form> {% endif %}{% endmacro %}{% macro script(message, actions, actions_confirmation) %} {% if actions %} <script src="{{ admin_static.url(filename='admin/js/actions.js') }}"></script> <script language="javascript"> var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }}); </script> {% endif %}{% endmacro %}
The final page (Part ):
<div class="btn-group"> <a class="dropdown-toggle btn btn-large" href="javascript:void(0)" data-toggle="dropdown"> With selected <b class="caret"></b> </a> <ul class="dropdown-menu"> <li> <a onclick="return modelActions.execute('delete');" href="javascript:void(0)">Delete</a> </li> <li> <a onclick="return modelActions.execute('edit');" href="javascript:void(0)">Edit</a> </li> </ul></div><form id="action_form" action="/admin/fileadmin/action/" method="POST" style="display: none"> <input type="hidden" id="action" name="action" /></form><script src="/admin/static/admin/js/actions.js"></script><script language="javascript"> var modelActions = new AdminModelActions("Please select at least one file.", {"delete": "Are you sure you want to delete these files?"});</script>
The processing method after selecting the menu is in actions. js:
var AdminModelActions = function(actionErrorMessage, actionConfirmations) { // Actions helpers. TODO: Move to separate file this.execute = function(name) { var selected = $('input.action-checkbox:checked').size(); if (selected === 0) { alert(actionErrorMessage); return false; } var msg = actionConfirmations[name]; if (!!msg) if (!confirm(msg)) return false; // Update hidden form and submit it var form = $('#action_form'); $('#action', form).val(name); $('input.action-checkbox', form).remove(); $('input.action-checkbox:checked').each(function() { form.append($(this).clone()); }); form.submit(); return false; }; $(function() { $('.action-rowtoggle').change(function() { $('input.action-checkbox').attr('checked', this.checked); }); });};
Compare the form before and after modification:
# Initialize <form id = "action_form" style = "display: none "method =" POST "action ="/admin/fileadmin/action/"> <input id =" action "type =" hidden "name =" action "> </form> # 'delete' <form id = "action_form" style = "display: none "method =" POST "action ="/admin/fileadmin/action/"> <input id =" action "type =" hidden "name =" action "value =" delete "> <input class =" action-checkbox "type =" checkbox "value =" 1.png" name = "rowid"> <input class = "action-checkbox" type = "checkbox" value = "2.png" name =" rowid "> <input class =" action-checkbox "type =" checkbox "value =" 3.png" name = "rowid"> </form> #' edit 'select a file <form id = "action_form" style = "display: none "method =" POST "action ="/admin/fileadmin/action/"> <input id =" action "type =" hidden "name =" action "value =" edit "> <input class =" action-checkbox "type =" checkbox "value =" 1.png" name = "rowid"> </form>
To sum up, when we click Delete or Edit in the drop-down menu, the confirmation box will pop up for the local JavaScript code (assuming there is confirmation information ), then, submit a form to/admin/fileadmin/action/. The request processing function action_view () calls different action processing functions based on the form type, and finally returns a page.
Flask-Admin field (column) Formatting
In some cases, we need to format a certain attribute of the model. For example, by default, the date and time are displayed for a long time, and only the month and day may need to be displayed. In this case, column formatting is useful.
For example, if you want to display double prices, you can do this:
class MyModelView(BaseModelView): column_formatters = dict(price=lambda v, c, m, p: m.price*2)
Or use the macro in the Jinja2 template:
from flask.ext.admin.model.template import macroclass MyModelView(BaseModelView): column_formatters = dict(price=macro('render_price'))# in template{% macro render_price(model, column) %} {{ model.price * 2 }}{% endmacro %}
Callback Function Model:
def formatter(view, context, model, name): # `view` is current administrative view # `context` is instance of jinja2.runtime.Context # `model` is model instance # `name` is property name pass
Corresponds to v, c, m, and p above.