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
52131105
Commit
52131105
authored
Mar 20, 2012
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Restyled edit form, more documentation.
parent
80b8377d
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
196 additions
and
48 deletions
+196
-48
TODO.txt
TODO.txt
+2
-2
simple.py
examples/sqla/simple.py
+5
-1
base.py
flask_adminex/base.py
+19
-2
sqlamodel.py
flask_adminex/ext/sqlamodel.py
+75
-1
model.py
flask_adminex/model.py
+23
-1
edit.html
flask_adminex/templates/admin/model/edit.html
+30
-29
tools.py
flask_adminex/tools.py
+42
-12
No files found.
TODO.txt
View file @
52131105
...
@@ -2,9 +2,9 @@
...
@@ -2,9 +2,9 @@
- Pregenerate URLs for menu
- Pregenerate URLs for menu
- Override base URL (/admin/)
- Override base URL (/admin/)
- Model Admin
- Model Admin
Ability to sort by fields that are not visible?
- Ability to sort by fields that are not visible?
- Proper error display
- SQLA Model Admin
- SQLA Model Admin
- Sort by foreign key
- Validation of the joins in the query
- Validation of the joins in the query
- Automatic joined load for foreign keys
- Automatic joined load for foreign keys
- Automatic PK detection
- Automatic PK detection
...
...
examples/sqla/simple.py
View file @
52131105
from
flask
import
Flask
from
flask
import
Flask
from
flaskext.sqlalchemy
import
SQLAlchemy
from
flaskext.sqlalchemy
import
SQLAlchemy
from
flask.ext
import
adminex
from
flask.ext
import
adminex
,
wtf
from
flask.ext.adminex.ext
import
sqlamodel
from
flask.ext.adminex.ext
import
sqlamodel
# Create application
# Create application
...
@@ -52,6 +52,10 @@ class PostAdmin(sqlamodel.ModelView):
...
@@ -52,6 +52,10 @@ class PostAdmin(sqlamodel.ModelView):
sortable_columns
=
(
'title'
,
(
'user'
,
User
.
username
))
sortable_columns
=
(
'title'
,
(
'user'
,
User
.
username
))
rename_columns
=
dict
(
title
=
'Tiiitle'
)
rename_columns
=
dict
(
title
=
'Tiiitle'
)
form_args
=
dict
(
text
=
dict
(
label
=
'Big Text'
,
validators
=
[
wtf
.
required
()])
)
def
__init__
(
self
,
session
):
def
__init__
(
self
,
session
):
super
(
PostAdmin
,
self
)
.
__init__
(
Post
,
session
)
super
(
PostAdmin
,
self
)
.
__init__
(
Post
,
session
)
...
...
flask_adminex/base.py
View file @
52131105
...
@@ -84,6 +84,7 @@ class BaseView(object):
...
@@ -84,6 +84,7 @@ class BaseView(object):
def
__init__
(
self
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
,
static_folder
=
None
):
def
__init__
(
self
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
,
static_folder
=
None
):
"""
"""
Constructor.
Constructor.
`name`
`name`
Name of this view. If not provided, will be defaulted to the class name.
Name of this view. If not provided, will be defaulted to the class name.
`category`
`category`
...
@@ -111,6 +112,9 @@ class BaseView(object):
...
@@ -111,6 +112,9 @@ class BaseView(object):
def
_set_admin
(
self
,
admin
):
def
_set_admin
(
self
,
admin
):
"""
"""
Associate this view with Admin class instance.
Associate this view with Admin class instance.
`admin`
Admin instance
"""
"""
self
.
admin
=
admin
self
.
admin
=
admin
...
@@ -145,6 +149,9 @@ class BaseView(object):
...
@@ -145,6 +149,9 @@ class BaseView(object):
def
_prettify_name
(
self
,
name
):
def
_prettify_name
(
self
,
name
):
"""
"""
Prettify class name by splitting name by capital characters. So, 'MySuperClass' will look like 'My Super Class'
Prettify class name by splitting name by capital characters. So, 'MySuperClass' will look like 'My Super Class'
`name`
String to prettify
"""
"""
return
sub
(
r'(?<=.)([A-Z])'
,
r' \1'
,
name
)
return
sub
(
r'(?<=.)([A-Z])'
,
r' \1'
,
name
)
...
@@ -176,6 +183,12 @@ class AdminIndexView(BaseView):
...
@@ -176,6 +183,12 @@ class AdminIndexView(BaseView):
return render_template('adminhome.html')
return render_template('adminhome.html')
admin = Admin(index_view=MyHomeView)
admin = Admin(index_view=MyHomeView)
By default, has following rules:
1. If name is not provided, will use 'Home'
2. If endpoint is not provided, will use 'admin'
3. If url is not provided, will use '/admin'
4. Automatically associates with static folder.
"""
"""
def
__init__
(
self
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
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'
)
super
(
AdminIndexView
,
self
)
.
__init__
(
name
or
'Home'
,
category
,
endpoint
or
'admin'
,
url
or
'/admin'
,
'static'
)
...
@@ -186,6 +199,9 @@ class AdminIndexView(BaseView):
...
@@ -186,6 +199,9 @@ class AdminIndexView(BaseView):
class
MenuItem
(
object
):
class
MenuItem
(
object
):
"""
Simple menu tree hierarchy.
"""
def
__init__
(
self
,
name
,
view
=
None
):
def
__init__
(
self
,
name
,
view
=
None
):
self
.
name
=
name
self
.
name
=
name
self
.
_view
=
view
self
.
_view
=
view
...
@@ -204,6 +220,7 @@ class MenuItem(object):
...
@@ -204,6 +220,7 @@ class MenuItem(object):
if
self
.
_view
is
None
:
if
self
.
_view
is
None
:
return
None
return
None
# TODO: Optimize me
return
url_for
(
'
%
s.
%
s'
%
(
self
.
_view
.
endpoint
,
self
.
_view
.
_default_view
))
return
url_for
(
'
%
s.
%
s'
%
(
self
.
_view
.
endpoint
,
self
.
_view
.
_default_view
))
def
is_active
(
self
,
view
):
def
is_active
(
self
,
view
):
...
@@ -237,7 +254,7 @@ class Admin(object):
...
@@ -237,7 +254,7 @@ class Admin(object):
Constructor.
Constructor.
`name`
`name`
Application name. Will be displayed in main menu and as a page title. If not provided, defaulted to "
Flask
"
Application name. Will be displayed in main menu and as a page title. If not provided, defaulted to "
Admin
"
`index_view`
`index_view`
Home page view to use. If not provided, will use `AdminIndexView`.
Home page view to use. If not provided, will use `AdminIndexView`.
"""
"""
...
@@ -245,7 +262,7 @@ class Admin(object):
...
@@ -245,7 +262,7 @@ class Admin(object):
self
.
_menu
=
[]
self
.
_menu
=
[]
if
name
is
None
:
if
name
is
None
:
name
=
'
Flask
'
name
=
'
Admin
'
self
.
name
=
name
self
.
name
=
name
if
index_view
is
None
:
if
index_view
is
None
:
...
...
flask_adminex/ext/sqlamodel.py
View file @
52131105
...
@@ -14,6 +14,9 @@ from flask.ext.adminex.model import BaseModelView
...
@@ -14,6 +14,9 @@ from flask.ext.adminex.model import BaseModelView
class
AdminModelConverter
(
ModelConverter
):
class
AdminModelConverter
(
ModelConverter
):
"""
SQLAlchemy model to form converter
"""
def
__init__
(
self
,
session
):
def
__init__
(
self
,
session
):
super
(
AdminModelConverter
,
self
)
.
__init__
()
super
(
AdminModelConverter
,
self
)
.
__init__
()
...
@@ -48,14 +51,40 @@ class AdminModelConverter(ModelConverter):
...
@@ -48,14 +51,40 @@ class AdminModelConverter(ModelConverter):
class
ModelView
(
BaseModelView
):
class
ModelView
(
BaseModelView
):
"""
SQLALchemy model view
Usage sample::
admin = ModelView(User, db.session)
"""
def
__init__
(
self
,
model
,
session
,
def
__init__
(
self
,
model
,
session
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
"""
Constructor.
`model`
Model class
`session`
SQLALchemy session
`name`
View name. If not set, will default to model name
`category`
Category name
`endpoint`
Endpoint name. If not set, will default to model name
`url`
Base URL. If not set, will default to '/admin/' + endpoint
"""
self
.
session
=
session
self
.
session
=
session
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
#
Public API
#
Scaffolding
def
scaffold_list_columns
(
self
):
def
scaffold_list_columns
(
self
):
"""
Return list of columns from the model.
"""
columns
=
[]
columns
=
[]
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
...
@@ -76,6 +105,10 @@ class ModelView(BaseModelView):
...
@@ -76,6 +105,10 @@ class ModelView(BaseModelView):
return
columns
return
columns
def
scaffold_sortable_columns
(
self
):
def
scaffold_sortable_columns
(
self
):
"""
Return dictionary of sortable columns.
Key is column name, value is sort column/field.
"""
columns
=
dict
()
columns
=
dict
()
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
mapper
=
self
.
model
.
_sa_class_manager
.
mapper
...
@@ -93,13 +126,29 @@ class ModelView(BaseModelView):
...
@@ -93,13 +126,29 @@ class ModelView(BaseModelView):
return
columns
return
columns
def
scaffold_form
(
self
):
def
scaffold_form
(
self
):
"""
Create form from the model.
"""
return
model_form
(
self
.
model
,
return
model_form
(
self
.
model
,
wtf
.
Form
,
wtf
.
Form
,
self
.
form_columns
,
self
.
form_columns
,
field_args
=
self
.
form_args
,
converter
=
AdminModelConverter
(
self
.
session
))
converter
=
AdminModelConverter
(
self
.
session
))
# Database-related API
# Database-related API
def
get_list
(
self
,
page
,
sort_column
,
sort_desc
,
execute
=
True
):
def
get_list
(
self
,
page
,
sort_column
,
sort_desc
,
execute
=
True
):
"""
Return models from the database.
`page`
Page number
`sort_column`
Sort column name
`sort_desc`
Descending or ascending sort
`execute`
Execute query immediately? Default is `True`
"""
query
=
self
.
session
.
query
(
self
.
model
)
query
=
self
.
session
.
query
(
self
.
model
)
count
=
query
.
count
()
count
=
query
.
count
()
...
@@ -133,16 +182,29 @@ class ModelView(BaseModelView):
...
@@ -133,16 +182,29 @@ class ModelView(BaseModelView):
query
=
query
.
limit
(
self
.
page_size
)
query
=
query
.
limit
(
self
.
page_size
)
# Execute if needed
if
execute
:
if
execute
:
query
=
query
.
all
()
query
=
query
.
all
()
return
count
,
query
return
count
,
query
def
get_one
(
self
,
id
):
def
get_one
(
self
,
id
):
"""
Return one model by its id.
`id`
Model
"""
return
self
.
session
.
query
(
self
.
model
)
.
get
(
id
)
return
self
.
session
.
query
(
self
.
model
)
.
get
(
id
)
# Model handlers
# Model handlers
def
create_model
(
self
,
form
):
def
create_model
(
self
,
form
):
"""
Create model from form.
`form`
Form instance
"""
try
:
try
:
model
=
self
.
model
()
model
=
self
.
model
()
form
.
populate_obj
(
model
)
form
.
populate_obj
(
model
)
...
@@ -154,6 +216,12 @@ class ModelView(BaseModelView):
...
@@ -154,6 +216,12 @@ class ModelView(BaseModelView):
return
False
return
False
def
update_model
(
self
,
form
,
model
):
def
update_model
(
self
,
form
,
model
):
"""
Update model from form.
`form`
Form instance
"""
try
:
try
:
form
.
populate_obj
(
model
)
form
.
populate_obj
(
model
)
self
.
session
.
commit
()
self
.
session
.
commit
()
...
@@ -163,5 +231,11 @@ class ModelView(BaseModelView):
...
@@ -163,5 +231,11 @@ class ModelView(BaseModelView):
return
False
return
False
def
delete_model
(
self
,
model
):
def
delete_model
(
self
,
model
):
"""
Delete model.
`model`
Model to delete
"""
self
.
session
.
delete
(
model
)
self
.
session
.
delete
(
model
)
self
.
session
.
commit
()
self
.
session
.
commit
()
flask_adminex/model.py
View file @
52131105
...
@@ -96,6 +96,19 @@ class BaseModelView(BaseView):
...
@@ -96,6 +96,19 @@ class BaseModelView(BaseView):
list_columns = ('name', 'email')
list_columns = ('name', 'email')
"""
"""
form_args
=
None
"""
Dictionary of form field arguments. Refer to WTForm documentation on
list of possible options.
Example::
class MyModelView(BaseModelView):
form_args = dict(
name=dict(label='First Name', validators=[wtf.required()])
}
"""
# Various settings
# Various settings
page_size
=
20
page_size
=
20
"""
"""
...
@@ -343,7 +356,7 @@ class BaseModelView(BaseView):
...
@@ -343,7 +356,7 @@ class BaseModelView(BaseView):
`name`
`name`
Name to prettify
Name to prettify
"""
"""
return
' '
.
join
(
x
.
capitalize
()
for
x
in
name
.
split
(
'_'
)
)
return
name
.
replace
(
'_'
,
' '
)
.
title
(
)
# URL generation helper
# URL generation helper
def
_get_extra_args
(
self
):
def
_get_extra_args
(
self
):
...
@@ -434,6 +447,9 @@ class BaseModelView(BaseView):
...
@@ -434,6 +447,9 @@ class BaseModelView(BaseView):
@
expose
(
'/new/'
,
methods
=
(
'GET'
,
'POST'
))
@
expose
(
'/new/'
,
methods
=
(
'GET'
,
'POST'
))
def
create_view
(
self
):
def
create_view
(
self
):
"""
Create model view
"""
return_url
=
request
.
args
.
get
(
'return'
)
return_url
=
request
.
args
.
get
(
'return'
)
if
not
self
.
can_create
:
if
not
self
.
can_create
:
...
@@ -449,6 +465,9 @@ class BaseModelView(BaseView):
...
@@ -449,6 +465,9 @@ class BaseModelView(BaseView):
@
expose
(
'/edit/<int:id>/'
,
methods
=
(
'GET'
,
'POST'
))
@
expose
(
'/edit/<int:id>/'
,
methods
=
(
'GET'
,
'POST'
))
def
edit_view
(
self
,
id
):
def
edit_view
(
self
,
id
):
"""
Edit model view
"""
return_url
=
request
.
args
.
get
(
'return'
)
return_url
=
request
.
args
.
get
(
'return'
)
if
not
self
.
can_edit
:
if
not
self
.
can_edit
:
...
@@ -472,6 +491,9 @@ class BaseModelView(BaseView):
...
@@ -472,6 +491,9 @@ class BaseModelView(BaseView):
@
expose
(
'/delete/<int:id>/'
)
@
expose
(
'/delete/<int:id>/'
)
def
delete_view
(
self
,
id
):
def
delete_view
(
self
,
id
):
"""
Delete model view
"""
return_url
=
request
.
args
.
get
(
'return'
)
return_url
=
request
.
args
.
get
(
'return'
)
# TODO: Use post
# TODO: Use post
...
...
flask_adminex/templates/admin/model/edit.html
View file @
52131105
{% extends 'admin/master.html' %}
{% extends 'admin/master.html' %}
{% block body %}
{% block body %}
<form
action=
""
method=
"POST"
>
<form
action=
""
method=
"POST"
class=
"form-horizontal"
>
{{ form.csrf }}
<fieldset>
<table
class=
"form"
>
{{ form.csrf }}
{% for f in form if f.label.text != 'Csrf' %}
{% for f in form if f.label.text != 'Csrf' %}
<tr>
{% if f.name in form.errors %}
<td>
<div
class=
"control-group error"
>
{{ f.label }}
{% else %}
</td>
<div
class=
"control-group"
>
<td>
{% endif %}
{{ f }}
{{ f.label(class='control-label') }}
</td>
<div
class=
"controls"
>
</tr>
<div>
{{ f }}
</div>
{% if f.name in form.errors %}
<ul>
{% for e in form.errors[f.name] %}
<li>
{{ e }}
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
{% endfor %}
<tr>
<div
class=
"control-group"
>
<td>
<div
class=
"controls"
>
</td>
<input
type=
"submit"
class=
"btn btn-primary btn-large"
/>
<td>
<a
href=
"{{ return_url }}"
class=
"btn btn-large"
>
Cancel
</a>
{% if form.errors %}
</div>
<ul>
</div>
{% for fn, fe in form.errors.items() if fe %}
</fieldset>
{% for e in fe %}
<li>
{{ e }}
</li>
{% endfor %}
{% endfor %}
</ul>
{% endif %}
<input
type=
"submit"
class=
"btn btn-primary btn-large"
/>
<a
href=
"{{ return_url }}"
class=
"btn btn-large"
>
Cancel
</a>
</td>
</tr>
</table>
</form>
</form>
{% endblock %}
{% endblock %}
flask_adminex/tools.py
View file @
52131105
...
@@ -2,6 +2,16 @@ import sys, traceback
...
@@ -2,6 +2,16 @@ import sys, traceback
def
import_module
(
name
,
required
=
True
):
def
import_module
(
name
,
required
=
True
):
"""
Import module by name
`name`
Module name
`required`
If set to `True` and module was not found - will throw exception.
If set to `False` and module was not found - will return None.
Default is `True`.
"""
try
:
try
:
__import__
(
name
,
globals
(),
locals
(),
[])
__import__
(
name
,
globals
(),
locals
(),
[])
except
ImportError
:
except
ImportError
:
...
@@ -13,10 +23,17 @@ def import_module(name, required=True):
...
@@ -13,10 +23,17 @@ def import_module(name, required=True):
def
import_attribute
(
name
):
def
import_attribute
(
name
):
"""
"""
Import attribute using string reference.
Import attribute using string reference.
Example:
import_attribute('a.b.c.foo')
`name`
Throws ImportError or AttributeError if module or attribute do not exist.
String reference.
Throws ImportError or AttributeError if module or attribute do not exist.
Example::
import_attribute('a.b.c.foo')
"""
"""
path
,
attr
=
name
.
rsplit
(
'.'
,
1
)
path
,
attr
=
name
.
rsplit
(
'.'
,
1
)
module
=
__import__
(
path
,
globals
(),
locals
(),
[
attr
])
module
=
__import__
(
path
,
globals
(),
locals
(),
[
attr
])
...
@@ -25,13 +42,15 @@ def import_attribute(name):
...
@@ -25,13 +42,15 @@ def import_attribute(name):
def
module_not_found
(
additional_depth
=
0
):
def
module_not_found
(
additional_depth
=
0
):
'''Checks if ImportError was raised because module does not exist or
"""
something inside it raised ImportError
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
`additional_depth`
import on the same level of code - f.e., if you call function, which is
supply int of depth of your call if you're not doing
doing import, you should pass 1 for single additional level of depth
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
]
tb
=
sys
.
exc_info
()[
2
]
if
len
(
traceback
.
extract_tb
(
tb
))
>
(
1
+
additional_depth
):
if
len
(
traceback
.
extract_tb
(
tb
))
>
(
1
+
additional_depth
):
return
False
return
False
...
@@ -39,8 +58,19 @@ def module_not_found(additional_depth=0):
...
@@ -39,8 +58,19 @@ def module_not_found(additional_depth=0):
def
rec_getattr
(
obj
,
attr
,
default
=
None
):
def
rec_getattr
(
obj
,
attr
,
default
=
None
):
"""
Recursive getattr.
`attr`
Dot delimited attribute name
`default`
Default value
Example::
rec_getattr(obj, 'a.b.c')
"""
try
:
try
:
return
reduce
(
getattr
,
attr
.
split
(
'.'
),
obj
)
return
reduce
(
getattr
,
attr
.
split
(
'.'
),
obj
)
except
AttributeError
:
except
AttributeError
:
return
None
return
default
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