Unverified Commit 0e32fc25 authored by ufo911's avatar ufo911 Committed by GitHub

Merge pull request #1 from flask-admin/master

pull master
parents 619699b6 bf17b7ad
...@@ -33,11 +33,13 @@ addons: ...@@ -33,11 +33,13 @@ addons:
services: services:
- postgresql - postgresql
- mongodb - mongodb
- docker
before_script: before_script:
- psql -U postgres -c 'CREATE DATABASE flask_admin_test;' - 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 postgis;' flask_admin_test
- psql -U postgres -c 'CREATE EXTENSION hstore;' flask_admin_test - psql -U postgres -c 'CREATE EXTENSION hstore;' flask_admin_test
- docker run --restart always -d -e executable=blob -p 10000:10000 --tmpfs /opt/azurite/folder arafato/azurite:2.6.5
install: install:
- pip install tox - pip install tox
......
...@@ -38,14 +38,28 @@ Flask-Admin is an active project, well-tested and production ready. ...@@ -38,14 +38,28 @@ Flask-Admin is an active project, well-tested and production ready.
Examples Examples
-------- --------
Several usage examples are included in the */examples* folder. Please feel free to add your own examples, or improve Several usage examples are included in the */examples* folder. Please add your own, or improve
on some of the existing ones, and then submit them via GitHub as a *pull-request*. on the existing examples, and submit 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 the examples in your local environment::
To run the examples on your local environment, one at a time, do something like::
cd flask-admin 1. Clone the repository::
python examples/simple/app.py
git clone https://github.com/flask-admin/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env -p python3
source env/bin/activate
3. Install requirements::
pip install -r 'examples/sqla/requirements.txt'
4. Run the application::
python examples/sqla/app.py
Documentation Documentation
------------- -------------
...@@ -91,7 +105,11 @@ You should see output similar to:: ...@@ -91,7 +105,11 @@ You should see output similar to::
For all the tests to pass successfully, you'll need Postgres & MongoDB to be running locally. For Postgres:: For all the tests to pass successfully, you'll need Postgres & MongoDB to be running locally. For Postgres::
> psql postgres
CREATE DATABASE flask_admin_test; CREATE DATABASE flask_admin_test;
\q
> psql flask_admin_test
CREATE EXTENSION postgis; CREATE EXTENSION postgis;
CREATE EXTENSION hstore; CREATE EXTENSION hstore;
...@@ -100,7 +118,8 @@ You can also run the tests on multiple environments using *tox*. ...@@ -100,7 +118,8 @@ You can also run the tests on multiple environments using *tox*.
3rd Party Stuff 3rd Party Stuff
--------------- ---------------
Flask-Admin is built with the help of `Bootstrap <http://getbootstrap.com/>`_ and `Select2 <https://github.com/ivaynberg/select2>`_. Flask-Admin is built with the help of `Bootstrap <http://getbootstrap.com/>`_, `Select2 <https://github.com/ivaynberg/select2>`_
and `Bootswatch <http://bootswatch.com/>`_.
If you want to localize your application, install the `Flask-BabelEx <https://pypi.python.org/pypi/Flask-BabelEx>`_ package. If you want to localize your application, install the `Flask-BabelEx <https://pypi.python.org/pypi/Flask-BabelEx>`_ package.
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
<ul> <ul>
<li><a href="http://flask.pocoo.org/" target="_blank">Flask</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://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> </ul>
<a class="github" href="http://github.com/flask-admin/flask-admin" target="_blank"><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;"
......
...@@ -34,7 +34,7 @@ Enabling localization is simple: ...@@ -34,7 +34,7 @@ Enabling localization is simple:
#. Initialize Flask-BabelEx by creating instance of `Babel` class:: #. Initialize Flask-BabelEx by creating instance of `Babel` class::
from flask import app from flask import Flask
from flask_babelex import Babel from flask_babelex import Babel
app = Flask(__name__) app = Flask(__name__)
...@@ -164,7 +164,7 @@ Image handling also requires you to have `Pillow <https://pypi.python.org/pypi/P ...@@ -164,7 +164,7 @@ Image handling also requires you to have `Pillow <https://pypi.python.org/pypi/P
installed if you need to do any processing on the image files. installed if you need to do any processing on the image files.
Have a look at the example at Have a look at the example at
https://github.com/flask-admin/Flask-Admin/tree/master/examples/forms. https://github.com/flask-admin/Flask-Admin/tree/master/examples/forms-files-images.
If you are using the MongoEngine backend, Flask-Admin supports GridFS-backed image and file uploads through WTForms fields. Documentation can be found at :mod:`flask_admin.contrib.mongoengine.fields`. If you are using the MongoEngine backend, Flask-Admin supports GridFS-backed image and file uploads through WTForms fields. Documentation can be found at :mod:`flask_admin.contrib.mongoengine.fields`.
...@@ -544,4 +544,3 @@ While the wrapped function should accept only one parameter - `ids`:: ...@@ -544,4 +544,3 @@ While the wrapped function should accept only one parameter - `ids`::
raise raise
flash(gettext('Failed to approve users. %(error)s', error=str(ex)), 'error') flash(gettext('Failed to approve users. %(error)s', error=str(ex)), 'error')
...@@ -15,6 +15,7 @@ API ...@@ -15,6 +15,7 @@ API
mod_actions mod_actions
mod_contrib_sqla mod_contrib_sqla
mod_contrib_sqla_fields
mod_contrib_mongoengine mod_contrib_mongoengine
mod_contrib_mongoengine_fields mod_contrib_mongoengine_fields
mod_contrib_peewee mod_contrib_peewee
......
``flask_admin.contrib.sqla.fields``
===================================
.. automodule:: flask_admin.contrib.sqla.fields
.. autoclass:: QuerySelectField
:members:
.. autoclass:: QuerySelectMultipleField
:members:
.. autoclass:: CheckboxListField
:members:
Changelog Changelog
========= =========
1.5.3
-----
* Fixed XSS vulnerability
* Support nested categories in the navbar menu
* SQLAlchemy
* sort on multiple columns with `column_default_sort`
* sort on related models in `column_sortable_list`
* fix: inline model forms can now also be used for models with multiple primary keys
* support for using mapped `column_property`
* Upgrade Leaflet and Leaflet.draw plugins, used for geoalchemy integration
* Specify `minimum_input_length` for ajax widget
* Peewee: support composite keys
* MongoEngine: when searching/filtering the input is now regarded as case-insensitive by default
* FileAdmin
* handle special characters in filename
* fix a bug with listing directories on Windows
* avoid raising an exception when unknown sort parameter is encountered
* WTForms 3 support
1.5.2
-----
* Fixed XSS vulnerability
* Fixed Peewee support
* Added detail view column formatters
* Updated Flask-Login example to work with the newer version of the library
* Various SQLAlchemy-related fixes
* Various Windows related fixes for the file admin
1.5.1
-----
* Dropped Python 2.6 support
* Fixed SQLAlchemy >= 1.2 compatibility
* Fixed Pewee 3.0 compatibility
* Fixed max year for a combo date inline editor
* Lots of small bug fixes
1.5.0 1.5.0
----- -----
...@@ -13,7 +52,7 @@ Changelog ...@@ -13,7 +52,7 @@ Changelog
- Added support for association proxies - Added support for association proxies
- Added support for remote hybrid properties filters - Added support for remote hybrid properties filters
- Added support for ARRAY column type - Added support for ARRAY column type
* Localization-related fixes * Localization-related fixes
* MongoEngine backend is now properly formats model labels * MongoEngine backend is now properly formats model labels
* Improved Google App Engine support: * Improved Google App Engine support:
- Added TextProperty, KeyProperty and SelectField support - Added TextProperty, KeyProperty and SelectField support
......
...@@ -23,9 +23,9 @@ because they let you group together all of the usual ...@@ -23,9 +23,9 @@ because they let you group together all of the usual
*Create, Read, Update, Delete* (CRUD) view logic into a single, self-contained *Create, Read, Update, Delete* (CRUD) view logic into a single, self-contained
class for each of your models. class for each of your models.
**What does it look like?** At http://examples.flask-admin.org/ you can see **What does it look like?** Clone the `GitHub repository <https://github.com/flask-admin/flask-admin>`_
some examples of Flask-Admin in action, or browse through the `examples/` and run the provided examples locally to get a feel for Flask-Admin. There are several to choose from
directory in the `GitHub repository <https://github.com/flask-admin/flask-admin>`_. in the `examples` directory.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
......
...@@ -18,6 +18,9 @@ The first step is to initialize an empty admin interface for your Flask app:: ...@@ -18,6 +18,9 @@ The first step is to initialize an empty admin interface for your Flask app::
app = Flask(__name__) app = Flask(__name__)
# set optional bootswatch theme
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean'
admin = Admin(app, name='microblog', template_mode='bootstrap3') admin = Admin(app, name='microblog', template_mode='bootstrap3')
# Add administrative views here # Add administrative views here
...@@ -27,7 +30,8 @@ Here, both the *name* and *template_mode* parameters are optional. Alternatively ...@@ -27,7 +30,8 @@ Here, both the *name* and *template_mode* parameters are optional. Alternatively
you could use the :meth:`~flask_admin.base.Admin.init_app` method. you could use the :meth:`~flask_admin.base.Admin.init_app` method.
If you start this application and navigate to `http://localhost:5000/admin/ <http://localhost:5000/admin/>`_, If you start this application and navigate to `http://localhost:5000/admin/ <http://localhost:5000/admin/>`_,
you should see an empty page with a navigation bar on top. you should see an empty page with a navigation bar on top. Customize the look by
specifying a Bootswatch theme that suits your needs (see http://bootswatch.com/3/ for available swatches).
Adding Model Views Adding Model Views
------------------ ------------------
...@@ -156,12 +160,9 @@ Customizing Built-in Views ...@@ -156,12 +160,9 @@ Customizing Built-in Views
**** ****
The built-in `ModelView` class is great for getting started quickly. But, you'll want When inheriting from `ModelView`, values can be specified for numerous
to configure its functionality to suit your particular models. This is done by setting configuration parameters. Use these to customize the views to suit your
values for the configuration attributes that are made available in the `ModelView` class. particular models::
To specify some global configuration parameters, you can subclass `ModelView` and use that
subclass when adding your models to the interface::
from flask_admin.contrib.sqla import ModelView from flask_admin.contrib.sqla import ModelView
...@@ -287,6 +288,28 @@ To **enable csv export** of the model view:: ...@@ -287,6 +288,28 @@ To **enable csv export** of the model view::
This will add a button to the model view that exports records, truncating at :attr:`~flask_admin.model.BaseModelView.export_max_rows`. This will add a button to the model view that exports records, truncating at :attr:`~flask_admin.model.BaseModelView.export_max_rows`.
Grouping Views
==============
When adding a view, specify a value for the `category` parameter
to group related views together in the menu::
admin.add_view(UserView(User, db.session, category="Team"))
admin.add_view(ModelView(Role, db.session, category="Team"))
admin.add_view(ModelView(Permission, db.session, category="Team"))
This will create a top-level menu item named 'Team', and a drop-down containing
links to the three views.
To nest related views within these drop-downs, use the `add_sub_category` method::
admin.add_sub_category(name="Links", parent_name="Team")
And to add arbitrary hyperlinks to the menu::
admin.add_link(MenuLink(name='Home Page', url='/', category='Links'))
Adding Your Own Views Adding Your Own Views
===================== =====================
...@@ -440,7 +463,7 @@ list_row_actions Row action cell with edit/remove/etc buttons ...@@ -440,7 +463,7 @@ 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 empty_list_message Message that will be displayed if there are no models found
======================= ============================================ ======================= ============================================
Have a look at the `layout` example at https://github.com/flask-admin/flask-admin/tree/master/examples/layout Have a look at the `layout` example at https://github.com/flask-admin/flask-admin/tree/master/examples/custom-layout
to see how you can take full stylistic control over the admin interface. to see how you can take full stylistic control over the admin interface.
Environment Variables Environment Variables
......
...@@ -14,11 +14,11 @@ To run this example: ...@@ -14,11 +14,11 @@ To run this example:
3. Install requirements:: 3. Install requirements::
pip install -r 'examples/layout/requirements.txt' pip install -r 'examples/custom-layout/requirements.txt'
4. Run the application:: 4. Run the application::
python examples/layout/app.py python examples/custom-layout/app.py
The first time you run this example, a sample sqlite database gets populated automatically. To suppress this behaviour, 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::: comment the following lines in app.py:::
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
{% endblock %} {% endblock %}
{% block page_body %} {% block page_body %}
<div class="container"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-md-2" role="navigation"> <div class="col-md-2" role="navigation">
<ul class="nav nav-pills nav-stacked"> <ul class="nav nav-pills nav-stacked">
......
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
import os
import os.path as op
from flask import Flask
import flask_admin as admin
from flask_admin.contrib import fileadmin
# Create flask app
app = Flask(__name__, template_folder='templates', static_folder='files')
# Create dummy secrey key so we can use flash
app.config['SECRET_KEY'] = '123456790'
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
if __name__ == '__main__':
# Create directory
path = op.join(op.dirname(__file__), 'files')
try:
os.mkdir(path)
except OSError:
pass
# Create admin interface
admin = admin.Admin(app, 'Example: Files')
admin.add_view(fileadmin.FileAdmin(path, '/files/', name='Files'))
# Start app
app.run(debug=True)
Example of custom filters for the SQLAlchemy backend. This example shows how you can::
* define your own custom forms by using form rendering rules
* handle generic static file uploads
* handle image uploads
* turn a TextArea field into a rich WYSIWYG editor using WTForms and CKEditor
* set up a Flask-Admin view as a Redis terminal
To run this example: To run this example:
...@@ -14,11 +21,11 @@ To run this example: ...@@ -14,11 +21,11 @@ To run this example:
3. Install requirements:: 3. Install requirements::
pip install -r 'examples/sqla-custom-filter/requirements.txt' pip install -r 'examples/forms-files-images/requirements.txt'
4. Run the application:: 4. Run the application::
python examples/sqla-custom-filter/app.py python examples/forms-files-images/app.py
The first time you run this example, a sample sqlite database gets populated automatically. To suppress this behaviour, 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::: comment the following lines in app.py:::
......
...@@ -3,18 +3,24 @@ import os.path as op ...@@ -3,18 +3,24 @@ import os.path as op
from flask import Flask, url_for from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from redis import Redis
from wtforms import fields, widgets
from sqlalchemy.event import listens_for from sqlalchemy.event import listens_for
from jinja2 import Markup from jinja2 import Markup
from flask_admin import Admin, form from flask_admin import Admin, form
from flask_admin.form import rules from flask_admin.form import rules
from flask_admin.contrib import sqla from flask_admin.contrib import sqla, rediscli
# Create application # Create application
app = Flask(__name__, static_folder='files') app = Flask(__name__, static_folder='files')
# set optional bootswatch theme
# see http://bootswatch.com/3/ for available swatches
app.config['FLASK_ADMIN_SWATCH'] = 'cerulean'
# Create dummy secrey key so we can use sessions # Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790' app.config['SECRET_KEY'] = '123456790'
...@@ -62,6 +68,15 @@ class User(db.Model): ...@@ -62,6 +68,15 @@ class User(db.Model):
notes = db.Column(db.UnicodeText) notes = db.Column(db.UnicodeText)
class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(64))
text = db.Column(db.UnicodeText)
def __unicode__(self):
return self.name
# Delete hooks for models, delete files if models are getting deleted # Delete hooks for models, delete files if models are getting deleted
@listens_for(File, 'after_delete') @listens_for(File, 'after_delete')
def del_file(mapper, connection, target): def del_file(mapper, connection, target):
...@@ -90,7 +105,28 @@ def del_image(mapper, connection, target): ...@@ -90,7 +105,28 @@ def del_image(mapper, connection, target):
pass pass
# define a custom wtforms widget and field.
# see https://wtforms.readthedocs.io/en/latest/widgets.html#custom-widgets
class CKTextAreaWidget(widgets.TextArea):
def __call__(self, field, **kwargs):
# add WYSIWYG class to existing classes
existing_classes = kwargs.pop('class', '') or kwargs.pop('class_', '')
kwargs['class'] = '{} {}'.format(existing_classes, "ckeditor")
return super(CKTextAreaWidget, self).__call__(field, **kwargs)
class CKTextAreaField(fields.TextAreaField):
widget = CKTextAreaWidget()
# Administrative views # Administrative views
class PageView(sqla.ModelView):
form_overrides = {
'text': CKTextAreaField
}
create_template = 'create_page.html'
edit_template = 'edit_page.html'
class FileView(sqla.ModelView): class FileView(sqla.ModelView):
# Override form field to use Flask-Admin FileUploadField # Override form field to use Flask-Admin FileUploadField
form_overrides = { form_overrides = {
...@@ -140,15 +176,15 @@ class UserView(sqla.ModelView): ...@@ -140,15 +176,15 @@ class UserView(sqla.ModelView):
rules.Field('city'), rules.Field('city'),
# String is resolved to form field, so there's no need to explicitly use `rules.Field` # String is resolved to form field, so there's no need to explicitly use `rules.Field`
'country', 'country',
# Show macro from Flask-Admin lib.html (it is included with 'lib' prefix) # Show macro that's included in the templates
rules.Container('rule_demo.wrap', rules.Field('notes')) rules.Container('rule_demo.wrap', rules.Field('notes'))
] ]
# Use same rule set for edit page # Use same rule set for edit page
form_edit_rules = form_create_rules form_edit_rules = form_create_rules
create_template = 'rule_create.html' create_template = 'create_user.html'
edit_template = 'rule_edit.html' edit_template = 'edit_user.html'
# Flask views # Flask views
...@@ -162,7 +198,9 @@ admin = Admin(app, 'Example: Forms', template_mode='bootstrap3') ...@@ -162,7 +198,9 @@ admin = Admin(app, 'Example: Forms', template_mode='bootstrap3')
# Add views # Add views
admin.add_view(FileView(File, db.session)) admin.add_view(FileView(File, db.session))
admin.add_view(ImageView(Image, db.session)) admin.add_view(ImageView(Image, db.session))
admin.add_view(UserView(User, db.session, name='User')) admin.add_view(UserView(User, db.session))
admin.add_view(PageView(Page, db.session))
admin.add_view(rediscli.RedisCli(Redis()))
def build_sample_db(): def build_sample_db():
...@@ -238,6 +276,10 @@ def build_sample_db(): ...@@ -238,6 +276,10 @@ def build_sample_db():
file.path = "example_" + str(i) + ".pdf" file.path = "example_" + str(i) + ".pdf"
db.session.add(file) db.session.add(file)
sample_text = "<h2>This is a test</h2>" + \
"<p>Create HTML content in a text area field with the help of <i>WTForms</i> and <i>CKEditor</i>.</p>"
db.session.add(Page(name="Test Page", text=sample_text))
db.session.commit() db.session.commit()
return return
......
Flask Flask
Flask-Admin Flask-Admin
Flask-SQLAlchemy Flask-SQLAlchemy
pillow
redis
{% extends 'admin/model/create.html' %}
{% block tail %}
{{ super() }}
<script src="https://cdn.ckeditor.com/ckeditor5/11.1.1/classic/ckeditor.js"></script>
<script>
ClassicEditor
.create(document.querySelector('.ckeditor'))
.catch(error => {
console.error( error );
});
</script>
{% endblock %}
{% extends 'admin/model/create.html' %} {% extends 'admin/model/create.html' %}
{% import 'rule_demo.html' as rule_demo %} {% import 'macros.html' as rule_demo %}
\ No newline at end of file
{% extends 'admin/model/create.html' %}
{% block tail %}
{{ super() }}
<script src="https://cdn.ckeditor.com/ckeditor5/11.1.1/classic/ckeditor.js"></script>
<script>
ClassicEditor
.create(document.querySelector('.ckeditor'))
.catch(error => {
console.error( error );
});
</script>
{% endblock %}
{% extends 'admin/model/edit.html' %} {% extends 'admin/model/edit.html' %}
{% import 'rule_demo.html' as rule_demo %} {% import 'macros.html' as rule_demo %}
This example shows how you can define your own custom forms by using form rendering rules. It also demonstrates general file handling as well as the handling of image files specifically.
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/forms/requirements.txt'
4. Run the application::
python examples/forms/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()
Flask
Flask-Admin
Flask-SQLAlchemy
pillow==2.9.0
...@@ -32,7 +32,7 @@ To run this example: ...@@ -32,7 +32,7 @@ To run this example:
5. Run the application:: 5. Run the application::
python examples/sqla/app.py python examples/geo_alchemy/app.py
6. You will notice that the maps are not rendered. To see them, you will have 6. You will notice that the maps are not rendered. To see them, you will have
to register for a free account at `Mapbox <https://www.mapbox.com/>`_ and set to register for a free account at `Mapbox <https://www.mapbox.com/>`_ and set
......
...@@ -7,4 +7,8 @@ SQLALCHEMY_ECHO = True ...@@ -7,4 +7,8 @@ SQLALCHEMY_ECHO = True
# credentials for loading map tiles from mapbox # credentials for loading map tiles from mapbox
MAPBOX_MAP_ID = '...' MAPBOX_MAP_ID = '...'
MAPBOX_ACCESS_TOKEN = '...' MAPBOX_ACCESS_TOKEN = '...'
\ No newline at end of file
# when the creating new shapes, use this default map center
DEFAULT_CENTER_LAT = -33.918861
DEFAULT_CENTER_LONG = 18.423300
import os
import os.path as op
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask_admin as admin
from flask_admin.contrib.sqla import ModelView
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(64))
email = db.Column(db.Unicode(64))
def __unicode__(self):
return self.name
class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Unicode(64))
content = db.Column(db.UnicodeText)
def __unicode__(self):
return self.name
# Customized admin interface
class CustomView(ModelView):
list_template = 'list.html'
create_template = 'create.html'
edit_template = 'edit.html'
class UserAdmin(CustomView):
column_searchable_list = ('name',)
column_filters = ('name', 'email')
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
# Create admin with custom base template
admin = admin.Admin(app, 'Example: Layout', base_template='layout.html')
# Add views
admin.add_view(UserAdmin(User, db.session))
admin.add_view(CustomView(Page, db.session))
def build_sample_db():
"""
Populate a small db with some example entries.
"""
db.drop_all()
db.create_all()
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)):
user = User()
user.name = first_names[i] + " " + last_names[i]
user.email = first_names[i].lower() + "@example.com"
db.session.add(user)
sample_text = [
{
'title': "de Finibus Bonorum et Malorum - Part I",
'content': "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure \
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. \
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \
mollit anim id est laborum."
},
{
'title': "de Finibus Bonorum et Malorum - Part II",
'content': "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque \
laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto \
beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur \
aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi \
nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, \
adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam \
aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam \
corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum \
iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum \
qui dolorem eum fugiat quo voluptas nulla pariatur?"
},
{
'title': "de Finibus Bonorum et Malorum - Part III",
'content': "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium \
voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati \
cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id \
est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam \
libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod \
maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. \
Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet \
ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur \
a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis \
doloribus asperiores repellat."
}
]
for entry in sample_text:
page = Page()
page.title = entry['title']
page.content = entry['content']
db.session.add(page)
db.session.commit()
return
if __name__ == '__main__':
# Build a sample db on the fly, if one does not exist yet.
app_dir = op.realpath(os.path.dirname(__file__))
database_path = op.join(app_dir, app.config['DATABASE_FILE'])
if not os.path.exists(database_path):
build_sample_db()
# Start app
app.run(debug=True)
body {
background: #EEE;
}
#content {
background: white;
border: 1px solid #CCC;
padding: 12px;
overflow: scroll;
}
#brand {
float: left;
font-weight: 300;
margin: 0;
}
.search-form {
margin: 0 5px;
}
.search-form form {
margin: 0;
}
.btn-menu {
margin: 4px 5px 0 0;
float: right;
}
.btn-menu a, .btn-menu input {
padding: 7px 16px !important;
border-radius: 0 !important;
}
.btn, textarea, input[type], button, .model-list {
border-radius: 0;
}
.model-list {
border-radius: 0;
}
.nav-pills li > a {
border-radius: 0;
}
.select2-container .select2-choice {
border-radius: 0;
}
\ No newline at end of file
{% extends 'admin/master.html' %}
{% block body %}
{{ super() }}
<div class="row-fluid">
<h1>Flask-Admin example</h1>
<p class="lead">
Customize the layout
</p>
<p>
This example shows how you can customize the look & feel of the admin interface.
</p>
<p>
This is done by overriding some of the built-in templates.
</p>
<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
{% import 'admin/layout.html' as layout with context -%}
{% extends 'admin/base.html' %}
{% block head_tail %}
{{ super() }}
<link href="{{ url_for('static', filename='layout.css') }}" rel="stylesheet">
{% endblock %}
{% block page_body %}
<div class="container">
<div class="row">
<div class="span2">
<ul class="nav nav-pills nav-stacked">
{{ layout.menu() }}
{{ layout.menu_links() }}
</ul>
</div>
<div class="span10">
<div id="content">
{% block brand %}
<h2 id="brand">{{ admin_view.name|capitalize }}</h2>
<div class="clearfix"></div>
{% endblock %}
{{ layout.messages() }}
{% block body %}{% endblock %}
</div>
</div>
</div>
</div>
{% endblock %}
{% extends 'admin/model/list.html' %}
{% import 'admin/model/layout.html' as model_layout with context %}
{% block brand %}
<h2 id="brand">{{ admin_view.name|capitalize }} list</h2>
{% if admin_view.can_create %}
<div class="btn-menu">
<a href="{{ url_for('.create_view', url=return_url) }}" class="btn btn-primary pull-right">{{ _gettext('Create') }}</a>
</div>
{% endif %}
{% if filter_groups %}
<div class="btn-group btn-menu">
{{ model_layout.filter_options(btn_class='btn dropdown-toggle btn-title') }}
</div>
{% endif %}
{% if actions %}
<div class="btn-group btn-menu">
{{ actionlib.dropdown(actions, btn_class='btn dropdown-toggle btn-title') }}
</div>
{% endif %}
{% if search_supported %}
<div class="search-form btn-menu">
{{ model_layout.search_form(input_class='span2 btn-title') }}
</div>
{% endif %}
<div class="clearfix"></div>
<hr>
{% endblock %}
{% block model_menu_bar %}
{% endblock %}
\ No newline at end of file
This example shows how you can customize the look & feel of the admin interface. This is done by overriding some of the built-in templates.
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/layout_bootstrap3/requirements.txt'
4. Run the application::
python examples/layout_bootstrap3/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()
{% extends 'admin/model/create.html' %}
{% block brand %}
<h2 id="brand">Create {{ admin_view.name|capitalize }}</h2>
<div class="clearfix"></div>
<hr>
{% endblock %}
{% block body %}
{% call lib.form_tag(form) %}
{{ lib.render_form_fields(form, form_opts=form_opts) }}
<div class="form-buttons">
{{ lib.render_form_buttons(return_url, extra()) }}
</div>
{% endcall %}
{% endblock %}
{% extends 'admin/model/edit.html' %}
{% block brand %}
<h2 id="brand">Edit {{ admin_view.name|capitalize }}</h2>
<div class="clearfix"></div>
<hr>
{% endblock %}
{% block body %}
{% call lib.form_tag(form) %}
{{ lib.render_form_fields(form, form_opts=form_opts) }}
<div class="form-buttons">
{{ lib.render_form_buttons(return_url) }}
</div>
{% endcall %}
{% endblock %}
This example shows how you can add links to external (non flask-admin) pages to the navbar menu, and how you can hide certain links if a user is not logged-in.
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/menu-external-links/requirements.txt'
4. Run the application::
python examples/menu-external-links/app.py
from flask import Flask, redirect, url_for
from flask.ext import login
from flask_login import current_user, UserMixin
from flask_admin.base import MenuLink, Admin, BaseView, expose
# Create fake user class for authentication
class User(UserMixin):
users_id = 0
def __init__(self, id=None):
if not id:
self.users_id += 1
self.id = self.users_id
else:
self.id = id
# Create menu links classes with reloaded accessible
class AuthenticatedMenuLink(MenuLink):
def is_accessible(self):
return current_user.is_authenticated
class NotAuthenticatedMenuLink(MenuLink):
def is_accessible(self):
return not current_user.is_authenticated
# Create custom admin view for authenticated users
class MyAdminView(BaseView):
@expose('/')
def index(self):
return self.render('authenticated-admin.html')
def is_accessible(self):
return current_user.is_authenticated
# Create flask app
app = Flask(__name__, template_folder='templates')
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
@app.route('/login/')
def login_view():
login.login_user(User())
return redirect(url_for('admin.index'))
@app.route('/logout/')
def logout_view():
login.logout_user()
return redirect(url_for('admin.index'))
login_manager = login.LoginManager()
login_manager.init_app(app)
# Create user loader function
@login_manager.user_loader
def load_user(user_id):
return User(user_id)
if __name__ == '__main__':
# Create admin interface
admin = Admin(name='Example: Menu')
admin.add_view(MyAdminView(name='Authenticated'))
# Add home link by url
admin.add_link(MenuLink(name='Back Home', url='/'))
# Add login link by endpoint
admin.add_link(NotAuthenticatedMenuLink(name='Login',
endpoint='login_view'))
# Add links with categories
admin.add_link(MenuLink(name='Google', category='Links', url='http://www.google.com/'))
admin.add_link(MenuLink(name='Mozilla', category='Links', url='http://mozilla.org/'))
# Add logout link by endpoint
admin.add_link(AuthenticatedMenuLink(name='Logout',
endpoint='logout_view'))
admin.init_app(app)
# Start app
app.run(debug=True)
{% extends 'admin/master.html' %}
{% block body %}
Hello World from Authenticated Admin!
{% endblock %}
...@@ -14,9 +14,8 @@ To run this example: ...@@ -14,9 +14,8 @@ To run this example:
3. Install requirements:: 3. Install requirements::
pip install -r 'examples/multi/requirements.txt' pip install -r 'examples/multiple-admin-instances/requirements.txt'
4. Run the application:: 4. Run the application::
python examples/multi/app.py python examples/multiple-admin-instances/app.py
Simple Flask-Admin examples used by the quickstart tutorial.
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/quickstart/requirements.txt'
4. Run the application with any of the following::
python examples/quickstart/app.py
python examples/quickstart/app2.py
python examples/quickstart/app3.py
from flask import Flask
from flask_admin import Admin
app = Flask(__name__)
app.debug = True
admin = Admin(app, name="Example: Quickstart")
if __name__ == '__main__':
# Start app
app.run(debug=True)
from flask import Flask
from flask_admin import Admin, BaseView, expose
class MyView(BaseView):
@expose('/')
def index(self):
return self.render('index.html')
app = Flask(__name__)
app.debug = True
admin = Admin(app, name="Example: Quickstart2")
admin.add_view(MyView(name='Hello'))
if __name__ == '__main__':
# Start app
app.run()
from flask import Flask
from flask_admin import Admin, BaseView, expose
class MyView(BaseView):
@expose('/')
def index(self):
return self.render('index.html')
app = Flask(__name__)
app.debug = True
admin = Admin(app, name="Example: Quickstart3")
admin.add_view(MyView(name='Hello 1', endpoint='test1', category='Test'))
admin.add_view(MyView(name='Hello 2', endpoint='test2', category='Test'))
admin.add_view(MyView(name='Hello 3', endpoint='test3', category='Test'))
if __name__ == '__main__':
# Start app
app.run()
{% extends 'admin/master.html' %}
{% block body %}
Hello World from MyView!
{% endblock %}
This example shows how to set up a Flask-Admin view as a Redis terminal.
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/rediscli/requirements.txt'
4. Run the application::
python examples/rediscli/app.py
You should now be able to access a Redis instance on your machine (if it is running) through the admin interface.
from flask import Flask
from redis import Redis
import flask_admin as admin
from flask_admin.contrib import rediscli
# Create flask app
app = Flask(__name__)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
if __name__ == '__main__':
# Create admin interface
admin = admin.Admin(app, name="Example: Redis")
admin.add_view(rediscli.RedisCli(Redis()))
# Start app
app.run(debug=True)
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin.contrib import sqla
from flask_admin import Admin
# required for creating custom filters
from flask_admin.contrib.sqla.filters import BaseSQLAFilter, FilterEqual
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
# Create model
class User(db.Model):
def __init__(self, first_name, last_name, username, email):
self.first_name = first_name
self.last_name = last_name
self.username = username
self.email = email
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(100))
last_name = db.Column(db.String(100))
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
# Required for admin interface. For python 3 please use __str__ instead.
def __unicode__(self):
return self.username
# Create custom filter class
class FilterLastNameBrown(BaseSQLAFilter):
def apply(self, query, value, alias=None):
if value == '1':
return query.filter(self.column == "Brown")
else:
return query.filter(self.column != "Brown")
def operation(self):
return 'is Brown'
# Add custom filter and standard FilterEqual to ModelView
class UserAdmin(sqla.ModelView):
# each filter in the list is a filter operation (equals, not equals, etc)
# filters with the same name will appear as operations under the same filter
column_filters = [
FilterEqual(column=User.last_name, name='Last Name'),
FilterLastNameBrown(column=User.last_name, name='Last Name',
options=(('1', 'Yes'), ('0', 'No')))
]
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(UserAdmin(User, db.session))
def build_sample_db():
db.drop_all()
db.create_all()
user_obj1 = User("Paul", "Brown", "pbrown", "paul@gmail.com")
user_obj2 = User("Luke", "Brown", "lbrown", "luke@gmail.com")
user_obj3 = User("Serge", "Koval", "skoval", "serge@gmail.com")
db.session.add_all([user_obj1, user_obj2, user_obj3])
db.session.commit()
if __name__ == '__main__':
build_sample_db()
app.run(port=5000, debug=True)
...@@ -14,10 +14,8 @@ To run this example: ...@@ -14,10 +14,8 @@ To run this example:
3. Install requirements:: 3. Install requirements::
pip install -r 'examples/sqla-inline/requirements.txt' pip install -r 'examples/sqla-custom-inline-forms/requirements.txt'
4. Run the application:: 4. Run the application::
python examples/sqla-inline/app.py python examples/sqla-custom-inline-forms/app.py
SQLA backend example showing how to filter select dropdown options in forms.
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/sqla-filter-selectable/requirements.txt'
4. Run the application::
python examples/sqla-filter-selectable/app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask_admin as admin
from flask_admin.contrib import sqla
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample_db_3.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
pets = db.relationship('Pet', backref='person')
def __unicode__(self):
return self.name
class Pet(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
available = db.Column(db.Boolean)
def __unicode__(self):
return self.name
class PersonAdmin(sqla.ModelView):
""" Override ModelView to filter options available in forms. """
def create_form(self):
return self._use_filtered_parent(
super(PersonAdmin, self).create_form()
)
def edit_form(self, obj):
return self._use_filtered_parent(
super(PersonAdmin, self).edit_form(obj)
)
def _use_filtered_parent(self, form):
form.pets.query_factory = self._get_parent_list
return form
def _get_parent_list(self):
# only show available pets in the form
return Pet.query.filter_by(available=True).all()
def __unicode__(self):
return self.name
# Create admin
admin = admin.Admin(app, name='Example: SQLAlchemy - Filtered Form Selectable',
template_mode='bootstrap3')
admin.add_view(PersonAdmin(Person, db.session))
admin.add_view(sqla.ModelView(Pet, db.session))
if __name__ == '__main__':
# Recreate DB
db.drop_all()
db.create_all()
person = Person(name='Bill')
pet1 = Pet(name='Dog', available=True)
pet2 = Pet(name='Fish', available=True)
pet3 = Pet(name='Ocelot', available=False)
db.session.add_all([person, pet1, pet2, pet3])
db.session.commit()
# Start app
app.run(debug=True)
Example of how to use (and filter on) a hybrid_property with the SQLAlchemy backend.
Hybrid properties allow you to treat calculations (for example: first_name + last_name)
like any other database column.
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/sqla-hybrid_property/requirements.txt'
4. Run the application::
python examples/sqla-hybrid_property/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()
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.hybrid import hybrid_property
import flask_admin as admin
from flask_admin.contrib import sqla
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample_db_2.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Screen(db.Model):
__tablename__ = 'screen'
id = db.Column(db.Integer, primary_key=True)
width = db.Column(db.Integer, nullable=False)
height = db.Column(db.Integer, nullable=False)
@hybrid_property
def number_of_pixels(self):
return self.width * self.height
class ScreenAdmin(sqla.ModelView):
""" Flask-admin can not automatically find a hybrid_property yet. You will
need to manually define the column in list_view/filters/sorting/etc."""
column_list = ['id', 'width', 'height', 'number_of_pixels']
column_sortable_list = ['id', 'width', 'height', 'number_of_pixels']
# Flask-admin can automatically detect the relevant filters for hybrid properties.
column_filters = ('number_of_pixels', )
# Create admin
admin = admin.Admin(app, name='Example: SQLAlchemy2', template_mode='bootstrap3')
admin.add_view(ScreenAdmin(Screen, db.session))
if __name__ == '__main__':
# Create DB
db.create_all()
# Start app
app.run(debug=True)
...@@ -16,10 +16,9 @@ To run this example: ...@@ -16,10 +16,9 @@ To run this example:
pip install -r 'examples/sqla/requirements.txt' pip install -r 'examples/sqla/requirements.txt'
4. Run either of these applications:: 4. Run the application::
python examples/sqla/app.py python examples/sqla/app.py
python examples/sqla/app2.py
The first time you run this example, a sample sqlite database gets populated automatically. To suppress this behaviour, 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::: comment the following lines in app.py:::
......
This diff is collapsed.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import flask_admin as admin
from flask_admin.contrib import sqla
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample_db_2.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Flask views
@app.route('/')
def index():
return '<a href="/admin/">Click me to get to Admin!</a>'
class Car(db.Model):
__tablename__ = 'cars'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
desc = db.Column(db.String(50))
def __str__(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))
class CarAdmin(sqla.ModelView):
column_display_pk = True
form_columns = ['id', 'desc']
class TyreAdmin(sqla.ModelView):
column_display_pk = True
form_columns = ['car', 'tyre_id', 'desc']
# Create admin
admin = admin.Admin(app, name='Example: SQLAlchemy2', template_mode='bootstrap3')
admin.add_view(CarAdmin(Car, db.session))
admin.add_view(TyreAdmin(Tyre, db.session))
if __name__ == '__main__':
# Create DB
db.create_all()
# Start app
app.run(debug=True)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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