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
461db9a0
Commit
461db9a0
authored
Nov 22, 2012
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Basic MongoEngine backend
parent
66005b73
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
471 additions
and
4 deletions
+471
-4
TODO.txt
TODO.txt
+2
-2
simple.py
examples/mongoengine/simple.py
+47
-0
__init__.py
flask_admin/contrib/mongoengine/__init__.py
+6
-0
filters.py
flask_admin/contrib/mongoengine/filters.py
+127
-0
form.py
flask_admin/contrib/mongoengine/form.py
+14
-0
view.py
flask_admin/contrib/mongoengine/view.py
+272
-0
filters.py
flask_admin/contrib/peeweemodel/filters.py
+1
-1
view.py
flask_admin/contrib/peeweemodel/view.py
+2
-1
No files found.
TODO.txt
View file @
461db9a0
...
...
@@ -3,6 +3,8 @@
- Localization
- Create documentation
- Model Admin
- Simplify scaffold_list_columns - remove excluded_list_columns check, update documentation
- Reduce number of parameters passed to list view
- Filters
- Use table to draw filters so column names will line up?
...
...
@@ -17,8 +19,6 @@
- File size restriction
- Unit tests
- Form generation tests
- Inline form generation tests
- Documentation
- Add all new stuff
- Fixed stylesheet
examples/mongoengine/simple.py
0 → 100644
View file @
461db9a0
import
datetime
from
flask
import
Flask
from
flask.ext
import
admin
from
flask.ext.mongoengine
import
MongoEngine
from
flask.ext.admin.contrib
import
mongoengine
# Create application
app
=
Flask
(
__name__
)
# Create dummy secrey key so we can use sessions
app
.
config
[
'SECRET_KEY'
]
=
'123456790'
app
.
config
[
'MONGODB_SETTINGS'
]
=
{
'DB'
:
'testing'
}
# Create models
db
=
MongoEngine
()
db
.
init_app
(
app
)
class
Todo
(
db
.
Document
):
title
=
db
.
StringField
(
max_length
=
60
)
text
=
db
.
StringField
()
done
=
db
.
BooleanField
(
default
=
False
)
pub_date
=
db
.
DateTimeField
(
default
=
datetime
.
datetime
.
now
)
# Required for administrative interface
def
__unicode__
(
self
):
return
self
.
title
# Flask views
@
app
.
route
(
'/'
)
def
index
():
return
'<a href="/admin/">Click me to get to Admin!</a>'
if
__name__
==
'__main__'
:
# Create admin
admin
=
admin
.
Admin
(
app
,
'Simple Models'
)
# Add views
admin
.
add_view
(
mongoengine
.
ModelView
(
Todo
))
# Start app
app
.
debug
=
True
app
.
run
(
'0.0.0.0'
,
8000
)
flask_admin/contrib/mongoengine/__init__.py
0 → 100644
View file @
461db9a0
try
:
import
flask.ext.mongoengine
except
ImportError
:
raise
Exception
(
'Please install flask-mongoengine in order to use mongoengine backend'
)
from
.view
import
ModelView
flask_admin/contrib/mongoengine/filters.py
0 → 100644
View file @
461db9a0
from
flask.ext.admin.babel
import
gettext
from
flask.ext.admin.model
import
filters
class
BaseMongoEngineFilter
(
filters
.
BaseFilter
):
"""
Base MongoEngine filter.
"""
def
__init__
(
self
,
column
,
name
,
options
=
None
,
data_type
=
None
):
"""
Constructor.
:param column:
Model field
:param name:
Display name
:param options:
Fixed set of options
:param data_type:
Client data type
"""
super
(
BaseMongoEngineFilter
,
self
)
.
__init__
(
name
,
options
,
data_type
)
self
.
column
=
column
# Common filters
class
FilterEqual
(
BaseMongoEngineFilter
):
def
apply
(
self
,
query
,
value
):
flt
=
{
'
%
s'
%
self
.
column
.
name
:
value
}
return
query
.
filter
(
**
flt
)
def
operation
(
self
):
return
gettext
(
'equals'
)
class
FilterNotEqual
(
BaseMongoEngineFilter
):
def
apply
(
self
,
query
,
value
):
flt
=
{
'
%
s__ne'
%
self
.
column
.
name
:
value
}
return
query
.
filter
(
**
flt
)
def
operation
(
self
):
return
gettext
(
'not equal'
)
class
FilterLike
(
BaseMongoEngineFilter
):
def
apply
(
self
,
query
,
value
):
term
,
data
=
parse_like_term
(
value
)
flt
=
{
'
%
s__
%
s'
%
(
self
.
column
.
name
,
term
):
data
}
return
query
.
filter
(
**
flt
)
def
operation
(
self
):
return
gettext
(
'contains'
)
class
FilterNotLike
(
BaseMongoEngineFilter
):
def
apply
(
self
,
query
,
value
):
term
,
data
=
parse_like_term
(
value
)
flt
=
{
'
%
s__not__
%
s'
%
(
self
.
column
.
name
,
term
):
data
}
return
query
.
filter
(
**
flt
)
def
operation
(
self
):
return
gettext
(
'not contains'
)
class
FilterGreater
(
BaseMongoEngineFilter
):
def
apply
(
self
,
query
,
value
):
flt
=
{
'
%
s__gt'
%
self
.
column
.
name
:
value
}
return
query
.
filter
(
**
flt
)
def
operation
(
self
):
return
gettext
(
'greater than'
)
class
FilterSmaller
(
BaseMongoEngineFilter
):
def
apply
(
self
,
query
,
value
):
flt
=
{
'
%
s__lt'
%
self
.
column
.
name
:
value
}
return
query
.
filter
(
**
flt
)
def
operation
(
self
):
return
gettext
(
'smaller than'
)
# Customized type filters
class
BooleanEqualFilter
(
FilterEqual
,
filters
.
BaseBooleanFilter
):
pass
class
BooleanNotEqualFilter
(
FilterNotEqual
,
filters
.
BaseBooleanFilter
):
pass
# Base peewee filter field converter
class
FilterConverter
(
filters
.
BaseFilterConverter
):
strings
=
(
FilterEqual
,
FilterNotEqual
,
FilterLike
,
FilterNotLike
)
numeric
=
(
FilterEqual
,
FilterNotEqual
,
FilterGreater
,
FilterSmaller
)
def
convert
(
self
,
type_name
,
column
,
name
):
#print type_name, column, name
if
type_name
in
self
.
converters
:
return
self
.
converters
[
type_name
](
column
,
name
)
return
None
@
filters
.
convert
(
'StringField'
)
def
conv_string
(
self
,
column
,
name
):
return
[
f
(
column
,
name
)
for
f
in
self
.
strings
]
@
filters
.
convert
(
'BooleanField'
)
def
conv_bool
(
self
,
column
,
name
):
return
[
BooleanEqualFilter
(
column
,
name
),
BooleanNotEqualFilter
(
column
,
name
)]
@
filters
.
convert
(
'IntField'
,
'DecimalField'
,
'FloatField'
)
def
conv_int
(
self
,
column
,
name
):
return
[
f
(
column
,
name
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'DateField'
)
def
conv_date
(
self
,
column
,
name
):
return
[
f
(
column
,
name
,
data_type
=
'datepicker'
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'DateTimeField'
)
def
conv_datetime
(
self
,
column
,
name
):
return
[
f
(
column
,
name
,
data_type
=
'datetimepicker'
)
for
f
in
self
.
numeric
]
flask_admin/contrib/mongoengine/form.py
0 → 100644
View file @
461db9a0
from
flask.ext.mongoengine.wtf
import
orm
from
flask.ext.admin.form
import
BaseForm
class
CustomModelConverter
(
orm
.
ModelConverter
):
pass
def
model_form
(
model
,
base_class
=
BaseForm
,
only
=
None
,
exclude
=
None
,
field_args
=
None
,
converter
=
None
):
return
orm
.
model_form
(
model
,
base_class
=
base_class
,
only
=
only
,
exclude
=
exclude
,
field_args
=
field_args
,
converter
=
converter
)
flask_admin/contrib/mongoengine/view.py
0 → 100644
View file @
461db9a0
import
logging
from
flask
import
flash
from
flask.ext.admin.babel
import
gettext
,
ngettext
,
lazy_gettext
from
flask.ext.admin.model
import
BaseModelView
import
mongoengine
from
bson.objectid
import
ObjectId
from
flask.ext.admin.actions
import
action
from
flask.ext.admin.form
import
BaseForm
from
.filters
import
FilterConverter
,
BaseMongoEngineFilter
from
.form
import
model_form
,
CustomModelConverter
SORTABLE_FIELDS
=
set
((
mongoengine
.
StringField
,
mongoengine
.
IntField
,
mongoengine
.
FloatField
,
mongoengine
.
BooleanField
,
mongoengine
.
DateTimeField
,
mongoengine
.
ObjectIdField
,
mongoengine
.
DecimalField
,
mongoengine
.
ReferenceField
,
mongoengine
.
EmailField
,
mongoengine
.
UUIDField
))
class
ModelView
(
BaseModelView
):
column_filters
=
None
"""
Collection of the column filters.
Can contain either field names or instances of
:class:`flask.ext.admin.contrib.mongoengine.filters.BaseFilter`
classes.
For example::
class MyModelView(BaseModelView):
column_filters = ('user', 'email')
or::
class MyModelView(BaseModelView):
column_filters = (BooleanEqualFilter(User.name, 'Name'))
"""
model_form_converter
=
CustomModelConverter
"""
Model form conversion class. Use this to implement custom
field conversion logic.
For example::
class MyModelConverter(AdminModelConverter):
pass
class MyAdminView(ModelView):
model_form_converter = MyModelConverter
"""
filter_converter
=
FilterConverter
()
"""
Field to filter converter.
Override this attribute to use non-default converter.
"""
fast_mass_delete
=
False
"""
If set to `False` and user deletes more than one model using actions,
all models will be read from the database and then deleted one by one
giving SQLAlchemy chance to manually cleanup any dependencies
(many-to-many relationships, etc).
If set to True, will run DELETE statement which is somewhat faster, but
might leave corrupted data if you forget to configure DELETE CASCADE
for your model.
"""
def
__init__
(
self
,
model
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
self
.
_search_fields
=
[]
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
self
.
_primary_key
=
self
.
scaffold_pk
()
def
_get_model_fields
(
self
,
model
=
None
):
if
model
is
None
:
model
=
self
.
model
return
sorted
(
model
.
_fields
.
iteritems
(),
key
=
lambda
n
:
n
[
1
]
.
creation_counter
)
def
scaffold_pk
(
self
):
# MongoEngine models have predefined 'id' as a key
return
'id'
def
get_pk_value
(
self
,
model
):
return
model
.
pk
def
scaffold_list_columns
(
self
):
columns
=
[]
for
n
,
f
in
self
.
_get_model_fields
():
#import pdb; pdb.set_trace()
# Filter by name
if
(
self
.
excluded_list_columns
and
n
in
self
.
excluded_list_columns
):
continue
# Verify type
field_class
=
type
(
f
)
if
self
.
list_display_pk
or
field_class
!=
mongoengine
.
ObjectIdField
:
columns
.
append
(
n
)
return
columns
def
scaffold_sortable_columns
(
self
):
columns
=
{}
for
n
,
f
in
self
.
_get_model_fields
():
if
type
(
f
)
in
SORTABLE_FIELDS
:
if
self
.
list_display_pk
or
type
(
f
)
!=
mongoengine
.
ObjectIdField
:
columns
[
n
]
=
f
return
columns
def
init_search
(
self
):
return
bool
(
self
.
_search_fields
)
def
scaffold_filters
(
self
,
name
):
if
isinstance
(
name
,
basestring
):
attr
=
self
.
model
.
_fields
.
get
(
name
)
else
:
attr
=
name
if
attr
is
None
:
raise
Exception
(
'Failed to find field for filter:
%
s'
%
name
)
# Find name
visible_name
=
None
if
not
isinstance
(
name
,
basestring
):
visible_name
=
self
.
get_column_name
(
attr
.
name
)
if
not
visible_name
:
visible_name
=
self
.
get_column_name
(
name
)
# Convert filter
type_name
=
type
(
attr
)
.
__name__
flt
=
self
.
filter_converter
.
convert
(
type_name
,
attr
,
visible_name
)
return
flt
def
is_valid_filter
(
self
,
filter
):
return
isinstance
(
filter
,
BaseMongoEngineFilter
)
def
scaffold_form
(
self
):
# TODO: Fix base_class
form_class
=
model_form
(
self
.
model
,
base_class
=
BaseForm
,
only
=
self
.
form_columns
,
exclude
=
self
.
excluded_form_columns
,
field_args
=
self
.
form_args
,
converter
=
self
.
model_form_converter
())
return
form_class
def
get_list
(
self
,
page
,
sort_column
,
sort_desc
,
search
,
filters
,
execute
=
True
):
query
=
self
.
model
.
objects
# TODO: Filters
# Get count
count
=
query
.
count
()
# Sorting
if
sort_column
:
query
=
query
.
order_by
(
'
%
s
%
s'
%
(
'-'
if
sort_desc
else
''
,
sort_column
))
# Pagination
if
page
is
not
None
:
query
=
query
.
skip
(
page
*
self
.
page_size
)
query
=
query
.
limit
(
self
.
page_size
)
if
execute
:
query
=
query
.
all
()
return
count
,
query
def
get_one
(
self
,
id
):
return
self
.
model
.
objects
.
with_id
(
id
)
def
create_model
(
self
,
form
):
try
:
model
=
self
.
model
(
**
form
.
data
)
self
.
on_model_change
(
form
,
model
)
model
.
save
()
return
True
except
Exception
,
ex
:
flash
(
gettext
(
'Failed to create model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
logging
.
exception
(
'Failed to create model'
)
return
False
def
update_model
(
self
,
form
,
model
):
try
:
for
f
in
form
:
name
,
field
=
f
.
name
,
f
if
hasattr
(
model
,
name
)
and
getattr
(
model
,
name
)
!=
field
.
data
:
setattr
(
model
,
name
,
field
.
data
)
self
.
on_model_change
(
form
,
model
)
model
.
save
()
return
True
except
Exception
,
ex
:
flash
(
gettext
(
'Failed to update model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
logging
.
exception
(
'Failed to update model'
)
return
False
def
delete_model
(
self
,
model
):
try
:
self
.
on_model_delete
(
model
)
model
.
delete
()
return
True
except
Exception
,
ex
:
flash
(
gettext
(
'Failed to delete model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
logging
.
exception
(
'Failed to delete model'
)
return
False
# Default model actions
def
is_action_allowed
(
self
,
name
):
# Check delete action permission
if
name
==
'delete'
and
not
self
.
can_delete
:
return
False
return
super
(
ModelView
,
self
)
.
is_action_allowed
(
name
)
@
action
(
'delete'
,
lazy_gettext
(
'Delete'
),
lazy_gettext
(
'Are you sure you want to delete selected models?'
))
def
action_delete
(
self
,
ids
):
try
:
count
=
0
all_ids
=
[
ObjectId
(
pk
)
for
pk
in
ids
]
for
obj
in
self
.
model
.
objects
.
in_bulk
(
all_ids
)
.
values
():
obj
.
delete
()
count
+=
1
flash
(
ngettext
(
'Model was successfully deleted.'
,
'
%(count)
s models were successfully deleted.'
,
count
,
count
=
count
))
except
Exception
,
ex
:
flash
(
gettext
(
'Failed to delete models.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
flask_admin/contrib/peeweemodel/filters.py
View file @
461db9a0
...
...
@@ -6,7 +6,7 @@ from .tools import parse_like_term
class
BasePeeweeFilter
(
filters
.
BaseFilter
):
"""
Base
SQLAlchemy
filter.
Base
Peewee
filter.
"""
def
__init__
(
self
,
column
,
name
,
options
=
None
,
data_type
=
None
):
"""
...
...
flask_admin/contrib/peeweemodel/view.py
View file @
461db9a0
...
...
@@ -20,7 +20,8 @@ class ModelView(BaseModelView):
"""
Collection of the column filters.
Can contain either field names or instances of :class:`flask.ext.admin.contrib.sqlamodel.filters.BaseFilter` classes.
Can contain either field names or instances of
:class:`flask.ext.admin.contrib.peeweemodel.filters.BaseFilter` classes.
For example::
...
...
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