Commit 3c03f471 authored by bryhoyt's avatar bryhoyt Committed by GitHub

Merge pull request #10 from flask-admin/master

Merge from central repo into bryhoyt's fork
parents b2ea5731 bc5ab4e7
......@@ -18,6 +18,11 @@ venv
__pycache__
examples/sqla-inline/static
examples/file/files
examples/forms/files
examples/appengine/lib
.DS_Store
.idea/
*.sqlite
env
*.egg
.eggs
sudo: false
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
install: "pip install -r travis.txt --use-mirrors"
env:
- WTFORMS_VERSION=1
- WTFORMS_VERSION=2
services: mongodb
addons:
postgresql: "9.4"
script: nosetests flask_admin/tests
services:
- postgresql
- mongodb
before_script:
- psql -U postgres -c 'CREATE DATABASE flask_admin_test;'
- psql -U postgres -c 'CREATE EXTENSION postgis;' flask_admin_test
- psql -U postgres -c 'CREATE EXTENSION hstore;' flask_admin_test
install:
- pip install "wtforms<$WTFORMS_VERSION.99"
- pip install -r requirements-dev.txt
script: nosetests flask_admin/tests --with-coverage --cover-erase --cover-inclusive
after_success:
- coveralls
......@@ -8,6 +8,8 @@ Development Lead
Patches and Suggestions
```````````````````````
- Paul Brown <paul90brown@gmail.com>
- Petrus Janse van Rensburg <petrus.jvrensburg@gmail.com>
- Priit Laes <plaes@plaes.org>
- Sean Lynch
- Andy Wilson <wilson.andrew.j+github@gmail.com>
......@@ -19,5 +21,7 @@ Patches and Suggestions
- Peter Ward <peteraward@gmail.com>
- Artem Serga <artem@serga.name>
- Koblaid
- Julian Gonggrijp (UUDigitalHumanitieslab)
- Arthur de Paula Bressan (ArthurPBressan)
.. and more. If I missed you, let me know.
\ No newline at end of file
.. and more. If I missed you, let me know.
Copyright (c) 2012, Serge S. Koval and contributors. See AUTHORS
Copyright (c) 2014, Serge S. Koval and contributors. See AUTHORS
for more details.
Some rights reserved.
......
......@@ -5,7 +5,8 @@ Select2
Distributed under `APLv2 <http://www.apache.org/licenses/LICENSE-2.0>`_.
Twitter Bootstrap
Bootstrap
=================
Distributed under `APLv2 <http://www.apache.org/licenses/LICENSE-2.0>`_.
v3.1.0 and subsequent versions distributed under `MIT <http://opensource.org/licenses/MIT>`_.
Versions prior to v3.1.0 distributed under `APLv2 <http://www.apache.org/licenses/LICENSE-2.0>`_.
Flask-Admin
===========
.. image:: https://travis-ci.org/mrjoes/flask-admin.png?branch=master
:target: https://travis-ci.org/mrjoes/flask-admin
The project was recently moved into its own organization. Please update your
references to *git@github.com:flask-admin/flask-admin.git*.
.. image:: https://d322cqt584bo4o.cloudfront.net/flask-admin/localized.png
:target: https://crowdin.com/project/flask-admin
.. image:: https://travis-ci.org/flask-admin/flask-admin.png?branch=master
:target: https://travis-ci.org/flask-admin/flask-admin
Introduction
------------
......@@ -36,30 +42,24 @@ Several usage examples are included in the */examples* folder. Please feel free
on some of the existing ones, and then submit them via GitHub as a *pull-request*.
You can see some of these examples in action at `http://examples.flask-admin.org <http://examples.flask-admin.org/>`_.
To run that same page in your local environment, simply::
cd flask-admin
python examples/runserver.py
Alternatively, you can run the examples one at a time, with something like::
To run the examples on your local environment, one at a time, do something like::
cd flask-admin
python examples/simple/simple.py
python examples/simple/app.py
Documentation
-------------
Flask-Admin is extensively documented, you can find all of the documentation at `http://readthedocs.org/docs/flask-admin <http://readthedocs.org/docs/flask-admin>`_.
Flask-Admin is extensively documented, you can find all of the documentation at `https://flask-admin.readthedocs.io/en/latest/ <https://flask-admin.readthedocs.io/en/latest/>`_.
The docs are auto-generated from the *.rst* files in the */doc* folder. So if you come across any errors, or
if you think of anything else that should be included, then please make the changes and submit them as a *pull-request*.
To build the docs in your local environment::
To build the docs in your local environment, from the project directory::
sudo pip install sphinx
cd flask-admin
pip install -r requirements-dev.txt
sudo make html
Or, if you want to preview any *.rst* snippets that you may want to contribute, go to `http://rst.ninjs.org/ <http://rst.ninjs.org/>`_.
And if you want to preview any *.rst* snippets that you may want to contribute, go to `http://rst.ninjs.org/ <http://rst.ninjs.org/>`_.
Installation
------------
......@@ -69,34 +69,37 @@ To install Flask-Admin, simply::
Or alternatively, you can download the repository and install manually by doing::
git clone git@github.com:mrjoes/flask-admin.git
git clone git@github.com:flask-admin/flask-admin.git
cd flask-admin
python setup.py install
Tests
-----
Test are run with *nose*. If you are not familiar with this package you can get some more info from `their website <http://nose.readthedocs.org/>`_.
Test are run with *nose*. If you are not familiar with this package you can get some more info from `their website <https://nose.readthedocs.io/>`_.
To run the tests, simply::
To run the tests, from the project directory, simply::
pip install nose
and then::
cd flask-admin
pip install -r requirements-dev.txt
nosetests
You should see output similar to::
...
.............................................
----------------------------------------------------------------------
Ran 41 tests in 2.092s
Ran 102 tests in 13.132s
Please note that you will need to install some additional dependencies in order for all of the tests to be executed successfully.
OK
For all the tests to pass successfully, you'll need Postgres & MongoDB to be running locally. For Postgres::
CREATE DATABASE flask_admin_test;
CREATE EXTENSION postgis;
3rd Party Stuff
---------------
Flask-Admin is built with the help of `Twitter Bootstrap <http://twitter.github.com/bootstrap/>`_ and `Select2 <https://github.com/ivaynberg/select2>`_.
Flask-Admin is built with the help of `Bootstrap <http://getbootstrap.com/>`_ and `Select2 <https://github.com/ivaynberg/select2>`_.
If you want to localize your application, install the `Flask-BabelEx <https://pypi.python.org/pypi/Flask-BabelEx>`_ package.
You can help improve Flask-Admin's translations through Crowdin: https://crowdin.com/project/flask-admin
This diff is collapsed.
#!/bin/sh
pybabel extract -F babel.ini -k _gettext -k _ngettext -k lazy_gettext -o admin.pot --project Flask-Admin ../flask_admin
pybabel compile -f -D admin -d ../flask_admin/translations/
#!/bin/sh
# get newest translations from Crowdin
cd ../flask_admin/translations/
curl http://api.crowdin.net/api/project/flask-admin/export?key=`cat ~/.crowdin.flaskadmin.key`
wget http://api.crowdin.net/api/project/flask-admin/download/all.zip?key=`cat ~/.crowdin.flaskadmin.key` -O all.zip
# unzip and move .po files in subfolders called LC_MESSAGES
unzip -o all.zip
find . -maxdepth 2 -name "*.po" -exec bash -c 'mkdir -p $(dirname {})/LC_MESSAGES; mv {} $(dirname {})/LC_MESSAGES/admin.po' \;
rm all.zip
mv es-ES/LC_MESSAGES/* es/LC_MESSAGES/
rm -r es-ES/
mv ca/LC_MESSAGES/* ca_ES/LC_MESSAGES/
rm -r ca/
mv zh-CN/LC_MESSAGES/* zh_Hans_CN/LC_MESSAGES/
rm -r zh-CN/
mv zh-TW/LC_MESSAGES/* zh_Hant_TW/LC_MESSAGES/
rm -r zh-TW/
mv pt-PT/LC_MESSAGES/* pt/LC_MESSAGES/
rm -r pt-PT/
mv pt-BR/LC_MESSAGES/* pt_BR/LC_MESSAGES/
rm -r pt-BR/
mv sv-SE/LC_MESSAGES/* sv/LC_MESSAGES/
rm -r sv-SE/
mv pa-IN/LC_MESSAGES/* pa/LC_MESSAGES/
rm -r pa-IN/
cd ../../babel
sh babel.sh
#!/bin/sh
sh babel.sh
curl -F "files[/admin.pot]=@admin.pot" http://api.crowdin.net/api/project/flask-admin/update-file?key=`cat ~/.crowdin.flaskadmin.key`
<h3>Useful Links</h3>
<ul>
<li><a href="http://flask.pocoo.org/">The Flask Website</a></li>
<li><a href="http://github.com/mrjoes/flask-admin">Flask-Admin @ github</a></li>
<li><a href="http://flask.pocoo.org/" target="_blank">Flask</a></li>
<li><a href="http://github.com/flask-admin/flask-admin" target="_blank">Flask-Admin @ github</a></li>
<li><a href="http://examples.flask-admin.org/" target="_blank">Flask-Admin Examples</a></li>
</ul>
<a href="http://github.com/mrjoes/flask-admin"><img style="position: fixed; top: 0; right: 0; border: 0;"
<a class="github" href="http://github.com/flask-admin/flask-admin" target="_blank"><img style="position: fixed; top: 0; right: 0; border: 0;"
src="//s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
{%- if display_toc %}
<h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
{{ toc }}
{%- endif %}
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
......@@ -4,8 +4,7 @@
{% if theme_touch_icon %}
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
{% endif %}
<link media="only screen and (max-device-width: 480px)" href="{{
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9">
{% endblock %}
{%- block relbar2 %}{% endblock %}
{% block header %}
......
File mode changed from 100644 to 100755
......@@ -8,11 +8,11 @@
{% set page_width = '940px' %}
{% set sidebar_width = '220px' %}
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Georgia', serif;
font-size: 17px;
......@@ -40,14 +40,10 @@ div.sphinxsidebar {
width: {{ sidebar_width }};
}
div.sphinxsidebar span.pre {
word-wrap: break-word;
}
hr {
border: 1px solid #B1B4B6;
}
div.body {
background-color: #ffffff;
color: #3E4349;
......@@ -58,7 +54,7 @@ img.floatingflask {
padding: 0 0 10px 10px;
float: right;
}
div.footer {
width: {{ page_width }};
margin: 20px auto 30px auto;
......@@ -74,7 +70,7 @@ div.footer a {
div.related {
display: none;
}
div.sphinxsidebar a {
color: #444;
text-decoration: none;
......@@ -84,7 +80,7 @@ div.sphinxsidebar a {
div.sphinxsidebar a:hover {
border-bottom: 1px solid #999;
}
div.sphinxsidebar {
font-size: 14px;
line-height: 1.5;
......@@ -99,7 +95,7 @@ div.sphinxsidebarwrapper p.logo {
margin: 0;
text-align: center;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: 'Garamond', 'Georgia', serif;
......@@ -113,7 +109,7 @@ div.sphinxsidebar h4 {
div.sphinxsidebar h4 {
font-size: 20px;
}
div.sphinxsidebar h3 a {
color: #444;
}
......@@ -124,7 +120,7 @@ div.sphinxsidebar p.logo a:hover,
div.sphinxsidebar h3 a:hover {
border: none;
}
div.sphinxsidebar p {
color: #555;
margin: 10px 0;
......@@ -135,25 +131,25 @@ div.sphinxsidebar ul {
padding: 0;
color: #000;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: 'Georgia', serif;
font-size: 1em;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #004B6B;
text-decoration: underline;
}
a:hover {
color: #6D4100;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
......@@ -173,25 +169,24 @@ div.indexwrapper h1 {
height: {{ theme_index_logo_height }};
}
{% endif %}
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
div.body h2 { font-size: 180%; }
div.body h3 { font-size: 150%; }
div.body h4 { font-size: 130%; }
div.body h5 { font-size: 100%; }
div.body h6 { font-size: 100%; }
a.headerlink {
color: #ddd;
padding: 0 4px;
text-decoration: none;
}
a.headerlink:hover {
color: #444;
background: #eaeaea;
}
div.body p, div.body dd, div.body li {
line-height: 1.4em;
}
......@@ -238,20 +233,20 @@ div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
......@@ -345,7 +340,7 @@ ul, ol {
margin: 10px 0 10px 30px;
padding: 0;
}
pre {
background: #eee;
padding: 7px 30px;
......@@ -362,7 +357,7 @@ dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
tt {
background-color: #ecf0f3;
color: #222;
......@@ -397,3 +392,192 @@ a.footnote-reference:hover {
a:hover tt {
background: #EEE;
}
@media screen and (max-width: 870px) {
div.sphinxsidebar {
display: none;
}
div.document {
width: 100%;
}
div.documentwrapper {
margin-left: 0;
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
}
div.bodywrapper {
margin-top: 0;
margin-right: 0;
margin-bottom: 0;
margin-left: 0;
}
ul {
margin-left: 0;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.bodywrapper {
margin: 0;
}
.footer {
width: auto;
}
.github {
display: none;
}
}
@media screen and (max-width: 768px) {
{% if theme_index_logo %}
div.indexwrapper h1 {
background-size: 100%;
}
{% endif %}
}
@media screen and (max-width: 875px) {
body {
margin: 0;
padding: 20px 30px;
}
div.documentwrapper {
float: none;
background: white;
}
div.sphinxsidebar {
display: block;
float: none;
width: 102.5%;
margin: 50px -30px -20px -30px;
padding: 10px 20px;
background: #333;
color: white;
}
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar a {
color: #aaa;
}
div.sphinxsidebar p.logo {
display: none;
}
div.document {
width: 100%;
margin: 0;
}
div.related {
display: block;
margin: 0;
padding: 10px 0 20px 0;
}
div.related ul,
div.related ul li {
margin: 0;
padding: 0;
}
div.footer {
display: none;
}
div.bodywrapper {
margin: 0;
}
div.body {
min-height: 0;
padding: 0;
}
.rtd_doc_footer {
display: none;
}
.document {
width: auto;
}
.footer {
width: auto;
}
.footer {
width: auto;
}
.github {
display: none;
}
}
/* scrollbars */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-button:start:decrement,
::-webkit-scrollbar-button:end:increment {
display: block;
height: 10px;
}
::-webkit-scrollbar-button:vertical:increment {
background-color: #fff;
}
::-webkit-scrollbar-track-piece {
background-color: #eee;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-thumb:vertical {
height: 50px;
background-color: #ccc;
-webkit-border-radius: 3px;
}
::-webkit-scrollbar-thumb:horizontal {
width: 50px;
background-color: #ccc;
-webkit-border-radius: 3px;
}
/* misc. */
.revsys-inline {
display: none!important;
}
\ No newline at end of file
/*
* small_flask.css_t
* ~~~~~~~~~~~~~~~~~
*
* :copyright: Copyright 2010 by Armin Ronacher.
* :license: Flask Design License, see LICENSE for details.
*/
body {
margin: 0;
padding: 20px 30px;
}
div.documentwrapper {
float: none;
background: white;
}
div.sphinxsidebar {
display: block;
float: none;
width: 102.5%;
margin: 50px -30px -20px -30px;
padding: 10px 20px;
background: #333;
color: white;
}
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar a {
color: #aaa;
}
div.sphinxsidebar p.logo {
display: none;
}
div.document {
width: 100%;
margin: 0;
}
div.related {
display: block;
margin: 0;
padding: 10px 0 20px 0;
}
div.related ul,
div.related ul li {
margin: 0;
padding: 0;
}
div.footer {
display: none;
}
div.bodywrapper {
margin: 0;
}
div.body {
min-height: 0;
padding: 0;
}
......@@ -7,4 +7,4 @@ pygments_style = flask_theme_support.FlaskyStyle
index_logo = 'flask-admin.png'
index_logo_height = 140px
touch_icon =
github_fork = 'mrjoes/flask-admin'
github_fork = 'flask-admin/flask-admin'
\ No newline at end of file
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
......@@ -5,7 +5,6 @@ nosidebar = true
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo =
index_logo = ''
index_logo_height = 120px
github_fork = MrJoes/Flask-AdminEx
github_fork = ''
File mode changed from 100644 to 100755
Adding a new model backend
==========================
.. _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:
......@@ -12,9 +14,9 @@ If that is the case, then you can implement your own database backend by extendi
and implementing the set of scaffolding methods listed below.
Extending BaseModelView
-------------------------
-----------------------
Start off by defining a new class, which derives from from :class:`~flask.ext.admin.model.BaseModelView`::
Start off by defining a new class, which derives from from :class:`~flask_admin.model.BaseModelView`::
class MyDbModel(BaseModelView):
pass
......@@ -24,11 +26,11 @@ Extending BaseModelView
Now, implement the following scaffolding methods for the new class:
1. :meth:`~flask.ext.admin.model.BaseModelView.get_pk_value`
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.ext.admin.contrib.sqla.ModelView.scaffold_pk`, caches it
using :meth:`~flask_admin.contrib.sqla.ModelView.scaffold_pk`, caches it
and then returns the value from the model whenever requested.
For example::
......@@ -37,7 +39,7 @@ Extending BaseModelView
def get_pk_value(self, model):
return self.model.id
2. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_list_columns`
2. :meth:`~flask_admin.model.BaseModelView.scaffold_list_columns`
Returns a list of columns to be displayed in a list view. For example::
......@@ -52,7 +54,7 @@ Extending BaseModelView
return columns
3. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_sortable_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.
......@@ -64,7 +66,7 @@ Extending BaseModelView
If your backend does not support sorting, return
`None` or an empty dictionary.
4. :meth:`~flask.ext.admin.model.BaseModelView.init_search`
4. :meth:`~flask_admin.model.BaseModelView.init_search`
Initialize search functionality. If your backend supports
full-text search, do initializations and return `True`.
......@@ -76,7 +78,7 @@ Extending BaseModelView
it will add a join, etc) and caches this information for
future use.
5. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_form`
5. :meth:`~flask_admin.model.BaseModelView.scaffold_form`
Generate `WTForms` form class from the model.
......@@ -90,7 +92,7 @@ Extending BaseModelView
# Do something
return MyForm
6. :meth:`~flask.ext.admin.model.BaseModelView.get_list`
6. :meth:`~flask_admin.model.BaseModelView.get_list`
This method should return list of model instances with paging,
sorting, etc applied.
......@@ -120,27 +122,27 @@ Extending BaseModelView
6. Return count, list as a tuple
7. :meth:`~flask.ext.admin.model.BaseModelView.get_one`
7. :meth:`~flask_admin.model.BaseModelView.get_one`
Return a model instance by its primary key.
8. :meth:`~flask.ext.admin.model.BaseModelView.create_model`
8. :meth:`~flask_admin.model.BaseModelView.create_model`
Create a new instance of the model from the `Form` object.
9. :meth:`~flask.ext.admin.model.BaseModelView.update_model`
9. :meth:`~flask_admin.model.BaseModelView.update_model`
Update the model instance with data from the form.
10. :meth:`~flask.ext.admin.model.BaseModelView.delete_model`
10. :meth:`~flask_admin.model.BaseModelView.delete_model`
Delete the specified model instance from the data store.
11. :meth:`~flask.ext.admin.model.BaseModelView.is_valid_filter`
11. :meth:`~flask_admin.model.BaseModelView.is_valid_filter`
Verify whether the given object is a valid filter.
12. :meth:`~flask.ext.admin.model.BaseModelView.scaffold_filters`
12. :meth:`~flask_admin.model.BaseModelView.scaffold_filters`
Return a list of filter objects for one model field.
......@@ -166,17 +168,17 @@ Implementing filters
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.ext.admin.model.filters.BaseFilter` which implements at least two methods:
The filter is a class derived from :class:`~flask_admin.model.filters.BaseFilter` which implements at least two methods:
1. :meth:`~flask.ext.admin.model.filters.BaseFilter.apply`
2. :meth:`~flask.ext.admin.model.filters.BaseFilter.operation`
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.ext.admin.contrib.sqla.filters.BaseSQLAFilter` class.
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.
......@@ -213,4 +215,4 @@ Implementing filters
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.
\ No newline at end of file
Also, if you get stuck, try taking a look at the SQLAlchemy model backend and use it as a reference.
This diff is collapsed.
``flask.ext.admin.actions``
===========================
``flask_admin.actions``
=======================
.. automodule:: flask.ext.admin.actions
.. automodule:: flask_admin.actions
.. autofunction:: action
......
``flask.ext.admin.base``
========================
``flask_admin.base``
====================
.. automodule:: flask.ext.admin.base
.. automodule:: flask_admin.base
Base View
---------
......
``flask.ext.admin.contrib.fileadmin``
=====================================
``flask_admin.contrib.fileadmin``
=================================
.. automodule:: flask.ext.admin.contrib.fileadmin
.. automodule:: flask_admin.contrib.fileadmin
.. autoclass:: FileAdmin
:members:
......
``flask.ext.admin.contrib.mongoengine``
=======================================
``flask_admin.contrib.mongoengine``
===================================
MongoEngine model backend implementation.
.. automodule:: flask.ext.admin.contrib.mongoengine
.. automodule:: flask_admin.contrib.mongoengine
.. autoclass:: ModelView
:members:
......@@ -12,7 +12,7 @@ MongoEngine model backend implementation.
filter_converter, model_form_converter
allowed_search_types, form_subdocuments
Class inherits configuration options from :class:`~flask.ext.admin.model.BaseModelView` and they're not displayed here.
Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.
.. autoattribute:: column_filters
.. autoattribute:: column_type_formatters
......
``flask.ext.admin.contrib.mongoengine.fields``
==============================================
``flask_admin.contrib.mongoengine.fields``
==========================================
.. automodule:: flask.ext.admin.contrib.mongoengine.fields
.. automodule:: flask_admin.contrib.mongoengine.fields
.. autoclass:: ModelFormField
:members:
......@@ -11,4 +11,3 @@
.. autoclass:: MongoImageField
:members:
``flask.ext.admin.contrib.peewee``
==================================
``flask_admin.contrib.peewee``
==============================
Peewee model backend implementation.
.. automodule:: flask.ext.admin.contrib.peewee
.. automodule:: flask_admin.contrib.peewee
.. autoclass:: ModelView
:members:
......@@ -11,7 +11,7 @@ Peewee model backend implementation.
: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.ext.admin.model.BaseModelView` and they're not displayed here.
Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.
.. autoattribute:: column_filters
.. autoattribute:: filter_converter
......
``flask.ext.admin.contrib.pymongo``
===================================
``flask_admin.contrib.pymongo``
===============================
PyMongo model backend implementation.
.. automodule:: flask.ext.admin.contrib.pymongo
.. automodule:: flask_admin.contrib.pymongo
.. autoclass:: ModelView
:members:
:inherited-members:
:exclude-members: column_filters
Class inherits configuration options from :class:`~flask.ext.admin.model.BaseModelView` and they're not displayed here.
Class inherits configuration options from :class:`~flask_admin.model.BaseModelView` and they're not displayed here.
.. autoattribute:: column_filters
``flask.ext.admin.contrib.sqla``
================================
``flask_admin.contrib.sqla``
============================
SQLAlchemy model backend implementation.
.. automodule:: flask.ext.admin.contrib.sqla
.. automodule:: flask_admin.contrib.sqla
.. autoclass:: ModelView
:members:
......@@ -15,7 +15,7 @@ SQLAlchemy model backend implementation.
inline_models, form_choices,
form_optional_types
Class inherits configuration options from :class:`~flask.ext.admin.model.BaseModelView` and they're not displayed here.
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
......
``flask.ext.admin.form``
========================
``flask_admin.form``
====================
.. automodule:: flask.ext.admin.form
.. automodule:: flask_admin.form
.. autoclass:: BaseForm
:members:
``flask.ext.admin.form.fields``
===============================
``flask_admin.form.fields``
===========================
.. automodule:: flask.ext.admin.form.fields
.. automodule:: flask_admin.form.fields
.. autoclass:: TimeField
:members:
......
``flask.ext.admin.form.rules``
==============================
``flask_admin.form.rules``
==========================
.. automodule:: flask.ext.admin.form.rules
.. automodule:: flask_admin.form.rules
.. autoclass:: BaseRule
:members: __init__
......@@ -35,4 +35,3 @@
.. autoclass:: FieldSet
:members: __init__
``flask.ext.admin.form.upload``
===============================
``flask_admin.form.upload``
===========================
.. automodule:: flask.ext.admin.form.upload
.. automodule:: flask_admin.form.upload
.. autoclass:: FileUploadField
:members: __init__
......@@ -10,7 +10,4 @@
:members: __init__
.. autoclass:: FileUploadInput
:members: __init__
.. autoclass:: ImageUploadInput
:members: __init__
``flask.ext.admin.helpers``
===========================
``flask_admin.helpers``
=======================
.. automodule:: flask.ext.admin.helpers
.. automodule:: flask_admin.helpers
.. autofunction:: get_current_view
......@@ -17,4 +17,3 @@
.. autofunction:: resolve_ctx
.. autofunction:: get_render_ctx
``flask.ext.admin.model``
=========================
``flask_admin.model``
=====================
.. automodule:: flask.ext.admin.model
.. automodule:: flask_admin.model
.. autoclass:: BaseModelView
:members:
......
``flask.ext.admin.model.template``
==================================
``flask_admin.model.template``
==============================
.. automodule:: flask.ext.admin.model.template
.. automodule:: flask_admin.model.template
.. autofunction:: macro
``flask.ext.admin.tools``
=========================
``flask_admin.tools``
=====================
.. automodule:: flask.ext.admin.tools
.. automodule:: flask_admin.tools
.. autofunction:: import_module
.. autofunction:: import_attribute
.. autofunction:: module_not_found
.. autofunction:: rec_getattr
Changelog
=========
1.0.7
1.4.2
-----
Full change log and feature walkthrough can be found `here <http://mrjoes.github.io/2013/10/21/flask-admin-107.html>`_.
* Small bug fix release. Fixes regression that prevented usage of "virtual" columns with a custom formatter.
Highlights:
1.4.1
-----
* Python 3 support
* AJAX-based foreign-key data loading for all backends
* New, optional, rule-based form rendering engine
* MongoEngine fixes and features: GridFS support, nested subdocument configuration and much more
* Greatly improved and more configurable inline models
* New WTForms fields and widgets
* `form_extra_columns` allows adding custom columns to the form declaratively
* Redis cli
* SQLAlchemy backend can handle inherited models with multiple PKs
* Lots of bug fixes
* 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.0.6
1.4.0
-----
* Model views now support default sorting order
* Model type/column formatters now accept additional `view` parameter
* `is_visible` for administrative views
* Model views have `after_model_change` method that can be overridden
* In model views, `get_query` was split into `get_count_query` and `get_query`
* Bootstrap 2.3.1
* Bulk deletes go through `delete_model`
* Flask-Admin no longer uses floating navigation bar
* Translations: French, Persian (Farsi), Chinese (Simplified/Traditional), Czech
* 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.0.5
1.3.0
-----
* SQLAlchemy 0.8 support
* Choices and PostgreSQL Enum field type support
* Flask-BabelEx will be used to localize administrative interface
* Simple text file editor
* File admin has additional hooks: rename, edit, upload, etc
* Simple text file editor
* External links in menu
* Column descriptions
* Possibility to override master template
* Reworked templates. New 'layout' sample with completely different administrative UI
* Ability to customize wtforms widget rendering through `form_widget_args` property
* German translation (WIP)
* Updated documentation
* Lots of bug fixes
* 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
......@@ -43,7 +43,7 @@ master_doc = 'index'
# General information about the project.
project = u'flask-admin'
copyright = u'2012-2013, Serge S. Koval'
copyright = u'2012-2015, Serge S. Koval'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
......@@ -137,9 +137,8 @@ html_last_updated_fmt = '%b %d, %Y'
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'],
'**': ['localtoc.html', 'relations.html',
'sourcelink.html', 'searchbox.html']
'index': ['sidebarintro.html', 'searchbox.html'],
'**': ['toc.html', 'relations.html', 'searchbox.html']
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
......@@ -179,10 +178,10 @@ htmlhelp_basename = 'flask-admin'
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
......@@ -191,7 +190,7 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'flask-admin', u'Flask-Admin documentation',
('index', 'flask-admin.tex', u'Flask-Admin documentation',
u'Serge S. Koval', 'manual'),
]
......
Database backends
=================
The purpose of Flask-Admin is to help you manage your data. For this, it needs some database backend in order to be
able to access that data in the first place. At present, there are four different backends for you to choose
from, depending on which database you would like to use for your application.
.. toctree::
:maxdepth: 2
db_sqla
db_mongoengine
db_peewee
db_pymongo
If you don't know where to start, but you're familiar with relational databases, then you should probably look at using
`SQLAlchemy <http://www.sqlalchemy.org/>`_. 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're looking for something simpler, or your data models are reasonably self-contained, then
`MongoEngine <http://mongoengine.org/>`_ could be a better option. It is a python wrapper around the popular
*NoSQL* database called `MongoDB <http://www.mongodb.org/>`_.
Of course, if you feel that there's an awesome database wrapper that is missing from the list above, we'd greatly
appreciate it if you could write the plugin for it and submit it as a pull request. A special section of these docs
are dedicated to helping you through this process. See :doc:`model_guidelines`.
MongoEngine backend
===================
Features:
- MongoEngine 0.7+ support
- Paging, sorting, filters, etc
- Supports complex document structure (lists, subdocuments and so on)
- GridFS support for file and image uploads
In order to use MongoEngine integration, you need to install the `flask-mongoengine` package,
as Flask-Admin uses form scaffolding from it.
You don't have to use Flask-MongoEngine in your project - Flask-Admin will work with "raw"
MongoEngine models without any problems.
Known issues:
- Search functionality can't split query into multiple terms due to
MongoEngine query language limitations
For more documentation, check :doc:`api/mod_contrib_mongoengine` documentation.
MongoEngine integration example is `here <https://github.com/mrjoes/flask-admin/tree/master/examples/mongoengine>`_.
Peewee backend
==============
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` and `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 documentation, check :doc:`api/mod_contrib_peewee` documentation.
Peewee example is `here <https://github.com/mrjoes/flask-admin/tree/master/examples/peewee>`_.
PyMongo backend
===============
Pretty simple PyMongo backend.
Flask-Admin does not make assumptions about document structure, so you
will have to configure ModelView to do what you need it to do.
This is bare minimum you have to provide for Flask-Admin view to work
with PyMongo:
1. Provide list of columns by setting `column_list` property
2. Provide form to use by setting `form` property
3. When instantiating :class:`flask.ext.admin.contrib.pymongo.ModelView` class, you have to provide PyMongo collection object
This is minimal PyMongo view::
class UserForm(Form):
name = TextField('Name')
email = TextField('Email')
class UserView(ModelView):
column_list = ('name', 'email')
form = UserForm
if __name__ == '__main__':
admin = Admin(app)
# '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 documentation, check :doc:`api/mod_contrib_pymongo` documentation.
PyMongo integration example is `here <https://github.com/mrjoes/flask-admin/tree/master/examples/pymongo>`_.
SQLAlchemy backend
==================
Flask-Admin comes with SQLAlchemy ORM backend.
Notable features:
- SQLAlchemy 0.6+ support
- Paging, sorting, filters
- Proper model relationship handling
- Inline editing of related models
Getting Started
---------------
In order to use SQLAlchemy model scaffolding, you need to have:
1. SQLAlchemy ORM `model <http://docs.sqlalchemy.org/en/rel_0_8/orm/tutorial.html#declare-a-mapping>`_
2. Initialized database `session <http://docs.sqlalchemy.org/en/rel_0_8/orm/tutorial.html#creating-a-session>`_
If you use Flask-SQLAlchemy, this is how you initialize Flask-Admin
and get session from the `SQLAlchemy` object::
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.admin import Admin
from flask.ext.admin.contrib.sqla import ModelView
app = Flask(__name__)
# .. read settings
db = SQLAlchemy(app)
# .. model declarations here
if __name__ == '__main__':
admin = Admin(app)
# .. add ModelViews
# admin.add_view(ModelView(SomeModel, db.session))
Creating simple model
---------------------
Using previous template, lets create simple model::
# .. flask initialization
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
email = db.Column(db.String(128))
if __name__ == '__main__':
admin = Admin(app)
admin.add_view(ModelView(User, db.session))
db.create_all()
app.run('0.0.0.0', 8000)
If you will run this example and open `http://localhost:8000/ <http://localhost:8000/>`_,
you will see that Flask-Admin generated administrative page for the
model:
.. image:: images/sqla/sqla_1.png
:width: 640
:target: ../_images/sqla_1.png
You can add new models, edit existing, etc.
Customizing administrative interface
------------------------------------
List view can be customized in different ways.
First of all, you can use various class-level properties to configure
what should be displayed and how. For example, :attr:`~flask.ext.admin.contrib.sqla.ModelView.column_list` can be used to show some of
the column or include extra columns from related models.
For example::
class UserView(ModelView):
# Show only name and email columns in list view
column_list = ('name', 'email')
# Enable search functionality - it will search for terms in
# name and email fields
column_searchable_list = ('name', 'email')
# Add filters for name and email columns
column_filters = ('name', 'email')
Alternatively, you can override some of the :class:`~flask.ext.admin.contrib.sqla.ModelView` methods and implement your custom logic.
For example, if you need to contribute additional field to the generated form,
you can do something like this::
class UserView(ModelView):
def scaffold_form(self):
form_class = super(UserView, self).scaffold_form()
form_class.extra = TextField('Extra')
return form_class
Check :doc:`api/mod_contrib_sqla` documentation for list of
configuration properties and methods.
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.
Lets Model a car with its tyres::
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::
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.
A standalone script with the Examples from above can be found in the examples directory.
Example
-------
Flask-Admin comes with relatively advanced example, which you can
see `here <https://github.com/mrjoes/flask-admin/tree/master/examples/sqla>`_.
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.
This guide will help you to get acquainted with the Flask-Admin library. It is assumed that you have some prior
knowledge of `Flask <http://flask.pocoo.org/>`_ .
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 <http://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, MongoEngine, Peewee, raw pymongo), the developer is even
free to mix different model types into one application by instantiating appropriate CRUD classes.
Getting started
---------------
At the basis of Flask-Admin is the idea that you can add components to your admin interface by declaring a separate
class for each component, and then adding a method to that class for every view that should be associated to the
component. Since classes can inherit from one another, and since several instances of the same class can be created,
this approach allows for a great deal of flexibility.
Let's write a bit of code to create a simple CRUD interface for the `Post` SQLAlchemy model. The example below uses the
Flask-SQLAlchemy extension, but you don't have to use it (you could also use the SQLAlchemy declarative extension)::
from flask import Flask
from flask.ext.admin import Admin
from flask.ext.admin.contrib.sqla import ModelView
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Unicode(120))
text = db.Column(db.UnicodeText, nullable=False)
admin = Admin(app)
admin.add_view(ModelView(Post, db.session))
To customize the behavior of the model's CRUD interface, you can set values for some of the special
properties (as listed below) that are made available through `model.BaseModelView`, or one of the ORM wrappers::
# ... imports
class PostView(ModelView):
list_columns = ('title',)
def __init__(self):
super(PostView, self).__init__(Post, db.session)
# ... initialization
admin.add_view(PostView())
Because each component is implemented as a class, you can also customize it in the constructor::
class PostView(ModelView):
list_columns = ('title',)
def __init__(self, include_id=False):
if include_id:
self.list_columns = ('id', 'title')
super(PostView, self).__init__(Post, db.session)
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 :doc:`api/mod_actions`
exclude :attr:`~flask.ext.admin.model.BaseModelView.form_excluded_columns`
fields :attr:`~flask.ext.admin.model.BaseModelView.form_columns`
form :attr:`~flask.ext.admin.model.BaseModelView.form`
formfield_overrides :attr:`~flask.ext.admin.model.BaseModelView.form_args`
inlines :attr:`~flask.ext.admin.contrib.sqlalchemy.ModelView.inline_models`
list_display :attr:`~flask.ext.admin.model.BaseModelView.column_list`
list_filter :attr:`~flask.ext.admin.contrib.sqlalchemy.ModelView.column_filters`
list_per_page :attr:`~flask.ext.admin.model.BaseModelView.page_size`
search_fields :attr:`~flask.ext.admin.model.BaseModelView.column_searchable_list`
add_form_template :attr:`~flask.ext.admin.model.BaseModelView.create_template`
change_form_template :attr:`~flask.ext.admin.model.BaseModelView.change_form_template`
=========================================== ==============================================
You might want to check :doc:`api/mod_model` for basic model configuration options (reused by all model
backends) and specific backend documentation, for example :doc:`api/mod_contrib_sqla`. There's much more
than what is displayed in this table.
Authentication
--------------
To restrict access to your admin interface, you can implement your own class for creating admin components, and
override the `is_accessible` method::
class MyModelView(ModelView):
def is_accessible(self):
return login.current_user.is_authenticated()
Components that are not accessible to a particular user, will also not be displayed in the menu for that user.
Templates
---------
Flask-Admin uses Jinja2 templating engine. Jinja2 is pretty advanced templating engine and Flask-Admin templates were made
to be easily extensible.
For example, if you need to include a javascript snippet on the *Edit* page for one of your models, you could::
{% extends 'admin/model/edit.html' %}
{% block tail %}
{{ super() }}
<script language="javascript">alert('Hello World!')</script>
{% endblock %}
and then point your class to this new template::
class MyModelView(ModelView):
edit_template = 'my_edit_template.html'
For list of available template blocks, check :doc:`templates`.
Tips and hints
--------------
1. Programming with Flask-Admin is not very different from normal application development - write some views and expose
them to the user, using templates to create a consistent user experience.
2. If you are missing some functionality which can be used more than once, you can create your own "base" class and use
it instead of default implementation.
3. Using Jinja2, you can easily extend the existing templates. You can even change the look and feel of the admin
interface completely, if you want to. Check `this example <https://github.com/mrjoes/flask-admin/tree/master/examples/layout>`_.
4. You are not limited to a simple CRUD interface for every model. Want to add some kind of realtime monitoring via websockets? No problem.
5. There's a so called "index view". By default it is empty, but you can put any information you need there. It is displayed
under the *Home* menu option.
Form 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 that 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 abstract the rendering, so that it becomes separate from the form definition. So,
for example, it no longer matters in which sequence yur form fields are defined.
Getting started
---------------
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::
class RuleView(sqla.ModelView):
form_create_rules = ('email', '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.ext.admin.form.rules.Field` class instance for that field.
Lets say we want to display some text between the `email` and `first_name` fields. This can be accomplished by
using the :class:`flask.ext.admin.form.rules.Text` class::
from flask.ext.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.ext.admin.form.rules` module:
======================================================= ========================================================
Form Rendering Rule Description
======================================================= ========================================================
:class:`flask.ext.admin.form.rules.BaseRule` All rules derive from this class
:class:`flask.ext.admin.form.rules.NestedRule` Allows rule nesting, useful for HTML containers
:class:`flask.ext.admin.form.rules.Text` Simple text rendering rule
:class:`flask.ext.admin.form.rules.HTML` Same as `Text` rule, but does not escape the text
:class:`flask.ext.admin.form.rules.Macro` Calls macro from current Jinja2 context
:class:`flask.ext.admin.form.rules.Container` Wraps child rules into container rendered by macro
:class:`flask.ext.admin.form.rules.Field` Renders single form field
:class:`flask.ext.admin.form.rules.Header` Renders form header
:class:`flask.ext.admin.form.rules.FieldSet` Renders form header and child rules
======================================================= ========================================================
Further reading
---------------
For additional documentation, check :mod:`flask.ext.admin.form.rules` module source code (it is quite short) and
look at the `forms example <https://github.com/mrjoes/flask-admin/tree/master/examples/forms>`_ on GitHub.
:tocdepth: 2
Flask-Admin
===========
###########
**Why Flask?** As a micro-framework, `Flask <http://flask.pocoo.org/>`_ 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.
Flask-Admin is a batteries-included, simple-to-use `Flask <http://flask.pocoo.org/>`_ 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 of the look, feel and functionality of the resulting application.
**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.
You can see some examples of how Flask-Admin can be used at `http://examples.flask-admin.org <http://examples.flask-admin.org/>`_.
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.
Browse through the documentation below to learn more about what you can do with Flask-Admin. Or head over to
`our GitHub repository <http://github.com/mrjoes/flask-admin>`_ to find out how you can contribute to the project.
**What does it look like?** At http://examples.flask-admin.org/ you can see
some examples of Flask-Admin in action, or browse through the `examples/`
directory in the `GitHub repository <https://github.com/flask-admin/flask-admin>`_.
.. toctree::
:maxdepth: 2
quickstart
django_migration
templates
localization
tips
db
model_guidelines
form_rules
introduction
advanced
adding_a_new_model_backend
api/index
changelog
renamed_columns
Support
-------
Indices and tables
****
Python 2.6 - 2.7 and 3.3 - 3.4.
Indices And Tables
------------------
****
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
This diff is collapsed.
Localization
============
Flask-Admin makes it possible for you to serve your application in more than one language. To do this, it makes use of
the `Flask-BabelEx <http://github.com/mrjoes/flask-babelex/>`_ package for handling translations. This package is a
fork of the popular `Flask-Babel <http://github.com/mitshuhiko/flask-babel/>`_ package, with the following features:
1. It is API-compatible with Flask-Babel
2. It allows distribution of translations with Flask extensions
3. It aims to be more configurable than Flask-Babel
Currently *Flask-BabelEx* is the only supported way of enabling localization support in Flask-Admin.
How to enable localization
--------------------------
1. Install Flask-BabelEx::
pip install flask-babelex
2. Initialize Flask-BabelEx by creating instance of `Babel` class::
from flask import app
from flask.ext.babelex import Babel
app = Flask(__name__)
babel = Babel(app)
3. Create a locale selector function::
@babel.localeselector
def get_locale():
# Put your logic here. Application can store locale in
# user profile, cookie, session, etc.
return 'en'
4. Initialize Flask-Admin as usual.
You can check the `babel` example to see localization in action. When running this example, you can change the
locale simply by adding a query parameter, like *?en=<locale name>* to the URL. For example, a French version of
the application should be accessible at:
`http://localhost:5000/admin/userview/?lang=fr <http://localhost:5000/admin/userview/?lang=fr>`_.
This diff is collapsed.
Renamed Columns
---------------
Starting from version 1.0.4, Flask-Admin uses different configuration
property names.
Please update your sources as support for old property names will be
removed in future Flask-Admin versions.
=========================== =============================
**Old Name** **New name**
--------------------------- -----------------------------
list_columns column_list
excluded_list_columns column_exclude_list
list_formatters column_formatters
list_type_formatters column_type_formatters
rename_columns column_labels
sortable_columns column_sortable_list
searchable_columns column_searchable_list
list_display_pk column_display_pk
auto_select_related column_auto_select_related
list_select_related column_select_related_list
list_display_all_relations column_display_all_relations
excluded_form_columns form_excluded_columns
disallowed_actions action_disallowed_list
=========================== =============================
Flask>=0.7
Flask-WTF>=0.6
Flask-SQLAlchemy>=0.15
peewee
wtf-peewee
flask-mongoengine
\ No newline at end of file
Working with templates
======================
One great advantage of building an extension on top of Flask, is the great templating engine that
comes with the package. Jinja2 allows you to use most of the Python syntax that you are used to, inside
of your templates, helping you generate either text or code in a powerful, yet flexible way.
To explore some more of what Jinja2 can offer you, head over to their documentation at
`http://jinja.pocoo.org/docs/ <http://jinja.pocoo.org/docs/>`_. But the most important features for you to
understand in order to get started with Flask-Admin are given below.
Inheritance
-----------
Templates can extend other templates. This enables you, for example, to build the standard components of
your site into a *base* template, where they are defined only once. This template can then be extended by
other templates, where more specific content may be added.
Large applications may end up having several layers of templates, starting for example with a very general HTML
structure, and then growing more and more specific at each level, until the final layer of templates define unique
pages in the application. But it needs not be very complicated, and the majority of applications will only really
need a handful of well-designed templates.
Building blocks
---------------
With Jinja2, templates are made up of *blocks* of code, which define where a child template's contents fit into the
bigger picture, as defined by the parent template.
A parent template may define any number of these code blocks, and a child template may define content for any number
of those. So, by extending an existing template, you get to just fill-in the blanks, rather than having to deal
with lots of boilerplate code that is not really relevant to the problem at hand.
Power & Flexibility
-------------------
When a block is defined in a parent template, it can already be given some content, ensuring that something
will be rendered in that place, even if a child template chooses to ignore that block completely.
If content is defined in a child template, you have the option of also rendering the code that the parent template
may have defined in that block by calling::
{{ super() }}
anywhere inside that block. But the default behaviour is to simply override the block entirely.
Since these template blocks are defined by name, you have a lot of freedom in how you decide to arrange / nest them
in your code.
Jinja2 & Flask Admin
--------------------
Flask-Admin defines one *base* template at `admin/master.html` that all the 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
body Content (that's where your view will be displayed)
tail Empty area below content
============== ========================================================================
Adding an Index Page
--------------------
You'll notice that the 'Home' page that is created by Flask-Admin at `/admin` is largely empty. By default, the
only content on the page is a set of controls for navigating to the views that you have defined. You can change this by
creating a template at `admin/index.html` in your `templates` directory.
Working with your Models
------------------------
By default, Flask-Admin uses three pre-defined templates for displaying your models in the admin-interface:
* `admin/model/list.html`
* `admin/model/create.html`
* `admin/model/edit.html`
All three of these extend the `admin/master.html` template, and you can override them by defining your own templates,
with the same path relative to your `templates` folder.
You could also choose to extend these templates, rather than overriding them. In this case you will need to
point your classes at your own templates, rather than letting them use the defaults. For example, your own template
for the *edit* views could be defined in `templates/my_edit_template.html` to look something like::
{% extends 'admin/model/edit.html' %}
{% block tail %}
{{ super() }}
...
{% endblock %}
And your classes could be made to use this template by setting the appropriate class property::
class MyModelView(ModelView):
edit_template = 'my_edit_template.html'
The three available properties are simply called `list_template`, `create_template` and `edit_template`.
Environment variables
---------------------
While working in any of the templates that extend `admin/master.html`, you have access to a small number of
environment variables:
==================== ================================
Variable Name Description
==================== ================================
admin_view Current administrative view
admin_base_template Base template name
_gettext Babel gettext
_ngettext Babel ngettext
h Helpers from :mod:`~flask.ext.admin.helpers` module
==================== ================================
Customizing templates
---------------------
As noted earlier, you can override any default Flask-Admin template by creating your own template with same name and
relative path inside your own `templates` directory.
You can also override the master template, but then you need to pass your own template name to the `Admin`
constructor::
admin = Admin(app, base_template='my_master.html')
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
model_list_table Table container
list_header Table header row
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
======================= ============================================
\ No newline at end of file
Usage Tips
==========
General tips
------------
1. A reasonably obvious, but very useful, pattern is to wrap any shared functionality that your different admin views
might need into a base class that they can all inherit from (to help you keep things
`DRY <http://en.wikipedia.org/wiki/Don't_repeat_yourself>`_).
For example, rather than manually checking user permissions in each of your admin views, you can implement a
base class such as ::
class MyView(BaseView):
def is_accessible(self):
return login.current_user.is_authenticated()
and every view that inherits from this, will have the permission checking done automatically. The important thing
to notice, is that your base class needs to inherit from a built-in Flask-Admin view.
2. You can override a default template either by passing the path to your own template in to the relevant `ModelView`
property (either `list_template`, `create_template` or `edit_template`) or by putting your own customized
version of a default template into your `templates/admin/` directory.
3. To customize the overall look and feel of the default model forms, you have two options: Either, you could
override the default create/edit templates. Or, alternatively, you could make use of the form rendering rules
(:mod:`flask.ext.admin.form.rules`) that were introduced in version 1.0.7.
4. To simplify the management of file uploads, Flask-Admin comes with a dedicated tool, for which you can find
documentation at: :mod:`flask.ext.admin.form.upload`.
5. 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.ext.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.
SQLAlchemy
----------
1. 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::
class MyView(ModelView):
def scaffold_form(self):
form_class = super(UserView, self).scaffold_form()
form_class.extra = TextField('Extra')
return form_class
MongoEngine
-----------
1. Flask-Admin supports GridFS-backed image- and file uploads, done through WTForms fields. Documentation can be found
at :mod:`flask.ext.admin.contrib.mongoengine.fields`.
from flask import Flask
import flask_admin
from flask_admin.contrib import appengine
from google.appengine.ext import db
from google.appengine.ext import ndb
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
admin = flask_admin.Admin(app, name="Admin")
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Car(db.Model):
owner = db.StringProperty()
make_model = db.StringProperty()
indexed_int = db.IntegerProperty()
unindexed_int = db.IntegerProperty(indexed=False)
class Tire(db.Model):
car = db.ReferenceProperty(Car)
position = db.StringProperty()
class House(ndb.Model):
address = db.StringProperty()
json_property = ndb.JsonProperty()
indexed_int = ndb.IntegerProperty()
unindexed_int = ndb.IntegerProperty(indexed=False)
text_property = ndb.TextProperty()
datetime_property = ndb.DateTimeProperty()
class Room(ndb.Model):
house = ndb.KeyProperty(kind=House)
name = ndb.StringProperty()
admin.add_view(appengine.ModelView(Car))
admin.add_view(appengine.ModelView(Tire))
admin.add_view(appengine.ModelView(House))
admin.add_view(appengine.ModelView(Room))
if __name__ == '__main__':
# Start app
app.run(debug=True)
runtime: python27
threadsafe: true
api_version: 1
module: default
handlers:
- url: /admin.*
script: app.app
# This file gets imported as part of running dev_appserver.py
import sys
sys.path = ['lib'] + sys.path
BASEDIR=$(dirname $0)
# Install wtforms-admin to our lib/ directory, using our local source tree
pip install -t $BASEDIR/lib/ $BASEDIR/../.. wtforms_appengine
# Now run our server
dev_appserver.py $BASEDIR/app.yaml
This example shows how to integrate Flask-Login authentication with Flask-Admin using the SQLAlchemy backend.
To run this example:
1. Clone the repository::
git clone https://github.com/flask-admin/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env
source env/bin/activate
3. Install requirements::
pip install -r 'examples/auth-flask-login/requirements.txt'
4. Run the application::
python examples/auth-flask-login/app.py
The first time you run this example, a sample sqlite database gets populated automatically. To suppress this behaviour,
comment the following lines in app.py:::
if not os.path.exists(database_path):
build_sample_db()
import os
from flask import Flask, url_for, redirect, render_template, request
from flask.ext.sqlalchemy import SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
from wtforms import form, fields, validators
from flask.ext import admin, login
from flask.ext.admin.contrib import sqla
from flask.ext.admin import helpers, expose
import flask_admin as admin
import flask_login as login
from flask_admin.contrib import sqla
from flask_admin import helpers, expose
from werkzeug.security import generate_password_hash, check_password_hash
# Create Flask application
......@@ -20,8 +22,7 @@ app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Create user model. For simplicity, it will store passwords in plain text.
# Obviously that's not right thing to do in real world application.
# Create user model.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(100))
......@@ -50,7 +51,7 @@ class User(db.Model):
# Define login and registration forms (for flask-login)
class LoginForm(form.Form):
login = fields.TextField(validators=[validators.required()])
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
......@@ -59,7 +60,10 @@ class LoginForm(form.Form):
if user is None:
raise validators.ValidationError('Invalid user')
if user.password != self.password.data:
# we're comparing the plaintext pw with the the hash from the db
if not check_password_hash(user.password, self.password.data):
# to compare plain text passwords use
# if user.password != self.password.data:
raise validators.ValidationError('Invalid password')
def get_user(self):
......@@ -67,8 +71,8 @@ class LoginForm(form.Form):
class RegistrationForm(form.Form):
login = fields.TextField(validators=[validators.required()])
email = fields.TextField()
login = fields.StringField(validators=[validators.required()])
email = fields.StringField()
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
......@@ -91,7 +95,7 @@ def init_login():
class MyModelView(sqla.ModelView):
def is_accessible(self):
return login.current_user.is_authenticated()
return login.current_user.is_authenticated
# Create customized index view class that handles login & registration
......@@ -99,7 +103,7 @@ class MyAdminIndexView(admin.AdminIndexView):
@expose('/')
def index(self):
if not login.current_user.is_authenticated():
if not login.current_user.is_authenticated:
return redirect(url_for('.login_view'))
return super(MyAdminIndexView, self).index()
......@@ -111,7 +115,7 @@ class MyAdminIndexView(admin.AdminIndexView):
user = form.get_user()
login.login_user(user)
if login.current_user.is_authenticated():
if login.current_user.is_authenticated:
return redirect(url_for('.index'))
link = '<p>Don\'t have an account? <a href="' + url_for('.register_view') + '">Click here to register.</a></p>'
self._template_args['form'] = form
......@@ -125,6 +129,9 @@ class MyAdminIndexView(admin.AdminIndexView):
user = User()
form.populate_obj(user)
# we hash the users password to avoid saving it as plaintext in the db,
# remove to use plain text:
user.password = generate_password_hash(form.password.data)
db.session.add(user)
db.session.commit()
......@@ -152,7 +159,7 @@ def index():
init_login()
# Create admin
admin = admin.Admin(app, 'Auth', index_view=MyAdminIndexView(), base_template='my_master.html')
admin = admin.Admin(app, 'Example: Auth', index_view=MyAdminIndexView(), base_template='my_master.html')
# Add view
admin.add_view(MyModelView(User, db.session))
......@@ -168,7 +175,9 @@ def build_sample_db():
db.drop_all()
db.create_all()
test_user = User(login="test", password="test")
# passwords are hashed, to use plaintext passwords instead:
# test_user = User(login="test", password="test")
test_user = User(login="test", password=generate_password_hash("test"))
db.session.add(test_user)
first_names = [
......@@ -188,7 +197,7 @@ def build_sample_db():
user.last_name = last_names[i]
user.login = user.first_name.lower()
user.email = user.login + "@example.com"
user.password = ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(10))
user.password = generate_password_hash(''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(10)))
db.session.add(user)
db.session.commit()
......
Flask
Flask-Admin
Flask-SQLAlchemy
Flask-Login>=0.3.0
{% extends 'admin/master.html' %}
{% block body %}
{{ super() }}
<div class="row-fluid">
<div>
{% if current_user.is_authenticated %}
<h1>Flask-Admin example</h1>
<p class="lead">
Authentication
</p>
<p>
This example shows how you can use Flask-Login for authentication. It is only intended as a basic demonstration.
</p>
{% else %}
<form method="POST" action="">
{{ form.hidden_tag() if form.hidden_tag }}
{% for f in form if f.type != 'CSRFTokenField' %}
<div>
{{ f.label }}
{{ f }}
{% if f.errors %}
<ul>
{% for e in f.errors %}
<li>{{ e }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
<button class="btn" type="submit">Submit</button>
</form>
{{ link | safe }}
{% endif %}
</div>
<a class="btn btn-primary" href="/"><i class="icon-arrow-left icon-white"></i> Back</a>
</div>
{% endblock body %}
<html>
<body>
<div>
<a href="{{ url_for('admin.index') }}">Go to admin!</a>
</div>
</body>
</html>
{% extends 'admin/base.html' %}
{% block access_control %}
{% if current_user.is_authenticated %}
<div class="btn-group pull-right">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-user"></i> {{ current_user.login }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('admin.logout_view') }}">Log out</a></li>
</ul>
</div>
{% endif %}
{% endblock %}
This example shows how to integrate Flask-Login authentication with the Flask-Admin using MongoEngine backend.
This example shows how to integrate Flask-Login authentication with Flask-Admin using the MongoEngine backend.
To run this example:
1. Clone the repository::
git clone https://github.com/flask-admin/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env
source env/bin/activate
3. Install requirements::
pip install -r 'examples/auth-mongoengine/requirements.txt'
4. Run the application::
python examples/auth-mongoengine/app.py
from flask import Flask, url_for, redirect, render_template, request
from flask.ext.mongoengine import MongoEngine
from flask_mongoengine import MongoEngine
from wtforms import form, fields, validators
from flask.ext import admin, login
from flask.ext.admin.contrib.mongoengine import ModelView
from flask.ext.admin import helpers
import flask_admin as admin
import flask_login as login
from flask_admin.contrib.mongoengine import ModelView
# Create application
app = Flask(__name__)
......@@ -46,7 +46,7 @@ class User(db.Document):
# Define login and registration forms (for flask-login)
class LoginForm(form.Form):
login = fields.TextField(validators=[validators.required()])
login = fields.StringField(validators=[validators.required()])
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
......@@ -63,8 +63,8 @@ class LoginForm(form.Form):
class RegistrationForm(form.Form):
login = fields.TextField(validators=[validators.required()])
email = fields.TextField()
login = fields.StringField(validators=[validators.required()])
email = fields.StringField()
password = fields.PasswordField(validators=[validators.required()])
def validate_login(self, field):
......@@ -86,13 +86,13 @@ def init_login():
# Create customized model view class
class MyModelView(ModelView):
def is_accessible(self):
return login.current_user.is_authenticated()
return login.current_user.is_authenticated
# Create customized index view class
class MyAdminIndexView(admin.AdminIndexView):
def is_accessible(self):
return login.current_user.is_authenticated()
return login.current_user.is_authenticated
# Flask views
......@@ -137,7 +137,7 @@ if __name__ == '__main__':
init_login()
# Create admin
admin = admin.Admin(app, 'Auth', index_view=MyAdminIndexView())
admin = admin.Admin(app, 'Example: Auth-Mongo', index_view=MyAdminIndexView())
# Add view
admin.add_view(MyModelView(User))
......
Flask
Flask-Admin
flask-mongoengine
Flask-Login>=0.3.0
<html>
<body>
<div>
{% if user and user.is_authenticated() %}
{% if user and user.is_authenticated %}
Hello {{ user.login }}! <a href="{{ url_for('logout_view') }}">Logout</a>
{% else %}
Welcome anonymous user!
......
This example shows how to integrate Flask-Login authentication with the Flask-Admin using SQLAlchemy backend.
This example shows how to integrate Flask-Security (https://pythonhosted.org/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').
To run this example:
1. Clone the repository::
git clone https://github.com/flask-admin/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env
source env/bin/activate
3. Install requirements::
pip install -r 'examples/auth/requirements.txt'
4. Run the application::
python examples/auth/app.py
The first time you run this example, a sample sqlite database gets populated automatically. To suppress this behaviour,
comment the following lines in app.py:::
if not os.path.exists(database_path):
build_sample_db()
import os
from flask import Flask, url_for, redirect, render_template, request, abort
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, \
UserMixin, RoleMixin, login_required, current_user
from flask_security.utils import encrypt_password
import flask_admin
from flask_admin.contrib import sqla
from flask_admin import helpers as admin_helpers
# Create Flask application
app = Flask(__name__)
app.config.from_pyfile('config.py')
db = SQLAlchemy(app)
# Define models
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
def __str__(self):
return self.name
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
last_name = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
def __str__(self):
return self.email
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Create customized model view class
class MyModelView(sqla.ModelView):
def is_accessible(self):
if not current_user.is_active or not current_user.is_authenticated:
return False
if current_user.has_role('superuser'):
return True
return False
def _handle_view(self, name, **kwargs):
"""
Override builtin _handle_view in order to redirect users when a view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
# permission denied
abort(403)
else:
# login
return redirect(url_for('security.login', next=request.url))
# Flask views
@app.route('/')
def index():
return render_template('index.html')
# Create admin
admin = flask_admin.Admin(
app,
'Example: Auth',
base_template='my_master.html',
template_mode='bootstrap3',
)
# Add model views
admin.add_view(MyModelView(Role, db.session))
admin.add_view(MyModelView(User, db.session))
# define a context processor for merging flask-admin's template context into the
# flask-security views.
@security.context_processor
def security_context_processor():
return dict(
admin_base_template=admin.base_template,
admin_view=admin.index_view,
h=admin_helpers,
get_url=url_for
)
def build_sample_db():
"""
Populate a small db with some example entries.
"""
import string
import random
db.drop_all()
db.create_all()
with app.app_context():
user_role = Role(name='user')
super_user_role = Role(name='superuser')
db.session.add(user_role)
db.session.add(super_user_role)
db.session.commit()
test_user = user_datastore.create_user(
first_name='Admin',
email='admin',
password=encrypt_password('admin'),
roles=[user_role, super_user_role]
)
first_names = [
'Harry', 'Amelia', 'Oliver', 'Jack', 'Isabella', 'Charlie', 'Sophie', 'Mia',
'Jacob', 'Thomas', 'Emily', 'Lily', 'Ava', 'Isla', 'Alfie', 'Olivia', 'Jessica',
'Riley', 'William', 'James', 'Geoffrey', 'Lisa', 'Benjamin', 'Stacey', 'Lucy'
]
last_names = [
'Brown', 'Smith', 'Patel', 'Jones', 'Williams', 'Johnson', 'Taylor', 'Thomas',
'Roberts', 'Khan', 'Lewis', 'Jackson', 'Clarke', 'James', 'Phillips', 'Wilson',
'Ali', 'Mason', 'Mitchell', 'Rose', 'Davis', 'Davies', 'Rodriguez', 'Cox', 'Alexander'
]
for i in range(len(first_names)):
tmp_email = first_names[i].lower() + "." + last_names[i].lower() + "@example.com"
tmp_pass = ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(10))
user_datastore.create_user(
first_name=first_names[i],
last_name=last_names[i],
email=tmp_email,
password=encrypt_password(tmp_pass),
roles=[user_role, ]
)
db.session.commit()
return
if __name__ == '__main__':
# Build a sample db on the fly, if one does not exist yet.
app_dir = os.path.realpath(os.path.dirname(__file__))
database_path = os.path.join(app_dir, app.config['DATABASE_FILE'])
if not os.path.exists(database_path):
build_sample_db()
# Start app
app.run(debug=True)
# Create dummy secrey key so we can use sessions
SECRET_KEY = '123456790'
# Create in-memory database
DATABASE_FILE = 'sample_db.sqlite'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + DATABASE_FILE
SQLALCHEMY_ECHO = True
# Flask-Security config
SECURITY_URL_PREFIX = "/admin"
SECURITY_PASSWORD_HASH = "pbkdf2_sha512"
SECURITY_PASSWORD_SALT = "ATGUOHAELKiubahiughaerGOJAEGj"
# Flask-Security URLs, overridden because they don't put a / at the end
SECURITY_LOGIN_URL = "/login/"
SECURITY_LOGOUT_URL = "/logout/"
SECURITY_REGISTER_URL = "/register/"
SECURITY_POST_LOGIN_VIEW = "/admin/"
SECURITY_POST_LOGOUT_VIEW = "/admin/"
SECURITY_POST_REGISTER_VIEW = "/admin/"
# Flask-Security features
SECURITY_REGISTERABLE = True
SECURITY_SEND_REGISTER_EMAIL = False
\ No newline at end of file
Flask
Flask-Admin
Flask-SQLAlchemy
Flask-Security>=1.7.5
{% extends 'admin/master.html' %}
{% block body %}
{{ super() }}
<div class="row-fluid">
<div>
{% if current_user.is_authenticated() %}
<h1>Flask-Admin example</h1>
<p class="lead">
Authentication
</p>
<p>
This example shows how you can use Flask-Login for authentication. It is only intended as a basic demonstration, so please don't freak out when you see passwords being stored as plain text.
</p>
{% else %}
<form method="POST" action="">
{{ form.hidden_tag() if form.hidden_tag }}
{% for f in form if f.type != 'CSRFTokenField' %}
<div>
{{ f.label }}
{{ f }}
{% if f.errors %}
<div class="container">
<div class="row">
<div class="col-sm-10 col-sm-offset-1">
<h1>Flask-Admin example</h1>
<p class="lead">
Authentication
</p>
<p>
This example shows how you can use <a href="https://pythonhosted.org/Flask-Security/index.html" target="_blank">Flask-Security</a> for authentication.
</p>
{% if not current_user.is_authenticated %}
<p>You can register as a regular user, or log in as a superuser with the following credentials:
<ul>
{% for e in f.errors %}
<li>{{ e }}</li>
{% endfor %}
<li>email: <b>admin</b></li>
<li>password: <b>admin</b></li>
</ul>
<p>
<a class="btn btn-primary" href="{{ url_for('security.login') }}">login</a> <a class="btn btn-default" href="{{ url_for('security.register') }}">register</a>
</p>
{% endif %}
</div>
{% endfor %}
<button class="btn" type="submit">Submit</button>
</form>
{{ link | safe }}
{% endif %}
<p>
<a class="btn btn-primary" href="/"><i class="glyphicon glyphicon-chevron-left"></i> Back</a>
</p>
</div>
</div>
<a class="btn btn-primary" href="/"><i class="icon-arrow-left icon-white"></i> Back</a>
</div>
{% endblock body %}
\ No newline at end of file
{% endblock body %}
{% extends 'admin/base.html' %}
{% block access_control %}
{% if current_user.is_authenticated() %}
<div class="btn-group pull-right">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<i class="icon-user"></i> {{ current_user.login }} <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('admin.logout_view') }}">Log out</a></li>
{% if current_user.is_authenticated %}
<div class="navbar-text btn-group pull-right">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
<i class="glyphicon glyphicon-user"></i>
{% if current_user.first_name -%}
{{ current_user.first_name }}
{% else -%}
{{ current_user.email }}
{%- endif %}<span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{{ url_for('security.logout') }}">Log out</a></li>
</ul>
</div>
{% endif %}
{% endblock %}
\ No newline at end of file
{% endblock %}
{% macro render_field_with_errors(field) %}
<div class="form-group">
{{ field.label }} {{ field(class_='form-control', **kwargs)|safe }}
{% if field.errors %}
<ul>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endmacro %}
{% macro render_field(field) %}
<p>{{ field(class_='form-control', **kwargs)|safe }}</p>
{% endmacro %}
{% macro render_checkbox_field(field) -%}
<div class="form-group">
<div class="checkbox">
<label>
{{ field(type='checkbox', **kwargs) }} {{ field.label }}
</label>
</div>
</div>
{%- endmacro %}
\ No newline at end of file
{% if security.registerable or security.recoverable or security.confirmable %}
<h2>Menu</h2>
<ul>
<li><a href="{{ url_for_security('login') }}{% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}">Login</a></li>
{% if security.registerable %}
<li><a href="{{ url_for_security('register') }}{% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}">Register</a><br/></li>
{% endif %}
{% if security.recoverable %}
<li><a href="{{ url_for_security('forgot_password') }}">Forgot password</a><br/></li>
{% endif %}
{% if security.confirmable %}
<li><a href="{{ url_for_security('send_confirmation') }}">Confirm account</a></li>
{% endif %}
</ul>
{% endif %}
{%- with messages = get_flashed_messages(with_categories=true) -%}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{%- endwith %}
\ No newline at end of file
{% extends 'admin/master.html' %}
{% from "security/_macros.html" import render_field, render_field_with_errors, render_checkbox_field %}
{% include "security/_messages.html" %}
{% block body %}
{{ super() }}
<div class="row-fluid">
<div class="col-sm-8 col-sm-offset-2">
<h1>Login</h1>
<div class="well">
<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form">
{{ login_user_form.hidden_tag() }}
{{ render_field_with_errors(login_user_form.email) }}
{{ render_field_with_errors(login_user_form.password) }}
{{ render_checkbox_field(login_user_form.remember) }}
{{ render_field(login_user_form.next) }}
{{ render_field(login_user_form.submit, class="btn btn-primary") }}
</form>
<p>Not yet signed up? Please <a href="{{ url_for('security.register') }}">register for an account</a>.</p>
</div>
</div>
</div>
{% endblock body %}
\ No newline at end of file
{% extends 'admin/master.html' %}
{% from "security/_macros.html" import render_field_with_errors, render_field %}
{% include "security/_messages.html" %}
{% block body %}
{{ super() }}
<div class="row-fluid">
<div class="col-sm-8 col-sm-offset-2">
<h1>Register</h1>
<div class="well">
<form action="{{ url_for_security('register') }}" method="POST" name="register_user_form">
{{ register_user_form.hidden_tag() }}
{{ render_field_with_errors(register_user_form.email) }}
{{ render_field_with_errors(register_user_form.password) }}
{% if register_user_form.password_confirm %}
{{ render_field_with_errors(register_user_form.password_confirm) }}
{% endif %}
{{ render_field(register_user_form.submit, class="btn btn-primary") }}
</form>
<p>Already signed up? Please <a href="{{ url_for('security.login') }}">log in</a>.</p>
</div>
</div>
</div>
{% endblock body %}
\ No newline at end of file
This example show how to translate Flask-Admin into different language using customized version of the `Flask-Babel <https://github.com/mrjoes/flask-babelex>`
\ No newline at end of file
This example show how to translate Flask-Admin into different language using customized version of the `Flask-Babel <https://github.com/mrjoes/flask-babelex>`
To run this example:
1. Clone the repository::
git clone https://github.com/flask-admin/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env
source env/bin/activate
3. Install requirements::
pip install -r 'examples/babel/requirements.txt'
4. Run the application::
python examples/babel/app.py
from flask import Flask, request, session
from flask.ext.sqlalchemy import SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
from flask.ext import admin
from flask.ext.babelex import Babel
import flask_admin as admin
from flask_babelex import Babel
from flask.ext.admin.contrib import sqla
from flask_admin.contrib import sqla
# Create application
app = Flask(__name__)
......@@ -13,7 +13,7 @@ app = Flask(__name__)
app.config['SECRET_KEY'] = '12345678'
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample_db.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
......@@ -58,11 +58,24 @@ class Post(db.Model):
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
tmp = u"""
<p><a href="/admin/?lang=en">Click me to get to Admin! (English)</a></p>
<p><a href="/admin/?lang=cs">Click me to get to Admin! (Czech)</a></p>
<p><a href="/admin/?lang=de">Click me to get to Admin! (German)</a></p>
<p><a href="/admin/?lang=es">Click me to get to Admin! (Spanish)</a></p>
<p><a href="/admin/?lang=fa">Click me to get to Admin! (Farsi)</a></p>
<p><a href="/admin/?lang=fr">Click me to get to Admin! (French)</a></p>
<p><a href="/admin/?lang=pt">Click me to get to Admin! (Portuguese)</a></p>
<p><a href="/admin/?lang=ru">Click me to get to Admin! (Russian)</a></p>
<p><a href="/admin/?lang=pa">Click me to get to Admin! (Punjabi)</a></p>
<p><a href="/admin/?lang=zh_CN">Click me to get to Admin! (Chinese - Simplified)</a></p>
<p><a href="/admin/?lang=zh_TW">Click me to get to Admin! (Chinese - Traditional)</a></p>
"""
return tmp
if __name__ == '__main__':
# Create admin
admin = admin.Admin(app, 'Simple Models')
admin = admin.Admin(app, 'Example: Babel')
#admin.locale_selector(get_locale)
......
Flask
Flask-Admin
Flask-SQLAlchemy
Flask-BabelEx
Simple file management interface example.
\ No newline at end of file
Simple file management interface example.
To run this example:
1. Clone the repository::
git clone https://github.com/flask-admin/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env
source env/bin/activate
3. Install requirements::
pip install -r 'examples/file/requirements.txt'
4. Run the application::
python examples/file/app.py
This diff is collapsed.
Flask
Flask-Admin
Flask-SQLAlchemy
pillow==2.9.0
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment