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
a214377c
Commit
a214377c
authored
Jul 20, 2013
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added form_extra_columns. Fixed #256
parent
0b7107fb
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
321 additions
and
28 deletions
+321
-28
form.py
flask_admin/contrib/mongoengine/form.py
+81
-6
view.py
flask_admin/contrib/mongoengine/view.py
+11
-8
form.py
flask_admin/contrib/peewee/form.py
+26
-1
view.py
flask_admin/contrib/peewee/view.py
+7
-8
form.py
flask_admin/contrib/sqla/form.py
+18
-2
view.py
flask_admin/contrib/sqla/view.py
+2
-1
form.py
flask_admin/form.py
+14
-0
base.py
flask_admin/model/base.py
+25
-2
form.py
flask_admin/model/form.py
+8
-0
test_basic.py
flask_admin/tests/mongoengine/test_basic.py
+52
-0
test_basic.py
flask_admin/tests/peeweemodel/test_basic.py
+26
-0
test_basic.py
flask_admin/tests/sqlamodel/test_basic.py
+51
-0
No files found.
flask_admin/contrib/mongoengine/form.py
View file @
a214377c
from
operator
import
itemgetter
from
mongoengine
import
ReferenceField
from
mongoengine.base
import
BaseDocument
,
DocumentMetaclass
from
wtforms
import
fields
,
validators
from
flask.ext.mongoengine.wtf
import
orm
,
fields
as
mongo_fields
from
flask.ext.admin
import
form
from
flask.ext.admin.model.form
import
FieldPlaceholder
from
flask.ext.admin.model.fields
import
InlineFieldList
from
flask.ext.admin.model.widgets
import
InlineFormWidget
from
flask.ext.admin._compat
import
iteritems
from
.fields
import
ModelFormField
...
...
@@ -32,6 +37,10 @@ class CustomModelConverter(orm.ModelConverter):
return
None
def
convert
(
self
,
model
,
field
,
field_args
):
# Check if it is overridden field
if
isinstance
(
field
,
FieldPlaceholder
):
return
form
.
recreate_field
(
field
.
field
)
kwargs
=
{
'label'
:
getattr
(
field
,
'verbose_name'
,
field
.
name
),
'description'
:
field
.
help_text
or
''
,
...
...
@@ -86,6 +95,7 @@ class CustomModelConverter(orm.ModelConverter):
doc_type
=
field
.
field
.
document_type
return
mongo_fields
.
ModelSelectMultipleField
(
model
=
doc_type
,
**
kwargs
)
if
field
.
field
.
choices
:
kwargs
[
'multiple'
]
=
True
return
self
.
convert
(
model
,
field
.
field
,
kwargs
)
...
...
@@ -105,7 +115,8 @@ class CustomModelConverter(orm.ModelConverter):
'widget'
:
InlineFormWidget
()
}
form_class
=
model_form
(
field
.
document_type_obj
,
field_args
=
{})
# TODO: Configurable params?
form_class
=
get_form
(
field
.
document_type_obj
,
self
,
field_args
=
{})
return
ModelFormField
(
field
.
document_type_obj
,
form_class
,
**
kwargs
)
@
orm
.
converts
(
'ReferenceField'
)
...
...
@@ -114,8 +125,72 @@ class CustomModelConverter(orm.ModelConverter):
return
orm
.
ModelConverter
.
conv_Reference
(
self
,
model
,
field
,
kwargs
)
def
model_form
(
model
,
base_class
=
form
.
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
)
def
get_form
(
model
,
converter
,
base_class
=
form
.
BaseForm
,
only
=
None
,
exclude
=
None
,
field_args
=
None
,
extra_fields
=
None
):
"""
Create a wtforms Form for a given mongoengine Document schema::
from flask.ext.mongoengine.wtf import model_form
from myproject.myapp.schemas import Article
ArticleForm = model_form(Article)
:param model:
A mongoengine Document schema class
:param base_class:
Base form class to extend from. Must be a ``wtforms.Form`` subclass.
:param only:
An optional iterable with the property names that should be included in
the form. Only these properties will have fields.
:param exclude:
An optional iterable with the property names that should be excluded
from the form. All other properties will have fields.
:param field_args:
An optional dictionary of field names mapping to keyword arguments used
to construct each field object.
:param converter:
A converter to generate the fields based on the model properties. If
not set, ``ModelConverter`` is used.
"""
if
not
isinstance
(
model
,
(
BaseDocument
,
DocumentMetaclass
)):
raise
TypeError
(
'Model must be a mongoengine Document schema'
)
field_args
=
field_args
or
{}
# Find properties
properties
=
((
k
,
v
)
for
k
,
v
in
iteritems
(
model
.
_fields
))
if
only
:
props
=
dict
(
properties
)
def
find
(
name
):
if
extra_fields
and
name
in
extra_fields
:
return
FieldPlaceholder
(
extra_fields
[
name
])
p
=
props
.
get
(
name
)
if
p
is
not
None
:
return
p
raise
ValueError
(
'Invalid model property name
%
s.
%
s'
%
(
model
,
name
))
properties
=
((
p
,
find
(
p
))
for
p
in
only
)
elif
exclude
:
properties
=
(
p
for
p
in
properties
in
p
[
0
]
not
in
exclude
)
# Create fields
field_dict
=
{}
for
name
,
p
in
properties
:
field
=
converter
.
convert
(
model
,
p
,
field_args
.
get
(
name
))
if
field
is
not
None
:
field_dict
[
name
]
=
field
# Contribute extra fields
if
not
only
and
extra_fields
:
for
name
,
field
in
iteritems
(
extra_fields
):
field_dict
[
name
]
=
form
.
recreate_field
(
field
)
field_dict
[
'model_class'
]
=
model
return
type
(
model
.
__name__
+
'Form'
,
(
base_class
,),
field_dict
)
flask_admin/contrib/mongoengine/view.py
View file @
a214377c
...
...
@@ -12,7 +12,7 @@ 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
from
.form
import
get
_form
,
CustomModelConverter
from
.typefmt
import
DEFAULT_FORMATTERS
from
.tools
import
parse_like_term
...
...
@@ -234,13 +234,16 @@ class ModelView(BaseModelView):
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
.
form_excluded_columns
,
field_args
=
self
.
form_args
,
converter
=
self
.
model_form_converter
(
self
))
"""
Create form from the model.
"""
form_class
=
get_form
(
self
.
model
,
self
.
model_form_converter
(
self
),
base_class
=
BaseForm
,
only
=
self
.
form_columns
,
exclude
=
self
.
form_excluded_columns
,
field_args
=
self
.
form_args
,
extra_fields
=
self
.
form_extra_fields
)
return
form_class
...
...
flask_admin/contrib/peewee/form.py
View file @
a214377c
...
...
@@ -6,7 +6,7 @@ from peewee import (DateTimeField, DateField, TimeField,
from
wtfpeewee.orm
import
ModelConverter
,
model_form
from
flask.ext.admin
import
form
from
flask.ext.admin._compat
import
itervalues
from
flask.ext.admin._compat
import
iter
items
,
iter
values
from
flask.ext.admin.model.form
import
InlineFormAdmin
,
InlineModelConverterBase
from
flask.ext.admin.model.fields
import
InlineModelFormField
,
InlineFieldList
...
...
@@ -103,6 +103,31 @@ class CustomModelConverter(ModelConverter):
return
field
.
name
,
form
.
TimeField
(
**
kwargs
)
def
get_form
(
model
,
converter
,
base_class
=
form
.
BaseForm
,
only
=
None
,
exclude
=
None
,
field_args
=
None
,
allow_pk
=
True
,
extra_fields
=
None
):
"""
Create form from peewee model and contribute extra fields, if necessary
"""
result
=
model_form
(
model
,
base_class
=
base_class
,
only
=
only
,
exclude
=
exclude
,
field_args
=
field_args
,
allow_pk
=
allow_pk
,
converter
=
converter
)
if
extra_fields
:
for
name
,
field
in
iteritems
(
extra_fields
):
setattr
(
result
,
name
,
form
.
recreate_field
(
field
))
return
result
class
InlineModelConverter
(
InlineModelConverterBase
):
"""
Inline model form helper.
...
...
flask_admin/contrib/peewee/view.py
View file @
a214377c
...
...
@@ -8,11 +8,10 @@ from flask.ext.admin.babel import gettext, ngettext, lazy_gettext
from
flask.ext.admin.model
import
BaseModelView
from
peewee
import
PrimaryKeyField
,
ForeignKeyField
,
Field
,
CharField
,
TextField
from
wtfpeewee.orm
import
model_form
from
flask.ext.admin.actions
import
action
from
flask.ext.admin.contrib.peewee
import
filters
from
.form
import
CustomModelConverter
,
InlineModelConverter
,
save_inline
from
.form
import
get_form
,
CustomModelConverter
,
InlineModelConverter
,
save_inline
from
.tools
import
get_primary_key
,
parse_like_term
...
...
@@ -218,12 +217,12 @@ class ModelView(BaseModelView):
return
isinstance
(
filter
,
filters
.
BasePeeweeFilter
)
def
scaffold_form
(
self
):
form_class
=
model_form
(
self
.
model
,
base_class
=
form
.
BaseForm
,
only
=
self
.
form_columns
,
exclude
=
self
.
form_excluded_columns
,
field_args
=
self
.
form_args
,
converter
=
self
.
model_form_converter
()
)
form_class
=
get_form
(
self
.
model
,
self
.
model_form_converter
()
,
base_class
=
form
.
BaseForm
,
only
=
self
.
form_columns
,
exclude
=
self
.
form_excluded_columns
,
field_args
=
self
.
form_args
,
extra_fields
=
self
.
form_extra_fields
)
if
self
.
inline_models
:
form_class
=
self
.
scaffold_inline_form_models
(
form_class
)
...
...
flask_admin/contrib/sqla/form.py
View file @
a214377c
...
...
@@ -4,8 +4,10 @@ from sqlalchemy import Boolean, Column
from
flask.ext.admin
import
form
from
flask.ext.admin.form
import
Select2Field
from
flask.ext.admin.model.form
import
(
converts
,
ModelConverterBase
,
InlineFormAdmin
,
InlineModelConverterBase
)
InlineFormAdmin
,
InlineModelConverterBase
,
FieldPlaceholder
)
from
flask.ext.admin._backwards
import
get_property
from
flask.ext.admin._compat
import
iteritems
from
.validators
import
Unique
from
.fields
import
QuerySelectField
,
QuerySelectMultipleField
,
InlineModelFormList
...
...
@@ -60,6 +62,10 @@ class AdminModelConverter(ModelConverterBase):
return
None
def
convert
(
self
,
model
,
mapper
,
prop
,
field_args
,
hidden_pk
):
# Properly handle forced fields
if
isinstance
(
prop
,
FieldPlaceholder
):
return
form
.
recreate_field
(
prop
.
field
)
kwargs
=
{
'validators'
:
[],
'filters'
:
[]
...
...
@@ -309,7 +315,8 @@ def get_form(model, converter,
exclude
=
None
,
field_args
=
None
,
hidden_pk
=
False
,
ignore_hidden
=
True
):
ignore_hidden
=
True
,
extra_fields
=
None
):
"""
Generate form from the model.
...
...
@@ -344,6 +351,10 @@ def get_form(model, converter,
props
=
dict
(
properties
)
def
find
(
name
):
# If field is in extra_fields, it has higher priority
if
extra_fields
and
name
in
extra_fields
:
return
FieldPlaceholder
(
extra_fields
[
name
])
# Try to look it up in properties list first
p
=
props
.
get
(
name
)
...
...
@@ -374,6 +385,11 @@ def get_form(model, converter,
if
field
is
not
None
:
field_dict
[
name
]
=
field
# Contribute extra fields
if
not
only
and
extra_fields
:
for
name
,
field
in
iteritems
(
extra_fields
):
field_dict
[
name
]
=
form
.
recreate_field
(
field
)
return
type
(
model
.
__name__
+
'Form'
,
(
base_class
,
),
field_dict
)
...
...
flask_admin/contrib/sqla/view.py
View file @
a214377c
...
...
@@ -515,7 +515,8 @@ class ModelView(BaseModelView):
form_class
=
form
.
get_form
(
self
.
model
,
converter
,
only
=
self
.
form_columns
,
exclude
=
self
.
form_excluded_columns
,
field_args
=
self
.
form_args
)
field_args
=
self
.
form_args
,
extra_fields
=
self
.
form_extra_fields
)
if
self
.
inline_models
:
form_class
=
self
.
scaffold_inline_form_models
(
form_class
)
...
...
flask_admin/form.py
View file @
a214377c
...
...
@@ -2,6 +2,7 @@ import time
import
datetime
from
wtforms
import
form
,
fields
,
widgets
from
wtforms.fields.core
import
UnboundField
from
flask.globals
import
_request_ctx_stack
from
flask.ext.admin.babel
import
gettext
,
ngettext
from
flask.ext.admin
import
helpers
as
h
...
...
@@ -214,3 +215,16 @@ class Select2TagsField(fields.TextField):
def
_value
(
self
):
return
u', '
.
join
(
self
.
data
)
if
isinstance
(
self
.
data
,
list
)
else
self
.
data
def
recreate_field
(
unbound
):
"""
Create new instance of the unbound field, resetting wtforms creation counter.
:param unbound:
UnboundField instance
"""
if
not
isinstance
(
unbound
,
UnboundField
):
raise
ValueError
(
'recreate_field expects UnboundField instance,
%
s was passed.'
%
type
(
unbound
))
return
unbound
.
field_class
(
*
unbound
.
args
,
**
unbound
.
kwargs
)
flask_admin/model/base.py
View file @
a214377c
...
...
@@ -12,7 +12,7 @@ from flask.ext.admin.actions import ActionsMixin
from
flask.ext.admin.helpers
import
get_form_data
,
validate_form_on_submit
from
flask.ext.admin.tools
import
rec_getattr
from
flask.ext.admin._backwards
import
ObsoleteAttr
from
flask.ext.admin._compat
import
as_unicode
from
flask.ext.admin._compat
import
iteritems
,
as_unicode
class
BaseModelView
(
BaseView
,
ActionsMixin
):
...
...
@@ -318,6 +318,29 @@ class BaseModelView(BaseView, ActionsMixin):
}
"""
form_extra_fields
=
None
"""
Dictionary of additional fields.
Example::
class MyModelView(BaseModelView):
form_extra_fields = {
password: PasswordField('Password')
}
You can control order of form fields using ``form_columns`` property. For example::
class MyModelView(BaseModelView):
form_columns = ('name', 'email', 'password', 'secret')
form_extra_fields = {
password: PasswordField('Password')
}
In this case, password field will be put between email and secret fields that are autogenerated.
"""
# Actions
action_disallowed_list
=
ObsoleteAttr
(
'action_disallowed_list'
,
'disallowed_actions'
,
...
...
@@ -876,7 +899,7 @@ class BaseModelView(BaseView, ActionsMixin):
"""
Return flattened filter dictionary which can be JSON-serialized.
"""
return
dict
((
as_unicode
(
k
),
v
)
for
k
,
v
in
self
.
_filter_dict
.
iteritems
(
))
return
dict
((
as_unicode
(
k
),
v
)
for
k
,
v
in
iteritems
(
self
.
_filter_dict
))
@
contextfunction
def
get_list_value
(
self
,
context
,
model
,
name
):
...
...
flask_admin/model/form.py
View file @
a214377c
...
...
@@ -165,3 +165,11 @@ class InlineModelConverterBase(object):
return
p
return
None
class
FieldPlaceholder
(
object
):
"""
Field placeholder for model convertors.
"""
def
__init__
(
self
,
field
):
self
.
field
=
field
flask_admin/tests/mongoengine/test_basic.py
View file @
a214377c
...
...
@@ -139,3 +139,55 @@ def test_default_sort():
eq_
(
data
[
0
]
.
test1
,
'a'
)
eq_
(
data
[
1
]
.
test1
,
'b'
)
eq_
(
data
[
2
]
.
test1
,
'c'
)
def
test_extra_fields
():
app
,
db
,
admin
=
setup
()
Model1
,
_
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
form_extra_fields
=
{
'extra_field'
:
fields
.
TextField
(
'Extra Field'
)
}
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model1view/new/'
)
eq_
(
rv
.
status_code
,
200
)
# Check presence and order
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'Extra Field'
in
data
)
pos1
=
data
.
find
(
'Extra Field'
)
pos2
=
data
.
find
(
'Test1'
)
ok_
(
pos2
<
pos1
)
def
test_extra_field_order
():
app
,
db
,
admin
=
setup
()
Model1
,
_
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
form_columns
=
(
'extra_field'
,
'test1'
),
form_extra_fields
=
{
'extra_field'
:
fields
.
TextField
(
'Extra Field'
)
}
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model1view/new/'
)
eq_
(
rv
.
status_code
,
200
)
# Check presence and order
data
=
rv
.
data
.
decode
(
'utf-8'
)
pos1
=
data
.
find
(
'Extra Field'
)
pos2
=
data
.
find
(
'Test1'
)
ok_
(
pos2
>
pos1
)
flask_admin/tests/peeweemodel/test_basic.py
View file @
a214377c
...
...
@@ -147,3 +147,29 @@ def test_default_sort():
eq_
(
data
[
0
]
.
test1
,
'a'
)
eq_
(
data
[
1
]
.
test1
,
'b'
)
eq_
(
data
[
2
]
.
test1
,
'c'
)
def
test_extra_fields
():
app
,
db
,
admin
=
setup
()
Model1
,
_
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
form_extra_fields
=
{
'extra_field'
:
fields
.
TextField
(
'Extra Field'
)
}
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model1view/new/'
)
eq_
(
rv
.
status_code
,
200
)
# Check presence and order
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'Extra Field'
in
data
)
pos1
=
data
.
find
(
'Extra Field'
)
pos2
=
data
.
find
(
'Test1'
)
ok_
(
pos2
<
pos1
)
flask_admin/tests/sqlamodel/test_basic.py
View file @
a214377c
...
...
@@ -567,4 +567,55 @@ def test_default_sort():
eq_
(
data
[
2
]
.
test1
,
'c'
)
def
test_extra_fields
():
app
,
db
,
admin
=
setup
()
Model1
,
_
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
db
.
session
,
form_extra_fields
=
{
'extra_field'
:
fields
.
TextField
(
'Extra Field'
)
}
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model1view/new/'
)
eq_
(
rv
.
status_code
,
200
)
# Check presence and order
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'Extra Field'
in
data
)
pos1
=
data
.
find
(
'Extra Field'
)
pos2
=
data
.
find
(
'Test1'
)
ok_
(
pos2
<
pos1
)
def
test_extra_field_order
():
app
,
db
,
admin
=
setup
()
Model1
,
_
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
db
.
session
,
form_columns
=
(
'extra_field'
,
'test1'
),
form_extra_fields
=
{
'extra_field'
:
fields
.
TextField
(
'Extra Field'
)
}
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model1view/new/'
)
eq_
(
rv
.
status_code
,
200
)
# Check presence and order
data
=
rv
.
data
.
decode
(
'utf-8'
)
pos1
=
data
.
find
(
'Extra Field'
)
pos2
=
data
.
find
(
'Test1'
)
ok_
(
pos2
>
pos1
)
# TODO: Babel tests
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