Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
4f019346
Commit
4f019346
authored
Mar 20, 2012
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support for sortable columns, FK now sortable, some docs.
parent
a90f1514
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
471 additions
and
66 deletions
+471
-66
TODO.txt
TODO.txt
+7
-2
simple.py
examples/sqla/simple.py
+4
-0
base.py
flask_adminex/base.py
+94
-0
sqlamodel.py
flask_adminex/ext/sqlamodel.py
+68
-29
model.py
flask_adminex/model.py
+215
-10
lib.html
flask_adminex/templates/admin/lib.html
+33
-21
list.html
flask_adminex/templates/admin/model/list.html
+4
-4
tools.py
flask_adminex/tools.py
+46
-0
No files found.
TODO.txt
View file @
4f019346
- Core
- Pregenerate URLs for menu
- Page titles
- Create base model-admin class and derive SQLA admin from it
- Override base URL (/admin/)
- Model admin
- Ability to override sortable fields
- SQLA Model Admin
- Sort by foreign key
- Validation of the joins in the query
- Automatic joined load for foreign keys
- Automatic PK detection
- Ability to override displayed form fields
- Ability to rename list columns
- Ability to change form without messing with form creation
...
...
examples/sqla/simple.py
View file @
4f019346
...
...
@@ -41,12 +41,16 @@ class Post(db.Model):
# Flask routes
@
app
.
route
(
'/'
)
def
index
():
db
.
session
.
query
(
Post
)
.
join
(
User
)
.
order_by
(
User
.
username
)
.
all
()
return
'<a href="/admin/">Click me to get to Admin!</a>'
class
PostAdmin
(
sqlamodel
.
ModelView
):
list_columns
=
(
'title'
,
'user'
)
sortable_columns
=
dict
(
title
=
'title'
,
user
=
User
.
username
)
def
__init__
(
self
,
session
):
super
(
PostAdmin
,
self
)
.
__init__
(
Post
,
session
)
...
...
flask_adminex/base.py
View file @
4f019346
...
...
@@ -5,6 +5,13 @@ from flask import Blueprint, render_template, url_for, abort
def
expose
(
url
=
'/'
,
methods
=
(
'GET'
,)):
"""
Use this decorator to expose views in your view classes.
`url`
Relative URL for the view
`methods`
Allowed HTTP methods. By default only GET is allowed.
"""
def
wrap
(
f
):
if
not
hasattr
(
f
,
'_urls'
):
f
.
_urls
=
[]
...
...
@@ -29,6 +36,12 @@ def _wrap_view(f):
class
AdminViewMeta
(
type
):
"""
View metaclass.
Does some precalculations (like getting list of view methods from the class) to avoid
calculating them for each view class instance.
"""
def
__init__
(
cls
,
classname
,
bases
,
fields
):
type
.
__init__
(
cls
,
classname
,
bases
,
fields
)
...
...
@@ -56,9 +69,35 @@ class AdminViewMeta(type):
class
BaseView
(
object
):
"""
Base administrative view.
Derive from this class to implement your administrative interface piece. For example::
class MyView(BaseView):
@expose('/')
def index(self):
return 'Hello World!'
"""
__metaclass__
=
AdminViewMeta
def
__init__
(
self
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
,
static_folder
=
None
):
"""
Constructor.
`name`
Name of this view. If not provided, will be defaulted to the class name.
`category`
View category. If not provided, will be shown as a top-level menu item. Otherwise, will
be in a submenu.
`endpoint`
Base endpoint name for the view. For example, if there's view method called "index" and
endpoint was set to "myadmin", you can use `url_for('myadmin.index')` to get URL to the
view method. By default, equals to the class name in lower case.
`url`
Base URL. If provided, affects how URLs are generated. For example, if url parameter
equals to "test", resulting URL will look like "/admin/test/". If not provided, will
use endpoint as a base url.
"""
self
.
name
=
name
self
.
category
=
category
self
.
endpoint
=
endpoint
...
...
@@ -70,9 +109,15 @@ class BaseView(object):
self
.
_create_blueprint
()
def
_set_admin
(
self
,
admin
):
"""
Associate this view with Admin class instance.
"""
self
.
admin
=
admin
def
_create_blueprint
(
self
):
"""
Create Flask blueprint.
"""
# If endpoint name is not provided, get it from the class name
if
self
.
endpoint
is
None
:
self
.
endpoint
=
self
.
__class__
.
__name__
.
lower
()
...
...
@@ -98,9 +143,20 @@ class BaseView(object):
methods
=
methods
)
def
_prettify_name
(
self
,
name
):
"""
Prettify class name by splitting name by capital characters. So, 'MySuperClass' will look like 'My Super Class'
"""
return
sub
(
r'(?<=.)([A-Z])'
,
r' \1'
,
name
)
def
is_accessible
(
self
):
"""
Override this method to add permission checks.
Flask-AdminEx does not make any assumptions about authentication system used in your application, so it is
up for you to implement it.
By default, it will allow access for the everyone.
"""
return
True
def
_handle_view
(
self
,
name
,
**
kwargs
):
...
...
@@ -109,6 +165,18 @@ class BaseView(object):
class
AdminIndexView
(
BaseView
):
"""
Administrative interface entry page. You can see it by going to the /admin/ URL.
You can override this page by passing your own view class to the `Admin` constructor::
class MyHomeView(AdminIndexView):
@expose('/')
def index(self):
return render_template('adminhome.html')
admin = Admin(index_view=MyHomeView)
"""
def
__init__
(
self
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
super
(
AdminIndexView
,
self
)
.
__init__
(
name
or
'Home'
,
category
,
endpoint
or
'admin'
,
url
or
'/admin'
,
'static'
)
...
...
@@ -161,7 +229,18 @@ class MenuItem(object):
class
Admin
(
object
):
"""
Collection of the views. Also manages menu structure.
"""
def
__init__
(
self
,
name
=
None
,
index_view
=
None
):
"""
Constructor.
`name`
Application name. Will be displayed in main menu and as a page title. If not provided, defaulted to "Flask"
`index_view`
Home page view to use. If not provided, will use `AdminIndexView`.
"""
self
.
_views
=
[]
self
.
_menu
=
[]
...
...
@@ -176,10 +255,22 @@ class Admin(object):
self
.
add_view
(
index_view
)
def
add_view
(
self
,
view
):
"""
Add view to the collection.
`view`
View to add.
"""
view
.
_set_admin
(
self
)
self
.
_views
.
append
(
view
)
def
apply
(
self
,
app
):
"""
Register all views with Flask application.
`app`
Flask application instance
"""
self
.
app
=
app
for
v
in
self
.
_views
:
...
...
@@ -206,4 +297,7 @@ class Admin(object):
category
.
add_child
(
MenuItem
(
v
.
name
,
v
))
def
menu
(
self
):
"""
Return menu hierarchy.
"""
return
self
.
_menu
flask_adminex/ext/sqlamodel.py
View file @
4f019346
from
sqlalchemy.orm.properties
import
RelationshipProperty
,
ColumnProperty
from
sqlalchemy.orm.interfaces
import
MANYTOONE
from
sqlalchemy.orm.attributes
import
InstrumentedAttribute
from
sqlalchemy.sql.expression
import
desc
from
wtforms.ext.sqlalchemy.orm
import
model_form
,
ModelConverter
...
...
@@ -42,41 +43,64 @@ class AdminModelConverter(ModelConverter):
if
column
.
foreign_keys
or
column
.
primary_key
:
return
None
return
super
(
AdminModelConverter
,
self
)
.
convert
(
model
,
mapper
,
prop
,
field_args
)
return
super
(
AdminModelConverter
,
self
)
.
convert
(
model
,
mapper
,
prop
,
field_args
)
class
ModelView
(
BaseModelView
):
def
__init__
(
self
,
model
,
session
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
def
__init__
(
self
,
model
,
session
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
self
.
session
=
session
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
# Public API
def
get
_list_columns
(
self
):
columns
=
self
.
list_columns
def
scaffold
_list_columns
(
self
):
columns
=
[]
if
columns
is
None
:
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
columns
=
[]
for
p
in
mapper
.
iterate_properties
:
if
isinstance
(
p
,
RelationshipProperty
):
if
p
.
direction
is
MANYTOONE
:
columns
.
append
(
p
.
key
)
elif
isinstance
(
p
,
ColumnProperty
):
# TODO: Check for multiple columns
column
=
p
.
columns
[
0
]
for
p
in
mapper
.
iterate_properties
:
if
isinstance
(
p
,
RelationshipProperty
):
if
p
.
direction
is
MANYTOONE
:
columns
.
append
(
p
.
key
)
elif
isinstance
(
p
,
ColumnProperty
):
# TODO: Check for multiple columns
column
=
p
.
columns
[
0
]
if
column
.
foreign_keys
or
column
.
primary_key
:
continue
if
column
.
foreign_keys
or
column
.
primary_key
:
continue
columns
.
append
((
p
.
key
,
self
.
prettify_name
(
p
.
key
)))
columns
.
append
(
p
.
key
)
return
columns
return
[(
c
,
self
.
prettify_name
(
c
))
for
c
in
columns
]
def
scaffold_sortable_columns
(
self
):
columns
=
dict
()
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
for
p
in
mapper
.
iterate_properties
:
if
isinstance
(
p
,
RelationshipProperty
):
if
p
.
direction
is
MANYTOONE
:
# TODO: Detect PK
columns
[
p
.
key
]
=
'
%
s.id'
%
p
.
target
.
name
elif
isinstance
(
p
,
ColumnProperty
):
# TODO: Check for multiple columns
column
=
p
.
columns
[
0
]
if
column
.
foreign_keys
or
column
.
primary_key
:
continue
columns
[
p
.
key
]
=
p
.
key
return
columns
def
scaffold_form
(
self
):
return
model_form
(
self
.
model
,
wtf
.
Form
,
self
.
form_columns
,
converter
=
AdminModelConverter
(
self
.
session
))
return
model_form
(
self
.
model
,
wtf
.
Form
,
self
.
form_columns
,
converter
=
AdminModelConverter
(
self
.
session
))
# Database-related API
def
get_list
(
self
,
page
,
sort_column
,
sort_desc
):
...
...
@@ -84,19 +108,34 @@ class ModelView(BaseModelView):
count
=
query
.
count
()
if
sort_column
is
not
None
:
# Validate first
if
sort_column
>=
0
and
sort_column
<
len
(
self
.
list_columns
):
if
sort_desc
:
query
=
query
.
order_by
(
desc
(
self
.
list_columns
[
sort_column
][
0
]))
# Sorting
column
=
self
.
_get_column_by_idx
(
sort_column
)
if
column
is
not
None
:
name
=
column
[
0
]
if
name
in
self
.
_sortable_columns
:
sort_field
=
self
.
_sortable_columns
[
name
]
# Try to handle it as a string
if
isinstance
(
sort_field
,
basestring
):
# Create automatic join if string contains dot
if
'.'
in
sort_field
:
parts
=
sort_field
.
split
(
'.'
,
1
)
query
=
query
.
join
(
parts
[
0
])
elif
isinstance
(
sort_field
,
InstrumentedAttribute
):
query
=
query
.
join
(
sort_field
.
parententity
)
else
:
query
=
query
.
order_by
(
self
.
list_columns
[
sort_column
][
0
])
sort_field
=
None
if
page
is
not
None
:
if
page
<
1
:
page
=
1
if
sort_field
is
not
None
:
if
sort_desc
:
query
=
query
.
order_by
(
desc
(
sort_field
))
else
:
query
=
query
.
order_by
(
sort_field
)
query
=
query
.
offset
((
page
-
1
)
*
self
.
page_size
)
# Pagination
if
page
is
not
None
:
query
=
query
.
offset
(
page
*
self
.
page_size
)
query
=
query
.
limit
(
self
.
page_size
)
...
...
flask_adminex/model.py
View file @
4f019346
...
...
@@ -4,29 +4,115 @@ from .base import BaseView, expose
class
BaseModelView
(
BaseView
):
"""
Base model view.
Does not make any assumptions on how models are stored or managed, but expects following:
1. Model is an object
2. Model contains properties
3. Each model contains 'id' attribute which uniquely identifies it (TBD: Make it more flexible)
4. You can get list of sorted models with pagination applied from a data source
5. You can get one model by its 'id' from the data source
Essentially, if you want to support new data store, all you have to do:
1. Derive from `BaseModelView` class
2. Implement various data-related methods (`get_list`, `get_one`, `create_model`, etc)
3. Implement automatic form generation from the model representation (`scaffold_form`, etc)
"""
# Permissions
can_create
=
True
"""Is model creation allowed"""
can_edit
=
True
"""Is model editing allowed"""
can_delete
=
True
"""Is model deletion allowed"""
# Templates
list_template
=
'admin/model/list.html'
"""Default list view template"""
edit_template
=
'admin/model/edit.html'
"""Default edit template"""
create_template
=
'admin/model/edit.html'
"""Default create template"""
# Customizations
list_columns
=
None
"""
Collection of the model field names for the list view.
If set to `None`, will get them from the model.
For example::
class MyModelView(BaseModelView):
list_columns = ('name', 'last_name', 'email')
If you want to rename column, use tuple instead of the name,
where first value is field name and second is display name.
You can also mix these values::
class MyModelView(BaseModelView):
list_columns = (('name', 'First Name'),
('last_name', 'Family Name'),
'email')
"""
sortable_columns
=
None
"""
Dictionary of the sortable columns names and property references.
If set to `None`, will get them from the model.
For example::
class MyModelView(BaseModelView):
sortable_columns = dict(name='name', user='user.id')
"""
form_columns
=
None
"""
Collection of the model field names for the form. If set to `None` will
get them from the model.
For example:
class MyModelView(BaseModelView):
list_columns = ('name', 'email')
"""
# Various settings
page_size
=
20
def
__init__
(
self
,
model
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
# If name not provided, it is modelname
"""
Page size. You can change it to something you want.
"""
def
__init__
(
self
,
model
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
"""
Constructor.
`model`
Model class
`name`
View name. If not provided, will use model class name
`category`
View category
`endpoint`
Base endpoint. If not provided, will use model name + 'view'.
For example if model name was 'User', endpoint will be
'userview'
`url`
Base URL. If not provided, will use endpoint as a URL.
"""
# If name not provided, it is model name
if
name
is
None
:
name
=
'
%
s'
%
self
.
_prettify_name
(
model
.
__name__
)
# If endpoint not provided, it is modelname + 'view'
# If endpoint not provided, it is model
name + 'view'
if
endpoint
is
None
:
endpoint
=
(
'
%
sview'
%
model
.
__name__
)
.
lower
()
...
...
@@ -35,26 +121,134 @@ class BaseModelView(BaseView):
self
.
model
=
model
# Scaffolding
self
.
list_columns
=
self
.
get_list_columns
()
self
.
_list_columns
=
self
.
get_list_columns
()
self
.
_sortable_columns
=
self
.
get_sortable_columns
()
self
.
create_form
=
self
.
scaffold_create_form
()
self
.
edit_form
=
self
.
scaffold_edit_form
()
self
.
_create_form_class
=
self
.
scaffold_create_form
()
self
.
_edit_form_class
=
self
.
scaffold_edit_form
()
# Public API
def
scaffold_list_columns
(
self
):
"""
Return list of the model field names. Must be implemented in
the child class.
Expected return format is list of tuples with field name and
display text. For example::
[('name', 'Name'),
('email', 'Email'),
('last_name', 'Last Name')]
"""
raise
NotImplemented
(
'Please implement scaffold_list_columns method'
)
def
get_list_columns
(
self
):
raise
NotImplemented
(
'Please implement get_list_columns method'
)
"""
Returns list of the model field names. If `list_columns` was
set, returns it. Otherwise calls `scaffold_list_columns`
to generate list from the model.
"""
if
self
.
list_columns
is
None
:
columns
=
self
.
scaffold_list_columns
()
else
:
columns
=
[]
for
c
in
self
.
list_columns
:
if
not
isinstance
(
c
,
tuple
):
columns
.
append
((
c
,
self
.
prettify_name
(
c
)))
else
:
columns
.
append
(
c
)
return
columns
def
scaffold_sortable_columns
(
self
):
"""
Returns dictionary of sortable columns. Must be implemented in
the child class.
Expected return format is dictionary, where key is field name and
value is property name.
"""
raise
NotImplemented
(
'Please implement scaffold_sortable_columns method'
)
def
get_sortable_columns
(
self
):
"""
Returns dictionary of the sortable columns. Key is a model
field name and value is sort column (for example - attribute).
If `sortable_columns` is set, will use it. Otherwise, will call
`scaffold_sortable_columns` to get them from the model.
"""
if
self
.
sortable_columns
is
None
:
print
self
.
__class__
.
__name__
return
self
.
scaffold_sortable_columns
()
else
:
return
self
.
sortable_columns
def
scaffold_form
(
self
):
"""
Create WTForm class from the model. Must be implemented in
the child class.
"""
raise
NotImplemented
(
'Please implement scaffold_form method'
)
def
scaffold_create_form
(
self
):
"""
Create form class for model creation view.
Override to implement customized behavior.
"""
return
self
.
scaffold_form
()
def
scaffold_edit_form
(
self
):
"""
Create form class for model editing view.
Override to implement customized behavior.
"""
return
self
.
scaffold_form
()
def
create_form
(
self
,
form
,
obj
=
None
):
"""
Instantiate model creation form and return it.
Override to implement custom behavior.
"""
return
self
.
_create_form_class
(
form
,
obj
)
def
edit_form
(
self
,
form
,
obj
=
None
):
"""
Instantiate model editing form and return it.
Override to implement custom behavior.
"""
return
self
.
_edit_form_class
(
form
,
obj
)
# Helpers
def
is_sortable
(
self
,
name
):
return
name
in
self
.
_sortable_columns
def
_get_column_by_idx
(
self
,
idx
):
if
idx
is
None
or
idx
<
0
or
idx
>=
len
(
self
.
_list_columns
):
return
None
return
self
.
_list_columns
[
idx
]
# Database-related API
def
get_list
(
self
,
page
,
sort_field
,
sort_desc
):
"""
Return list of models from the data source with applied pagination
and sorting.
Must be implemented in child class.
`page`
Page number, 0 based. Can be set to None if it is first page.
`sort_field`
Sort field index in the `self.list_columns` or None.
`sort_desc`
If set to True, sorting is in descending order.
"""
raise
NotImplemented
(
'Please implement get_list method'
)
def
get_one
(
self
,
id
):
...
...
@@ -76,7 +270,7 @@ class BaseModelView(BaseView):
# URL generation helper
def
_get_extra_args
(
self
):
page
=
request
.
args
.
get
(
'page'
,
None
,
type
=
int
)
page
=
request
.
args
.
get
(
'page'
,
0
,
type
=
int
)
sort
=
request
.
args
.
get
(
'sort'
,
None
,
type
=
int
)
sort_desc
=
request
.
args
.
get
(
'desc'
,
None
,
type
=
int
)
...
...
@@ -101,6 +295,10 @@ class BaseModelView(BaseView):
# Various URL generation helpers
def
pager_url
(
p
):
# Do not add page number if it is first page
if
p
==
0
:
p
=
None
return
self
.
_get_url
(
'.index_view'
,
p
,
sort
,
sort_desc
)
def
sort_url
(
column
,
invert
=
False
):
...
...
@@ -111,10 +309,17 @@ class BaseModelView(BaseView):
return
self
.
_get_url
(
'.index_view'
,
page
,
column
,
desc
)
def
get_value
(
obj
,
field
):
return
getattr
(
obj
,
field
,
None
)
return
render_template
(
self
.
list_template
,
view
=
self
,
data
=
data
,
# Return URL
# List
list_columns
=
self
.
_list_columns
,
sortable_columns
=
self
.
_sortable_columns
,
# Stuff
get_value
=
get_value
,
return_url
=
self
.
_get_url
(
'.index_view'
,
page
,
sort
,
sort_desc
),
# Pagination
pager_url
=
pager_url
,
...
...
flask_adminex/templates/admin/lib.html
View file @
4f019346
...
...
@@ -2,58 +2,70 @@
{% if pages > 1 %}
<div
class=
"pagination"
>
<ul>
{% if not page %}
{% set page = 1 %}
{% endif %}
{% set min = page - 3 %}
{% set max = page + 3 + 1 %}
{% if min
<
0
%}
{%
set
max =
max
-
min
+
1
%}
{%
set
max =
max
-
min
%}
{%
endif
%}
{%
if
max
>
pages %}
{% set min = min
+ pages - max - 1
%}
{%
if
max
>
=
pages %}
{% set min = min
- max + pages
%}
{% endif %}
{% if min
<
1
%}
{%
set
min =
1
%}
{% if min
<
0
%}
{%
set
min =
0
%}
{%
endif
%}
{%
if
max
>
pages + 1
%}
{% set max = pages
+ 1
%}
{%
if
max
>
= pages
%}
{% set max = pages %}
{% endif %}
{% if min >
1
%}
{% if min >
0
%}
<li>
<a
href=
"{{ generator(1) }}"
>
<<
</a>
<a
href=
"{{ generator(0) }}"
>
«
</a>
</li>
{% else %}
<li
class=
"disabled"
>
<a
href=
"#"
>
«
</a>
</li>
{% endif %}
{% if page >
1
%}
{% if page >
0
%}
<li>
<a
href=
"{{ generator(page-1) }}"
>
<
</a>
</li>
{% else %}
<li
class=
"disabled"
>
<a
href=
"#"
>
<
</a>
</li>
{% endif %}
{% for p in range(min, max) %}
{% if page == p %}
<li
class=
"
disabled
"
>
<a
href=
"#"
>
{{ p }}
</a>
<li
class=
"
active
"
>
<a
href=
"#"
>
{{ p
+ 1
}}
</a>
</li>
{% else %}
<li>
<a
href=
"{{ generator(p) }}"
>
{{ p }}
</a>
<a
href=
"{{ generator(p) }}"
>
{{ p
+ 1
}}
</a>
</li>
{% endif %}
{% endfor %}
{% if page
+1
<
=
pages
%}
{% if page
+ 1
<
pages
%}
<
li
>
<a
href=
"{{ generator(page+1) }}"
>
>
</a>
<a
href=
"{{ generator(page + 1) }}"
>
>
</a>
</li>
{% else %}
<li
class=
"disabled"
>
<a
href=
"#"
>
>
</a>
</li>
{% endif %}
{% if max
<
=
pages
%}
{% if max
<
pages
%}
<
li
>
<a
href=
"{{ generator(pages) }}"
>
>>
</a>
<a
href=
"{{ generator(pages - 1) }}"
>
»
</a>
</li>
{% else %}
<li
class=
"disabled"
>
<a
href=
"#"
>
»
</a>
</li>
{% endif %}
</ul>
...
...
flask_adminex/templates/admin/model/list.html
View file @
4f019346
...
...
@@ -7,7 +7,7 @@
<tr>
<th
class=
"span1"
>
</th>
{% set column = 0 %}
{% for c, name in
view.
list_columns %}
{% for c, name in list_columns %}
<th>
{% if sort_column == column %}
<a
href=
"{{ sort_url(column, True) }}"
>
...
...
@@ -40,14 +40,14 @@
</a>
{%- endif -%}
</td>
{% for c, name in
view.
list_columns %}
<td>
{{
row[c]
}}
</td>
{% for c, name in list_columns %}
<td>
{{
get_value(row, c)
}}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{{ lib.pager(page, num_pages, pager_url) }}
{% if view.can_create %}
<a
class=
"btn btn-primary btn-large"
href=
"{{ url_for('.create_view') }}"
>
Create New
</a>
<a
class=
"btn btn-primary btn-large"
href=
"{{ url_for('.create_view'
, return=return_url
) }}"
>
Create New
</a>
{% endif %}
{% endblock %}
flask_adminex/tools.py
0 → 100644
View file @
4f019346
import
sys
,
traceback
def
import_module
(
name
,
required
=
True
):
try
:
__import__
(
name
,
globals
(),
locals
(),
[])
except
ImportError
:
if
not
required
and
module_not_found
():
return
None
raise
return
sys
.
modules
[
name
]
def
import_attribute
(
name
):
"""
Import attribute using string reference.
Example:
import_attribute('a.b.c.foo')
Throws ImportError or AttributeError if module or attribute do not exist.
"""
path
,
attr
=
name
.
rsplit
(
'.'
,
1
)
module
=
__import__
(
path
,
globals
(),
locals
(),
[
attr
])
return
getattr
(
module
,
attr
)
def
module_not_found
(
additional_depth
=
0
):
'''Checks if ImportError was raised because module does not exist or
something inside it raised ImportError
- additional_depth - supply int of depth of your call if you're not doing
import on the same level of code - f.e., if you call function, which is
doing import, you should pass 1 for single additional level of depth
'''
tb
=
sys
.
exc_info
()[
2
]
if
len
(
traceback
.
extract_tb
(
tb
))
>
(
1
+
additional_depth
):
return
False
return
True
def
rec_getattr
(
obj
,
attr
,
default
=
None
):
try
:
return
reduce
(
getattr
,
attr
.
split
(
'.'
),
obj
)
except
AttributeError
:
return
None
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment