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
1aec2a18
Commit
1aec2a18
authored
Aug 14, 2013
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed #258. Recursive subdocument configuration
parent
5d442bc2
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
295 additions
and
20 deletions
+295
-20
fields.py
flask_admin/contrib/mongoengine/fields.py
+4
-1
form.py
flask_admin/contrib/mongoengine/form.py
+41
-6
view.py
flask_admin/contrib/mongoengine/view.py
+86
-0
form.py
flask_admin/contrib/sqla/form.py
+5
-5
form.py
flask_admin/model/form.py
+21
-8
test_basic.py
flask_admin/tests/mongoengine/test_basic.py
+138
-0
No files found.
flask_admin/contrib/mongoengine/fields.py
View file @
1aec2a18
...
@@ -10,10 +10,11 @@ class ModelFormField(fields.FormField):
...
@@ -10,10 +10,11 @@ class ModelFormField(fields.FormField):
"""
"""
Customized ModelFormField for MongoEngine EmbeddedDocuments.
Customized ModelFormField for MongoEngine EmbeddedDocuments.
"""
"""
def
__init__
(
self
,
model
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
model
,
view
,
*
args
,
**
kwargs
):
super
(
ModelFormField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
(
ModelFormField
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
model
=
model
self
.
model
=
model
self
.
view
=
view
def
populate_obj
(
self
,
obj
,
name
):
def
populate_obj
(
self
,
obj
,
name
):
candidate
=
getattr
(
obj
,
name
,
None
)
candidate
=
getattr
(
obj
,
name
,
None
)
...
@@ -23,6 +24,8 @@ class ModelFormField(fields.FormField):
...
@@ -23,6 +24,8 @@ class ModelFormField(fields.FormField):
self
.
form
.
populate_obj
(
candidate
)
self
.
form
.
populate_obj
(
candidate
)
self
.
view
.
on_model_change
(
self
.
form
,
candidate
)
class
MongoFileField
(
fields
.
FileField
):
class
MongoFileField
(
fields
.
FileField
):
widget
=
widgets
.
MongoFileInput
()
widget
=
widgets
.
MongoFileInput
()
...
...
flask_admin/contrib/mongoengine/form.py
View file @
1aec2a18
...
@@ -7,7 +7,7 @@ from wtforms import fields, validators
...
@@ -7,7 +7,7 @@ from wtforms import fields, validators
from
flask.ext.mongoengine.wtf
import
orm
,
fields
as
mongo_fields
from
flask.ext.mongoengine.wtf
import
orm
,
fields
as
mongo_fields
from
flask.ext.admin
import
form
from
flask.ext.admin
import
form
from
flask.ext.admin.model.form
import
FieldPlaceholder
from
flask.ext.admin.model.form
import
FieldPlaceholder
,
InlineFormAdmin
from
flask.ext.admin.model.fields
import
InlineFieldList
from
flask.ext.admin.model.fields
import
InlineFieldList
from
flask.ext.admin.model.widgets
import
InlineFormWidget
from
flask.ext.admin.model.widgets
import
InlineFormWidget
from
flask.ext.admin._compat
import
iteritems
from
flask.ext.admin._compat
import
iteritems
...
@@ -36,6 +36,25 @@ class CustomModelConverter(orm.ModelConverter):
...
@@ -36,6 +36,25 @@ class CustomModelConverter(orm.ModelConverter):
return
None
return
None
def
_get_subdocument_config
(
self
,
name
):
config
=
getattr
(
self
.
view
,
'form_subdocuments'
,
{})
print
'x'
,
name
,
config
p
=
config
.
get
(
name
)
if
not
p
:
return
InlineFormAdmin
()
if
isinstance
(
p
,
dict
):
return
InlineFormAdmin
(
**
p
)
elif
isinstance
(
p
,
InlineFormAdmin
):
return
p
raise
ValueError
(
'Invalid subdocument type: expecting dict or instance of InlineFormAdmin, got
%
s'
%
type
(
p
))
def
clone_converter
(
self
,
view
):
return
self
.
__class__
(
view
)
def
convert
(
self
,
model
,
field
,
field_args
):
def
convert
(
self
,
model
,
field
,
field_args
):
# Check if it is overridden field
# Check if it is overridden field
if
isinstance
(
field
,
FieldPlaceholder
):
if
isinstance
(
field
,
FieldPlaceholder
):
...
@@ -96,11 +115,15 @@ class CustomModelConverter(orm.ModelConverter):
...
@@ -96,11 +115,15 @@ class CustomModelConverter(orm.ModelConverter):
doc_type
=
field
.
field
.
document_type
doc_type
=
field
.
field
.
document_type
return
mongo_fields
.
ModelSelectMultipleField
(
model
=
doc_type
,
**
kwargs
)
return
mongo_fields
.
ModelSelectMultipleField
(
model
=
doc_type
,
**
kwargs
)
# Create converter
view
=
self
.
_get_subdocument_config
(
field
.
name
)
converter
=
self
.
clone_converter
(
view
)
if
field
.
field
.
choices
:
if
field
.
field
.
choices
:
kwargs
[
'multiple'
]
=
True
kwargs
[
'multiple'
]
=
True
return
self
.
convert
(
model
,
field
.
field
,
kwargs
)
return
converter
.
convert
(
model
,
field
.
field
,
kwargs
)
unbound_field
=
self
.
convert
(
model
,
field
.
field
,
{})
unbound_field
=
converter
.
convert
(
model
,
field
.
field
,
{})
kwargs
=
{
kwargs
=
{
'validators'
:
[],
'validators'
:
[],
'filters'
:
[],
'filters'
:
[],
...
@@ -115,9 +138,21 @@ class CustomModelConverter(orm.ModelConverter):
...
@@ -115,9 +138,21 @@ class CustomModelConverter(orm.ModelConverter):
'widget'
:
InlineFormWidget
()
'widget'
:
InlineFormWidget
()
}
}
# TODO: Configurable params?
view
=
self
.
_get_subdocument_config
(
field
.
name
)
form_class
=
get_form
(
field
.
document_type_obj
,
self
,
field_args
=
{})
return
ModelFormField
(
field
.
document_type_obj
,
form_class
,
**
kwargs
)
form_class
=
view
.
get_form
()
if
form_class
is
None
:
converter
=
self
.
clone_converter
(
view
)
form_class
=
get_form
(
field
.
document_type_obj
,
converter
,
base_class
=
view
.
form_base_class
or
form
.
BaseForm
,
only
=
view
.
form_columns
,
exclude
=
view
.
form_excluded_columns
,
field_args
=
view
.
form_args
,
extra_fields
=
view
.
form_extra_fields
)
form_class
=
view
.
postprocess_form
(
form_class
)
return
ModelFormField
(
field
.
document_type_obj
,
view
,
form_class
,
**
kwargs
)
@
orm
.
converts
(
'ReferenceField'
)
@
orm
.
converts
(
'ReferenceField'
)
def
conv_Reference
(
self
,
model
,
field
,
kwargs
):
def
conv_Reference
(
self
,
model
,
field
,
kwargs
):
...
...
flask_admin/contrib/mongoengine/view.py
View file @
1aec2a18
...
@@ -98,6 +98,92 @@ class ModelView(BaseModelView):
...
@@ -98,6 +98,92 @@ class ModelView(BaseModelView):
List of allowed search field types.
List of allowed search field types.
"""
"""
form_subdocuments
=
None
"""
Subdocument configuration options.
This field accepts dictionary, where key is field name and value is either dictionary or instance of the
`InlineFormAdmin`.
Consider following example::
class Comment(db.EmbeddedDocument):
name = db.StringField(max_length=20, required=True)
value = db.StringField(max_length=20)
class Post(db.Document):
text = db.StringField(max_length=30)
data = db.EmbeddedDocumentField(Comment)
class MyAdmin(ModelView):
form_subdocuments = {
'data': {
'form_subdocuments': {
'form_columns': ('name',)
}
}
}
In this example, `Post` model has child `Comment` subdocument. When generating form for `Comment` embedded
document, Flask-Admin will only create `name` field.
It is also possible to use class-based embedded document configuration:
class CommentEmbed(InlineFormAdmin):
form_columns = ('name',)
class MyAdmin(ModelView):
form_subdocuments = {
'data': CommentEmbed()
}
Arbitrary depth nesting is supported::
class SomeEmbed(InlineFormAdmin):
form_excluded_columns = ('test',)
class CommentEmbed(InlineFormAdmin):
form_columns = ('name',)
form_subdocuments = {
'inner': SomeEmbed()
}
class MyAdmin(ModelView):
form_subdocuments = {
'data': CommentEmbed()
}
There's also support for forms embedded into `ListField`. All you have
to do is to create nested rule with `None` as a name. Even though it
is slightly confusing, but that's how Flask-MongoEngine creates
form fields embedded into ListField::
class Comment(db.EmbeddedDocument):
name = db.StringField(max_length=20, required=True)
value = db.StringField(max_length=20)
class Post(db.Document):
text = db.StringField(max_length=30)
data = db.ListField(db.EmbeddedDocumentField(Comment))
class MyAdmin(ModelView):
form_subdocuments = {
'data': {
'form_subdocuments': {
data: {
'form_subdocuments': {
None: {
'form_columns': ('name',)
}
}
}
}
}
}
"""
def
__init__
(
self
,
model
,
name
=
None
,
def
__init__
(
self
,
model
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
category
=
None
,
endpoint
=
None
,
url
=
None
):
"""
"""
...
...
flask_admin/contrib/sqla/form.py
View file @
1aec2a18
...
@@ -4,7 +4,7 @@ from sqlalchemy import Boolean, Column
...
@@ -4,7 +4,7 @@ from sqlalchemy import Boolean, Column
from
flask.ext.admin
import
form
from
flask.ext.admin
import
form
from
flask.ext.admin.form
import
Select2Field
from
flask.ext.admin.form
import
Select2Field
from
flask.ext.admin.model.form
import
(
converts
,
ModelConverterBase
,
from
flask.ext.admin.model.form
import
(
converts
,
ModelConverterBase
,
InlineFormAdmin
,
InlineModelConverterBase
,
Inline
Model
FormAdmin
,
InlineModelConverterBase
,
FieldPlaceholder
)
FieldPlaceholder
)
from
flask.ext.admin.model.helpers
import
prettify_name
from
flask.ext.admin.model.helpers
import
prettify_name
from
flask.ext.admin._backwards
import
get_property
from
flask.ext.admin._backwards
import
get_property
...
@@ -437,7 +437,7 @@ class InlineModelConverter(InlineModelConverterBase):
...
@@ -437,7 +437,7 @@ class InlineModelConverter(InlineModelConverterBase):
Flask-Admin view object
Flask-Admin view object
:param model_converter:
:param model_converter:
Model converter class. Will be automatically instantiated with
Model converter class. Will be automatically instantiated with
appropriate `InlineFormAdmin` instance.
appropriate `Inline
Model
FormAdmin` instance.
"""
"""
super
(
InlineModelConverter
,
self
)
.
__init__
(
view
)
super
(
InlineModelConverter
,
self
)
.
__init__
(
view
)
self
.
session
=
session
self
.
session
=
session
...
@@ -449,7 +449,7 @@ class InlineModelConverter(InlineModelConverterBase):
...
@@ -449,7 +449,7 @@ class InlineModelConverter(InlineModelConverterBase):
# Special case for model instances
# Special case for model instances
if
info
is
None
:
if
info
is
None
:
if
hasattr
(
p
,
'_sa_class_manager'
):
if
hasattr
(
p
,
'_sa_class_manager'
):
return
InlineFormAdmin
(
p
)
return
Inline
Model
FormAdmin
(
p
)
else
:
else
:
model
=
getattr
(
p
,
'model'
,
None
)
model
=
getattr
(
p
,
'model'
,
None
)
...
@@ -461,9 +461,9 @@ class InlineModelConverter(InlineModelConverterBase):
...
@@ -461,9 +461,9 @@ class InlineModelConverter(InlineModelConverterBase):
if
not
attr
.
startswith
(
'_'
)
and
attr
!=
'model'
:
if
not
attr
.
startswith
(
'_'
)
and
attr
!=
'model'
:
attrs
[
attr
]
=
getattr
(
p
,
attr
)
attrs
[
attr
]
=
getattr
(
p
,
attr
)
return
InlineFormAdmin
(
model
,
**
attrs
)
return
Inline
Model
FormAdmin
(
model
,
**
attrs
)
info
=
InlineFormAdmin
(
model
,
**
attrs
)
info
=
Inline
Model
FormAdmin
(
model
,
**
attrs
)
return
info
return
info
...
...
flask_admin/model/form.py
View file @
1aec2a18
...
@@ -21,19 +21,15 @@ class InlineFormAdmin(object):
...
@@ -21,19 +21,15 @@ class InlineFormAdmin(object):
class MyUserInfoForm(InlineFormAdmin):
class MyUserInfoForm(InlineFormAdmin):
form_columns = ('name', 'email')
form_columns = ('name', 'email')
"""
"""
_defaults
=
[
'form_
columns'
,
'form_excluded_columns'
,
'form_arg
s'
]
_defaults
=
[
'form_
base_class'
,
'form_columns'
,
'form_excluded_columns'
,
'form_args'
,
'form_extra_field
s'
]
def
__init__
(
self
,
model
,
**
kwargs
):
def
__init__
(
self
,
**
kwargs
):
"""
"""
Constructor
Constructor
:param model:
Target model class
:param kwargs:
:param kwargs:
Additional options
Additional options
"""
"""
self
.
model
=
model
for
k
in
self
.
_defaults
:
for
k
in
self
.
_defaults
:
if
not
hasattr
(
self
,
k
):
if
not
hasattr
(
self
,
k
):
setattr
(
self
,
k
,
None
)
setattr
(
self
,
k
,
None
)
...
@@ -76,6 +72,23 @@ class InlineFormAdmin(object):
...
@@ -76,6 +72,23 @@ class InlineFormAdmin(object):
pass
pass
class
InlineModelFormAdmin
(
InlineFormAdmin
):
"""
Settings for inline form administration. Used by relational backends (SQLAlchemy, Peewee), where model
class can not be inherited from the parent model definition.
"""
def
__init__
(
self
,
model
,
**
kwargs
):
"""
Constructor
:param model:
Model class
"""
self
.
model
=
model
super
(
InlineModelFormAdmin
,
self
)
.
__init__
(
**
kwargs
)
class
ModelConverterBase
(
object
):
class
ModelConverterBase
(
object
):
def
__init__
(
self
,
converters
=
None
,
use_mro
=
True
):
def
__init__
(
self
,
converters
=
None
,
use_mro
=
True
):
self
.
use_mro
=
use_mro
self
.
use_mro
=
use_mro
...
@@ -160,8 +173,8 @@ class InlineModelConverterBase(object):
...
@@ -160,8 +173,8 @@ class InlineModelConverterBase(object):
- Model class
- Model class
"""
"""
if
isinstance
(
p
,
tuple
):
if
isinstance
(
p
,
tuple
):
return
InlineFormAdmin
(
p
[
0
],
**
p
[
1
])
return
Inline
Model
FormAdmin
(
p
[
0
],
**
p
[
1
])
elif
isinstance
(
p
,
InlineFormAdmin
):
elif
isinstance
(
p
,
Inline
Model
FormAdmin
):
return
p
return
p
return
None
return
None
...
...
flask_admin/tests/mongoengine/test_basic.py
View file @
1aec2a18
...
@@ -212,3 +212,141 @@ def test_custom_form_base():
...
@@ -212,3 +212,141 @@ def test_custom_form_base():
create_form
=
view
.
create_form
()
create_form
=
view
.
create_form
()
ok_
(
isinstance
(
create_form
,
TestForm
))
ok_
(
isinstance
(
create_form
,
TestForm
))
def
test_subdocument_config
():
app
,
db
,
admin
=
setup
()
class
Comment
(
db
.
EmbeddedDocument
):
name
=
db
.
StringField
(
max_length
=
20
,
required
=
True
)
value
=
db
.
StringField
(
max_length
=
20
)
class
Model1
(
db
.
Document
):
test1
=
db
.
StringField
(
max_length
=
20
)
subdoc
=
db
.
EmbeddedDocumentField
(
Comment
)
# Check only
view1
=
CustomModelView
(
Model1
,
form_subdocuments
=
{
'subdoc'
:
{
'form_columns'
:
(
'name'
,)
}
}
)
ok_
(
hasattr
(
view1
.
_create_form_class
,
'subdoc'
))
form
=
view1
.
create_form
()
ok_
(
'name'
in
dir
(
form
.
subdoc
.
form
))
ok_
(
'value'
not
in
dir
(
form
.
subdoc
.
form
))
# Check exclude
view2
=
CustomModelView
(
Model1
,
form_subdocuments
=
{
'subdoc'
:
{
'form_excluded_columns'
:
(
'value'
,)
}
}
)
form
=
view2
.
create_form
()
ok_
(
'name'
in
dir
(
form
.
subdoc
.
form
))
ok_
(
'value'
not
in
dir
(
form
.
subdoc
.
form
))
def
test_subdocument_class_config
():
app
,
db
,
admin
=
setup
()
from
flask.ext.admin.model.form
import
InlineFormAdmin
class
Comment
(
db
.
EmbeddedDocument
):
name
=
db
.
StringField
(
max_length
=
20
,
required
=
True
)
value
=
db
.
StringField
(
max_length
=
20
)
class
Model1
(
db
.
Document
):
test1
=
db
.
StringField
(
max_length
=
20
)
subdoc
=
db
.
EmbeddedDocumentField
(
Comment
)
class
EmbeddedConfig
(
InlineFormAdmin
):
form_columns
=
(
'name'
,)
# Check only
view1
=
CustomModelView
(
Model1
,
form_subdocuments
=
{
'subdoc'
:
EmbeddedConfig
()
}
)
form
=
view1
.
create_form
()
ok_
(
'name'
in
dir
(
form
.
subdoc
.
form
))
ok_
(
'value'
not
in
dir
(
form
.
subdoc
.
form
))
def
test_nested_subdocument_config
():
app
,
db
,
admin
=
setup
()
# Check recursive
class
Comment
(
db
.
EmbeddedDocument
):
name
=
db
.
StringField
(
max_length
=
20
,
required
=
True
)
value
=
db
.
StringField
(
max_length
=
20
)
class
Nested
(
db
.
EmbeddedDocument
):
name
=
db
.
StringField
(
max_length
=
20
,
required
=
True
)
comment
=
db
.
EmbeddedDocumentField
(
Comment
)
class
Model1
(
db
.
Document
):
test1
=
db
.
StringField
(
max_length
=
20
)
nested
=
db
.
EmbeddedDocumentField
(
Nested
)
view1
=
CustomModelView
(
Model1
,
form_subdocuments
=
{
'nested'
:
{
'form_subdocuments'
:
{
'comment'
:
{
'form_columns'
:
(
'name'
,)
}
}
}
}
)
form
=
view1
.
create_form
()
ok_
(
'name'
in
dir
(
form
.
nested
.
form
.
comment
.
form
))
ok_
(
'value'
not
in
dir
(
form
.
nested
.
form
.
comment
.
form
))
def
test_nested_list_subdocument
():
app
,
db
,
admin
=
setup
()
class
Comment
(
db
.
EmbeddedDocument
):
name
=
db
.
StringField
(
max_length
=
20
,
required
=
True
)
value
=
db
.
StringField
(
max_length
=
20
)
class
Model1
(
db
.
Document
):
test1
=
db
.
StringField
(
max_length
=
20
)
subdoc
=
db
.
ListField
(
db
.
EmbeddedDocumentField
(
Comment
))
# Check only
view1
=
CustomModelView
(
Model1
,
form_subdocuments
=
{
'subdoc'
:
{
'form_subdocuments'
:
{
None
:
{
'form_columns'
:
(
'name'
,)
}
}
}
}
)
form
=
view1
.
create_form
()
inline_form
=
form
.
subdoc
.
unbound_field
.
args
[
2
]
ok_
(
'name'
in
dir
(
inline_form
))
ok_
(
'value'
not
in
dir
(
inline_form
))
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