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
b310562b
Commit
b310562b
authored
Mar 19, 2013
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #189 from ArtemSerga/_choices
Add full support for SQLAlchemy.Enum field
parents
2bdb4fba
3aeb0cea
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
163 additions
and
35 deletions
+163
-35
filters.py
flask_admin/contrib/sqlamodel/filters.py
+30
-14
form.py
flask_admin/contrib/sqlamodel/form.py
+11
-0
view.py
flask_admin/contrib/sqlamodel/view.py
+33
-13
form.py
flask_admin/form.py
+39
-0
base.py
flask_admin/model/base.py
+34
-4
test_basic.py
flask_admin/tests/sqlamodel/test_basic.py
+16
-4
No files found.
flask_admin/contrib/sqlamodel/filters.py
View file @
b310562b
from
flask.ext.admin.babel
import
gettext
import
warnings
from
flask.ext.admin.babel
import
gettext
from
flask.ext.admin.model
import
filters
from
flask.ext.admin.model
import
filters
from
flask.ext.admin.contrib.sqlamodel
import
tools
from
flask.ext.admin.contrib.sqlamodel
import
tools
...
@@ -90,30 +91,45 @@ class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter):
...
@@ -90,30 +91,45 @@ class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter):
class
FilterConverter
(
filters
.
BaseFilterConverter
):
class
FilterConverter
(
filters
.
BaseFilterConverter
):
strings
=
(
FilterEqual
,
FilterNotEqual
,
FilterLike
,
FilterNotLike
)
strings
=
(
FilterEqual
,
FilterNotEqual
,
FilterLike
,
FilterNotLike
)
numeric
=
(
FilterEqual
,
FilterNotEqual
,
FilterGreater
,
FilterSmaller
)
numeric
=
(
FilterEqual
,
FilterNotEqual
,
FilterGreater
,
FilterSmaller
)
bool
=
(
BooleanEqualFilter
,
BooleanNotEqualFilter
)
enum
=
(
FilterEqual
,
FilterNotEqual
)
def
convert
(
self
,
type_name
,
column
,
name
):
def
convert
(
self
,
type_name
,
column
,
name
,
**
kwargs
):
if
type_name
in
self
.
converters
:
if
type_name
in
self
.
converters
:
return
self
.
converters
[
type_name
](
column
,
name
)
return
self
.
converters
[
type_name
](
column
,
name
,
**
kwargs
)
return
None
return
None
@
filters
.
convert
(
'String'
,
'Unicode'
,
'Text'
,
'UnicodeText'
)
@
filters
.
convert
(
'String'
,
'Unicode'
,
'Text'
,
'UnicodeText'
)
def
conv_string
(
self
,
column
,
name
):
def
conv_string
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
)
for
f
in
self
.
strings
]
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
strings
]
@
filters
.
convert
(
'Boolean'
)
@
filters
.
convert
(
'Boolean'
)
def
conv_bool
(
self
,
column
,
name
):
def
conv_bool
(
self
,
column
,
name
,
**
kwargs
):
return
[
BooleanEqualFilter
(
column
,
name
),
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
bool
]
BooleanNotEqualFilter
(
column
,
name
)]
@
filters
.
convert
(
'Integer'
,
'SmallInteger'
,
'Numeric'
,
'Float'
)
@
filters
.
convert
(
'Integer'
,
'SmallInteger'
,
'Numeric'
,
'Float'
)
def
conv_int
(
self
,
column
,
name
):
def
conv_int
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
)
for
f
in
self
.
numeric
]
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'Date'
)
@
filters
.
convert
(
'Date'
)
def
conv_date
(
self
,
column
,
name
):
def
conv_date
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
,
data_type
=
'datepicker'
)
for
f
in
self
.
numeric
]
return
[
f
(
column
,
name
,
data_type
=
'datepicker'
,
**
kwargs
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'DateTime'
)
@
filters
.
convert
(
'DateTime'
)
def
conv_datetime
(
self
,
column
,
name
):
def
conv_datetime
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
,
data_type
=
'datetimepicker'
)
for
f
in
self
.
numeric
]
return
[
f
(
column
,
name
,
data_type
=
'datetimepicker'
,
**
kwargs
)
for
f
in
self
.
numeric
]
@
filters
.
convert
(
'Enum'
,
'ENUM'
)
def
conv_enum
(
self
,
column
,
name
,
options
=
None
,
**
kwargs
):
if
not
options
:
warnings
.
warn
(
'You can make SQ field with `Enum` type '
'more human readable in the form by using '
'`column_choices` in your `ModelView`'
)
options
=
[
(
v
,
v
)
for
v
in
column
.
type
.
enums
]
return
[
f
(
column
,
name
,
options
,
**
kwargs
)
for
f
in
self
.
enum
]
flask_admin/contrib/sqlamodel/form.py
View file @
b310562b
...
@@ -2,6 +2,7 @@ from wtforms import fields, validators
...
@@ -2,6 +2,7 @@ from wtforms import fields, validators
from
sqlalchemy
import
Boolean
,
Column
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.tools
import
get_property
from
flask.ext.admin.tools
import
get_property
from
flask.ext.admin.model.form
import
(
converts
,
ModelConverterBase
,
from
flask.ext.admin.model.form
import
(
converts
,
ModelConverterBase
,
InlineFormAdmin
,
InlineModelConverterBase
)
InlineFormAdmin
,
InlineModelConverterBase
)
...
@@ -186,6 +187,16 @@ class AdminModelConverter(ModelConverterBase):
...
@@ -186,6 +187,16 @@ class AdminModelConverter(ModelConverterBase):
if
override
:
if
override
:
return
override
(
**
kwargs
)
return
override
(
**
kwargs
)
# Check choices
if
mapper
.
class_
==
self
.
view
.
model
and
self
.
view
.
form_choices
:
choices
=
self
.
view
.
form_choices
.
get
(
column
.
key
)
if
choices
:
return
Select2Field
(
choices
=
choices
,
allow_blank
=
column
.
nullable
,
**
kwargs
)
# Run converter
# Run converter
converter
=
self
.
get_converter
(
column
)
converter
=
self
.
get_converter
(
column
)
...
...
flask_admin/contrib/sqlamodel/view.py
View file @
b310562b
...
@@ -218,6 +218,18 @@ class ModelView(BaseModelView):
...
@@ -218,6 +218,18 @@ class ModelView(BaseModelView):
column_type_formatters
=
DEFAULT_FORMATTERS
column_type_formatters
=
DEFAULT_FORMATTERS
form_choices
=
None
"""
Map choices to form fields
Example::
class MyModelView(BaseModelView):
form_choices = {'my_form_field': [
('db_value', 'display_value'),
]
"""
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
):
"""
"""
...
@@ -243,6 +255,9 @@ class ModelView(BaseModelView):
...
@@ -243,6 +255,9 @@ class ModelView(BaseModelView):
self
.
_filter_joins
=
dict
()
self
.
_filter_joins
=
dict
()
if
self
.
form_choices
is
None
:
self
.
form_choices
=
{}
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
super
(
ModelView
,
self
)
.
__init__
(
model
,
name
,
category
,
endpoint
,
url
)
# Primary key
# Primary key
...
@@ -453,9 +468,11 @@ class ModelView(BaseModelView):
...
@@ -453,9 +468,11 @@ class ModelView(BaseModelView):
column
=
columns
[
0
]
column
=
columns
[
0
]
if
self
.
_need_join
(
column
.
table
):
if
self
.
_need_join
(
column
.
table
)
and
name
not
in
self
.
column_labels
:
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
column
.
table
.
name
),
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
column
.
name
))
self
.
get_column_name
(
column
.
table
.
name
),
self
.
get_column_name
(
column
.
name
)
)
else
:
else
:
if
not
isinstance
(
name
,
basestring
):
if
not
isinstance
(
name
,
basestring
):
visible_name
=
self
.
get_column_name
(
name
.
property
.
key
)
visible_name
=
self
.
get_column_name
(
name
.
property
.
key
)
...
@@ -463,16 +480,19 @@ class ModelView(BaseModelView):
...
@@ -463,16 +480,19 @@ class ModelView(BaseModelView):
visible_name
=
self
.
get_column_name
(
name
)
visible_name
=
self
.
get_column_name
(
name
)
type_name
=
type
(
column
.
type
)
.
__name__
type_name
=
type
(
column
.
type
)
.
__name__
flt
=
self
.
filter_converter
.
convert
(
type_name
,
column
,
if
join_tables
:
visible_name
)
self
.
_filter_joins
[
column
.
table
.
name
]
=
join_tables
if
flt
:
flt
=
self
.
filter_converter
.
convert
(
# If there's relation to other table, do it
type_name
,
if
join_tables
:
column
,
self
.
_filter_joins
[
column
.
table
.
name
]
=
join_tables
visible_name
,
elif
self
.
_need_join
(
column
.
table
):
options
=
self
.
column_choices
.
get
(
name
),
self
.
_filter_joins
[
column
.
table
.
name
]
=
[
column
.
table
]
)
if
flt
and
not
join_tables
and
self
.
_need_join
(
column
.
table
):
self
.
_filter_joins
[
column
.
table
.
name
]
=
[
column
.
table
]
return
flt
return
flt
...
...
flask_admin/form.py
View file @
b310562b
...
@@ -110,6 +110,45 @@ class Select2Field(fields.SelectField):
...
@@ -110,6 +110,45 @@ class Select2Field(fields.SelectField):
"""
"""
widget
=
Select2Widget
()
widget
=
Select2Widget
()
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
coerce
=
unicode
,
choices
=
None
,
allow_blank
=
False
,
blank_text
=
None
,
**
kwargs
):
super
(
Select2Field
,
self
)
.
__init__
(
label
,
validators
,
coerce
,
choices
,
**
kwargs
)
self
.
allow_blank
=
allow_blank
self
.
blank_text
=
blank_text
or
' '
def
iter_choices
(
self
):
if
self
.
allow_blank
:
yield
(
u'__None'
,
self
.
blank_text
,
self
.
data
is
None
)
for
value
,
label
in
self
.
choices
:
yield
(
value
,
label
,
self
.
coerce
(
value
)
==
self
.
data
)
def
process_data
(
self
,
value
):
if
value
is
None
:
self
.
data
=
None
else
:
try
:
self
.
data
=
self
.
coerce
(
value
)
except
(
ValueError
,
TypeError
):
self
.
data
=
None
def
process_formdata
(
self
,
valuelist
):
if
valuelist
:
if
valuelist
[
0
]
==
'__None'
:
self
.
data
=
None
else
:
try
:
self
.
data
=
self
.
coerce
(
valuelist
[
0
])
except
ValueError
:
raise
ValueError
(
self
.
gettext
(
u'Invalid Choice: could not coerce'
))
def
pre_validate
(
self
,
form
):
if
self
.
allow_blank
and
self
.
data
is
None
:
return
super
(
Select2Field
,
self
)
.
pre_validate
(
form
)
class
DatePickerWidget
(
widgets
.
TextInput
):
class
DatePickerWidget
(
widgets
.
TextInput
):
"""
"""
...
...
flask_admin/model/base.py
View file @
b310562b
...
@@ -180,6 +180,20 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -180,6 +180,20 @@ class BaseModelView(BaseView, ActionsMixin):
class MyModelView(BaseModelView):
class MyModelView(BaseModelView):
column_searchable_list = ('name', 'email')
column_searchable_list = ('name', 'email')
"""
"""
column_choices
=
None
"""
Map choices to columns in list view
Example::
class MyModelView(BaseModelView):
column_choices = {
'my_column': [
('db_value', 'display_value'),
]
}
"""
column_filters
=
None
column_filters
=
None
"""
"""
...
@@ -339,6 +353,10 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -339,6 +353,10 @@ class BaseModelView(BaseView, ActionsMixin):
self
.
_list_columns
=
self
.
get_list_columns
()
self
.
_list_columns
=
self
.
get_list_columns
()
self
.
_sortable_columns
=
self
.
get_sortable_columns
()
self
.
_sortable_columns
=
self
.
get_sortable_columns
()
# Labels
if
self
.
column_labels
is
None
:
self
.
column_labels
=
{}
# Forms
# Forms
self
.
_create_form_class
=
self
.
get_create_form
()
self
.
_create_form_class
=
self
.
get_create_form
()
self
.
_edit_form_class
=
self
.
get_edit_form
()
self
.
_edit_form_class
=
self
.
get_edit_form
()
...
@@ -349,6 +367,15 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -349,6 +367,15 @@ class BaseModelView(BaseView, ActionsMixin):
# Search
# Search
self
.
_search_supported
=
self
.
init_search
()
self
.
_search_supported
=
self
.
init_search
()
# Choices
if
self
.
column_choices
:
self
.
_column_choices_map
=
dict
([
(
column
,
dict
(
choices
))
for
column
,
choices
in
self
.
column_choices
.
items
()
])
else
:
self
.
column_choices
=
self
.
_column_choices_map
=
dict
()
# Filters
# Filters
self
.
_filters
=
self
.
get_filters
()
self
.
_filters
=
self
.
get_filters
()
...
@@ -499,15 +526,14 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -499,15 +526,14 @@ class BaseModelView(BaseView, ActionsMixin):
collection
=
[]
collection
=
[]
for
n
in
self
.
column_filters
:
for
n
in
self
.
column_filters
:
if
not
self
.
is_valid_filter
(
n
):
if
self
.
is_valid_filter
(
n
):
collection
.
append
(
n
)
else
:
flt
=
self
.
scaffold_filters
(
n
)
flt
=
self
.
scaffold_filters
(
n
)
if
flt
:
if
flt
:
collection
.
extend
(
flt
)
collection
.
extend
(
flt
)
else
:
else
:
raise
Exception
(
'Unsupported filter type
%
s'
%
n
)
raise
Exception
(
'Unsupported filter type
%
s'
%
n
)
else
:
collection
.
append
(
n
)
return
collection
return
collection
else
:
else
:
return
None
return
None
...
@@ -794,6 +820,10 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -794,6 +820,10 @@ class BaseModelView(BaseView, ActionsMixin):
value
=
rec_getattr
(
model
,
name
)
value
=
rec_getattr
(
model
,
name
)
choices_map
=
self
.
_column_choices_map
.
get
(
name
,
{})
if
choices_map
:
return
choices_map
.
get
(
value
)
or
value
type_fmt
=
self
.
column_type_formatters
.
get
(
type
(
value
))
type_fmt
=
self
.
column_type_formatters
.
get
(
type
(
value
))
if
type_fmt
is
not
None
:
if
type_fmt
is
not
None
:
value
=
type_fmt
(
value
)
value
=
type_fmt
(
value
)
...
...
flask_admin/tests/sqlamodel/test_basic.py
View file @
b310562b
...
@@ -33,6 +33,7 @@ def create_models(db):
...
@@ -33,6 +33,7 @@ def create_models(db):
test3
=
db
.
Column
(
db
.
Text
)
test3
=
db
.
Column
(
db
.
Text
)
test4
=
db
.
Column
(
db
.
UnicodeText
)
test4
=
db
.
Column
(
db
.
UnicodeText
)
bool_field
=
db
.
Column
(
db
.
Boolean
)
bool_field
=
db
.
Column
(
db
.
Boolean
)
enum_field
=
db
.
Column
(
db
.
Enum
(
'model1_v1'
,
'model1_v1'
),
nullable
=
True
)
class
Model2
(
db
.
Model
):
class
Model2
(
db
.
Model
):
def
__init__
(
self
,
string_field
=
None
,
int_field
=
None
,
bool_field
=
None
,
model1
=
None
):
def
__init__
(
self
,
string_field
=
None
,
int_field
=
None
,
bool_field
=
None
,
model1
=
None
):
...
@@ -45,6 +46,9 @@ def create_models(db):
...
@@ -45,6 +46,9 @@ def create_models(db):
string_field
=
db
.
Column
(
db
.
String
)
string_field
=
db
.
Column
(
db
.
String
)
int_field
=
db
.
Column
(
db
.
Integer
)
int_field
=
db
.
Column
(
db
.
Integer
)
bool_field
=
db
.
Column
(
db
.
Boolean
)
bool_field
=
db
.
Column
(
db
.
Boolean
)
enum_field
=
db
.
Column
(
db
.
Enum
(
'model2_v1'
,
'model2_v2'
),
nullable
=
True
)
# Relation
model1_id
=
db
.
Column
(
db
.
Integer
,
db
.
ForeignKey
(
Model1
.
id
))
model1_id
=
db
.
Column
(
db
.
Integer
,
db
.
ForeignKey
(
Model1
.
id
))
model1
=
db
.
relationship
(
Model1
)
model1
=
db
.
relationship
(
Model1
)
...
@@ -162,8 +166,10 @@ def test_exclude_columns():
...
@@ -162,8 +166,10 @@ def test_exclude_columns():
Model1
,
Model2
=
create_models
(
db
)
Model1
,
Model2
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
db
.
session
,
view
=
CustomModelView
(
column_exclude_list
=
[
'test2'
,
'test4'
])
Model1
,
db
.
session
,
column_exclude_list
=
[
'test2'
,
'test4'
,
'enum_field'
]
)
admin
.
add_view
(
view
)
admin
.
add_view
(
view
)
eq_
(
eq_
(
...
@@ -210,8 +216,10 @@ def test_column_filters():
...
@@ -210,8 +216,10 @@ def test_column_filters():
Model1
,
Model2
=
create_models
(
db
)
Model1
,
Model2
=
create_models
(
db
)
view
=
CustomModelView
(
Model1
,
db
.
session
,
view
=
CustomModelView
(
column_filters
=
[
'test1'
])
Model1
,
db
.
session
,
column_filters
=
[
'test1'
]
)
admin
.
add_view
(
view
)
admin
.
add_view
(
view
)
eq_
(
len
(
view
.
_filters
),
4
)
eq_
(
len
(
view
.
_filters
),
4
)
...
@@ -258,6 +266,10 @@ def test_column_filters():
...
@@ -258,6 +266,10 @@ def test_column_filters():
(
16
,
'equals'
),
(
16
,
'equals'
),
(
17
,
'not equal'
),
(
17
,
'not equal'
),
],
],
'Model1 / Enum Field'
:
[
(
18
,
u'equals'
),
(
19
,
u'not equal'
),
]
})
})
# Test filter with a dot
# Test filter with a dot
...
...
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