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
12e7b17a
Commit
12e7b17a
authored
Jun 24, 2019
by
P.J. Janse van Rensburg
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into sqlalchemy-utils-types
parents
6cc02583
d29796b6
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
126 additions
and
42 deletions
+126
-42
changelog.rst
doc/changelog.rst
+2
-2
conf.py
doc/conf.py
+8
-8
app.py
examples/auth/app.py
+4
-8
requirements.txt
examples/mongoengine/requirements.txt
+1
-0
fields.py
flask_admin/contrib/geoa/fields.py
+5
-5
typefmt.py
flask_admin/contrib/geoa/typefmt.py
+3
-1
form.py
flask_admin/contrib/mongoengine/form.py
+5
-1
view.py
flask_admin/contrib/mongoengine/view.py
+2
-2
view.py
flask_admin/contrib/peewee/view.py
+2
-2
filters.py
flask_admin/contrib/sqla/filters.py
+26
-2
view.py
flask_admin/contrib/sqla/view.py
+9
-1
validators.py
flask_admin/form/validators.py
+16
-0
helpers.py
flask_admin/helpers.py
+4
-2
filters.py
flask_admin/model/filters.py
+24
-0
widgets.py
flask_admin/model/widgets.py
+1
-0
form.js
flask_admin/static/admin/js/form.js
+14
-8
No files found.
doc/changelog.rst
View file @
12e7b17a
Changelog
Changelog
=========
=========
-----
Next release
Next release
-----
* Fix display of inline x-editable boolean fields on list view
* Add support for several SQLAlchemy-Utils data types
* Add support for several SQLAlchemy-Utils data types
1.5.3
1.5.3
-----
-----
...
...
doc/conf.py
View file @
12e7b17a
...
@@ -43,7 +43,7 @@ master_doc = 'index'
...
@@ -43,7 +43,7 @@ master_doc = 'index'
# General information about the project.
# General information about the project.
project
=
u'flask-admin'
project
=
u'flask-admin'
copyright
=
u'2012-201
5, Serge S. Koval
'
copyright
=
u'2012-201
9, Flask-Admin Team
'
# The version info for the project you're documenting, acts as replacement for
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# |version| and |release|, also used in various other places throughout the
...
@@ -256,13 +256,13 @@ intersphinx_mapping = {'http://docs.python.org/': None}
...
@@ -256,13 +256,13 @@ intersphinx_mapping = {'http://docs.python.org/': None}
# fall back if theme is not there
# fall back if theme is not there
try
:
try
:
__import__
(
'flask_theme_support'
)
__import__
(
'flask_theme_support'
)
except
ImportError
,
e
:
except
ImportError
as
e
:
print
'-'
*
74
print
(
'-'
*
74
)
print
'Warning: Flask themes unavailable. Building with default theme'
print
(
'Warning: Flask themes unavailable. Building with default theme'
)
print
'If you want the Flask themes, run this command and build again:'
print
(
'If you want the Flask themes, run this command and build again:'
)
print
print
()
print
' git submodule update --init'
print
(
' git submodule update --init'
)
print
'-'
*
74
print
(
'-'
*
74
)
pygments_style
=
'tango'
pygments_style
=
'tango'
html_theme
=
'default'
html_theme
=
'default'
...
...
examples/auth/app.py
View file @
12e7b17a
...
@@ -54,15 +54,11 @@ security = Security(app, user_datastore)
...
@@ -54,15 +54,11 @@ security = Security(app, user_datastore)
# Create customized model view class
# Create customized model view class
class
MyModelView
(
sqla
.
ModelView
):
class
MyModelView
(
sqla
.
ModelView
):
def
is_accessible
(
self
):
def
is_accessible
(
self
):
if
not
current_user
.
is_active
or
not
current_user
.
is_authenticated
:
return
(
current_user
.
is_active
and
return
False
current_user
.
is_authenticated
and
current_user
.
has_role
(
'superuser'
)
if
current_user
.
has_role
(
'superuser'
):
)
return
True
return
False
def
_handle_view
(
self
,
name
,
**
kwargs
):
def
_handle_view
(
self
,
name
,
**
kwargs
):
"""
"""
...
...
examples/mongoengine/requirements.txt
View file @
12e7b17a
...
@@ -2,3 +2,4 @@ Flask
...
@@ -2,3 +2,4 @@ Flask
Flask-Admin
Flask-Admin
Flask-MongoEngine
Flask-MongoEngine
Flask-Login>=0.3.0
Flask-Login>=0.3.0
Pillow
\ No newline at end of file
flask_admin/contrib/geoa/fields.py
View file @
12e7b17a
...
@@ -19,7 +19,7 @@ class GeoJSONField(JSONField):
...
@@ -19,7 +19,7 @@ class GeoJSONField(JSONField):
super
(
GeoJSONField
,
self
)
.
__init__
(
label
,
validators
,
**
kwargs
)
super
(
GeoJSONField
,
self
)
.
__init__
(
label
,
validators
,
**
kwargs
)
self
.
web_srid
=
4326
self
.
web_srid
=
4326
self
.
srid
=
srid
self
.
srid
=
srid
if
self
.
srid
is
-
1
:
if
self
.
srid
==
-
1
:
self
.
transform_srid
=
self
.
web_srid
self
.
transform_srid
=
self
.
web_srid
else
:
else
:
self
.
transform_srid
=
self
.
srid
self
.
transform_srid
=
self
.
srid
...
@@ -30,11 +30,11 @@ class GeoJSONField(JSONField):
...
@@ -30,11 +30,11 @@ class GeoJSONField(JSONField):
if
self
.
raw_data
:
if
self
.
raw_data
:
return
self
.
raw_data
[
0
]
return
self
.
raw_data
[
0
]
if
type
(
self
.
data
)
is
geoalchemy2
.
elements
.
WKBElement
:
if
type
(
self
.
data
)
is
geoalchemy2
.
elements
.
WKBElement
:
if
self
.
srid
is
-
1
:
if
self
.
srid
==
-
1
:
return
self
.
session
.
scalar
(
func
.
ST_AsGeoJ
son
(
self
.
data
))
return
self
.
session
.
scalar
(
func
.
ST_AsGeoJ
SON
(
self
.
data
))
else
:
else
:
return
self
.
session
.
scalar
(
return
self
.
session
.
scalar
(
func
.
ST_AsGeoJ
son
(
func
.
ST_AsGeoJ
SON
(
func
.
ST_Transform
(
self
.
data
,
self
.
web_srid
)
func
.
ST_Transform
(
self
.
data
,
self
.
web_srid
)
)
)
)
)
...
@@ -43,7 +43,7 @@ class GeoJSONField(JSONField):
...
@@ -43,7 +43,7 @@ class GeoJSONField(JSONField):
def
process_formdata
(
self
,
valuelist
):
def
process_formdata
(
self
,
valuelist
):
super
(
GeoJSONField
,
self
)
.
process_formdata
(
valuelist
)
super
(
GeoJSONField
,
self
)
.
process_formdata
(
valuelist
)
if
str
(
self
.
data
)
is
''
:
if
str
(
self
.
data
)
==
''
:
self
.
data
=
None
self
.
data
=
None
if
self
.
data
is
not
None
:
if
self
.
data
is
not
None
:
web_shape
=
self
.
session
.
scalar
(
web_shape
=
self
.
session
.
scalar
(
...
...
flask_admin/contrib/geoa/typefmt.py
View file @
12e7b17a
...
@@ -17,8 +17,10 @@ def geom_formatter(view, value):
...
@@ -17,8 +17,10 @@ def geom_formatter(view, value):
"data-tile-layer-url"
:
view
.
tile_layer_url
,
"data-tile-layer-url"
:
view
.
tile_layer_url
,
"data-tile-layer-attribution"
:
view
.
tile_layer_attribution
"data-tile-layer-attribution"
:
view
.
tile_layer_attribution
})
})
if
value
.
srid
is
-
1
:
if
value
.
srid
==
-
1
:
value
.
srid
=
4326
value
.
srid
=
4326
geojson
=
view
.
session
.
query
(
view
.
model
)
.
with_entities
(
func
.
ST_AsGeoJSON
(
value
))
.
scalar
()
geojson
=
view
.
session
.
query
(
view
.
model
)
.
with_entities
(
func
.
ST_AsGeoJSON
(
value
))
.
scalar
()
return
Markup
(
'<textarea
%
s>
%
s</textarea>'
%
(
params
,
geojson
))
return
Markup
(
'<textarea
%
s>
%
s</textarea>'
%
(
params
,
geojson
))
...
...
flask_admin/contrib/mongoengine/form.py
View file @
12e7b17a
...
@@ -7,6 +7,7 @@ from flask_mongoengine.wtf import orm, fields as mongo_fields
...
@@ -7,6 +7,7 @@ from flask_mongoengine.wtf import orm, fields as mongo_fields
from
flask_admin
import
form
from
flask_admin
import
form
from
flask_admin.model.form
import
FieldPlaceholder
from
flask_admin.model.form
import
FieldPlaceholder
from
flask_admin.model.fields
import
InlineFieldList
,
AjaxSelectField
,
AjaxSelectMultipleField
from
flask_admin.model.fields
import
InlineFieldList
,
AjaxSelectField
,
AjaxSelectMultipleField
from
flask_admin.form.validators
import
FieldListInputRequired
from
flask_admin._compat
import
iteritems
from
flask_admin._compat
import
iteritems
from
.fields
import
ModelFormField
,
MongoFileField
,
MongoImageField
from
.fields
import
ModelFormField
,
MongoFileField
,
MongoImageField
...
@@ -74,7 +75,10 @@ class CustomModelConverter(orm.ModelConverter):
...
@@ -74,7 +75,10 @@ class CustomModelConverter(orm.ModelConverter):
kwargs
[
'validators'
]
=
list
(
kwargs
[
'validators'
])
kwargs
[
'validators'
]
=
list
(
kwargs
[
'validators'
])
if
field
.
required
:
if
field
.
required
:
kwargs
[
'validators'
]
.
append
(
validators
.
InputRequired
())
if
isinstance
(
field
,
ListField
):
kwargs
[
'validators'
]
.
append
(
FieldListInputRequired
())
else
:
kwargs
[
'validators'
]
.
append
(
validators
.
InputRequired
())
elif
not
isinstance
(
field
,
ListField
):
elif
not
isinstance
(
field
,
ListField
):
kwargs
[
'validators'
]
.
append
(
validators
.
Optional
())
kwargs
[
'validators'
]
.
append
(
validators
.
Optional
())
...
...
flask_admin/contrib/mongoengine/view.py
View file @
12e7b17a
...
@@ -364,8 +364,8 @@ class ModelView(BaseModelView):
...
@@ -364,8 +364,8 @@ class ModelView(BaseModelView):
# Check type
# Check type
if
(
field_type
not
in
self
.
allowed_search_types
):
if
(
field_type
not
in
self
.
allowed_search_types
):
raise
Exception
(
'Can only search on text columns. '
+
raise
Exception
(
'Can only search on text columns. '
+
'Failed to setup search for "
%
s"'
%
p
)
'Failed to setup search for "
%
s"'
%
p
)
self
.
_search_fields
.
append
(
p
)
self
.
_search_fields
.
append
(
p
)
...
...
flask_admin/contrib/peewee/view.py
View file @
12e7b17a
...
@@ -221,8 +221,8 @@ class ModelView(BaseModelView):
...
@@ -221,8 +221,8 @@ class ModelView(BaseModelView):
# Check type
# Check type
if
not
isinstance
(
p
,
(
CharField
,
TextField
)):
if
not
isinstance
(
p
,
(
CharField
,
TextField
)):
raise
Exception
(
'Can only search on text columns. '
+
raise
Exception
(
'Can only search on text columns. '
+
'Failed to setup search for "
%
s"'
%
p
)
'Failed to setup search for "
%
s"'
%
p
)
self
.
_search_fields
.
append
(
p
)
self
.
_search_fields
.
append
(
p
)
...
...
flask_admin/contrib/sqla/filters.py
View file @
12e7b17a
...
@@ -436,6 +436,22 @@ class ChoiceTypeNotLikeFilter(FilterNotLike):
...
@@ -436,6 +436,22 @@ class ChoiceTypeNotLikeFilter(FilterNotLike):
return
query
return
query
class
UuidFilterEqual
(
FilterEqual
,
filters
.
BaseUuidFilter
):
pass
class
UuidFilterNotEqual
(
FilterNotEqual
,
filters
.
BaseUuidFilter
):
pass
class
UuidFilterInList
(
filters
.
BaseUuidListFilter
,
FilterInList
):
pass
class
UuidFilterNotInList
(
filters
.
BaseUuidListFilter
,
FilterNotInList
):
pass
# Base SQLA filter field converter
# Base SQLA filter field converter
class
FilterConverter
(
filters
.
BaseFilterConverter
):
class
FilterConverter
(
filters
.
BaseFilterConverter
):
strings
=
(
FilterLike
,
FilterNotLike
,
FilterEqual
,
FilterNotEqual
,
strings
=
(
FilterLike
,
FilterNotLike
,
FilterEqual
,
FilterNotEqual
,
...
@@ -457,12 +473,16 @@ class FilterConverter(filters.BaseFilterConverter):
...
@@ -457,12 +473,16 @@ class FilterConverter(filters.BaseFilterConverter):
DateTimeGreaterFilter
,
DateTimeSmallerFilter
,
DateTimeGreaterFilter
,
DateTimeSmallerFilter
,
DateTimeBetweenFilter
,
DateTimeNotBetweenFilter
,
DateTimeBetweenFilter
,
DateTimeNotBetweenFilter
,
FilterEmpty
)
FilterEmpty
)
time_filters
=
(
TimeEqualFilter
,
TimeNotEqualFilter
,
TimeGreaterFilter
,
TimeSmallerFilter
,
time_filters
=
(
TimeEqualFilter
,
TimeNotEqualFilter
,
TimeGreaterFilter
,
TimeBetweenFilter
,
TimeNotBetweenFilter
,
FilterEmpty
)
TimeSmallerFilter
,
TimeBetweenFilter
,
TimeNotBetweenFilter
,
FilterEmpty
)
choice_type_filters
=
(
ChoiceTypeEqualFilter
,
ChoiceTypeNotEqualFilter
,
choice_type_filters
=
(
ChoiceTypeEqualFilter
,
ChoiceTypeNotEqualFilter
,
ChoiceTypeLikeFilter
,
ChoiceTypeNotLikeFilter
,
FilterEmpty
)
ChoiceTypeLikeFilter
,
ChoiceTypeNotLikeFilter
,
FilterEmpty
)
uuid_filters
=
(
UuidFilterEqual
,
UuidFilterNotEqual
,
FilterEmpty
,
UuidFilterInList
,
UuidFilterNotInList
)
arrow_type_filters
=
(
DateTimeGreaterFilter
,
DateTimeSmallerFilter
,
FilterEmpty
)
arrow_type_filters
=
(
DateTimeGreaterFilter
,
DateTimeSmallerFilter
,
FilterEmpty
)
def
convert
(
self
,
type_name
,
column
,
name
,
**
kwargs
):
def
convert
(
self
,
type_name
,
column
,
name
,
**
kwargs
):
filter_name
=
type_name
.
lower
()
filter_name
=
type_name
.
lower
()
...
@@ -531,3 +551,7 @@ class FilterConverter(filters.BaseFilterConverter):
...
@@ -531,3 +551,7 @@ class FilterConverter(filters.BaseFilterConverter):
kwargs
[
'enum_class'
]
=
column
.
type
.
_enum_class
kwargs
[
'enum_class'
]
=
column
.
type
.
_enum_class
return
[
f
(
column
,
name
,
options
,
**
kwargs
)
for
f
in
self
.
enum
]
return
[
f
(
column
,
name
,
options
,
**
kwargs
)
for
f
in
self
.
enum
]
@
filters
.
convert
(
'uuid'
)
def
conv_uuid
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
uuid_filters
]
flask_admin/contrib/sqla/view.py
View file @
12e7b17a
...
@@ -3,6 +3,7 @@ import warnings
...
@@ -3,6 +3,7 @@ import warnings
import
inspect
import
inspect
from
sqlalchemy.orm.attributes
import
InstrumentedAttribute
from
sqlalchemy.orm.attributes
import
InstrumentedAttribute
from
sqlalchemy.orm.base
import
manager_of_class
,
instance_state
from
sqlalchemy.orm
import
joinedload
,
aliased
from
sqlalchemy.orm
import
joinedload
,
aliased
from
sqlalchemy.sql.expression
import
desc
from
sqlalchemy.sql.expression
import
desc
from
sqlalchemy
import
Boolean
,
Table
,
func
,
or_
from
sqlalchemy
import
Boolean
,
Table
,
func
,
or_
...
@@ -328,6 +329,8 @@ class ModelView(BaseModelView):
...
@@ -328,6 +329,8 @@ class ModelView(BaseModelView):
menu_icon_type
=
menu_icon_type
,
menu_icon_type
=
menu_icon_type
,
menu_icon_value
=
menu_icon_value
)
menu_icon_value
=
menu_icon_value
)
self
.
_manager
=
manager_of_class
(
self
.
model
)
# Primary key
# Primary key
self
.
_primary_key
=
self
.
scaffold_pk
()
self
.
_primary_key
=
self
.
scaffold_pk
()
...
@@ -1111,7 +1114,12 @@ class ModelView(BaseModelView):
...
@@ -1111,7 +1114,12 @@ class ModelView(BaseModelView):
Form instance
Form instance
"""
"""
try
:
try
:
model
=
self
.
model
()
model
=
self
.
_manager
.
new_instance
()
# TODO: We need a better way to create model instances and stay compatible with
# SQLAlchemy __init__() behavior
state
=
instance_state
(
model
)
self
.
_manager
.
dispatch
.
init
(
state
,
[],
{})
form
.
populate_obj
(
model
)
form
.
populate_obj
(
model
)
self
.
session
.
add
(
model
)
self
.
session
.
add
(
model
)
self
.
_on_model_change
(
form
,
model
,
True
)
self
.
_on_model_change
(
form
,
model
,
True
)
...
...
flask_admin/form/validators.py
0 → 100644
View file @
12e7b17a
from
flask_admin.babel
import
gettext
from
wtforms.validators
import
StopValidation
class
FieldListInputRequired
(
object
):
"""
Validates that at least one item was provided for a FieldList
"""
field_flags
=
(
'required'
,)
def
__call__
(
self
,
form
,
field
):
if
len
(
field
.
entries
)
==
0
:
field
.
errors
[:]
=
[]
raise
StopValidation
(
gettext
(
'This field requires at least one item.'
))
flask_admin/helpers.py
View file @
12e7b17a
...
@@ -45,13 +45,15 @@ def get_url(endpoint, **kwargs):
...
@@ -45,13 +45,15 @@ def get_url(endpoint, **kwargs):
def
is_required_form_field
(
field
):
def
is_required_form_field
(
field
):
"""
"""
Check if form field has `DataRequired` or `InputRequired` validators.
Check if form field has `DataRequired`, `InputRequired`, or
`FieldListInputRequired` validators.
:param field:
:param field:
WTForms field to check
WTForms field to check
"""
"""
from
flask_admin.form.validators
import
FieldListInputRequired
for
validator
in
field
.
validators
:
for
validator
in
field
.
validators
:
if
isinstance
(
validator
,
(
DataRequired
,
InputRequired
)):
if
isinstance
(
validator
,
(
DataRequired
,
InputRequired
,
FieldListInputRequired
)):
return
True
return
True
return
False
return
False
...
...
flask_admin/model/filters.py
View file @
12e7b17a
import
time
import
time
import
datetime
import
datetime
import
uuid
from
flask_admin.babel
import
lazy_gettext
from
flask_admin.babel
import
lazy_gettext
...
@@ -269,6 +270,29 @@ class BaseTimeBetweenFilter(BaseFilter):
...
@@ -269,6 +270,29 @@ class BaseTimeBetweenFilter(BaseFilter):
return
False
return
False
class
BaseUuidFilter
(
BaseFilter
):
"""
Base uuid filter
"""
def
__init__
(
self
,
name
,
options
=
None
,
data_type
=
None
):
super
(
BaseUuidFilter
,
self
)
.
__init__
(
name
,
options
,
data_type
=
'uuid'
)
def
clean
(
self
,
value
):
value
=
uuid
.
UUID
(
value
)
return
str
(
value
)
class
BaseUuidListFilter
(
BaseFilter
):
"""
Base uuid list filter
"""
def
clean
(
self
,
value
):
return
[
str
(
uuid
.
UUID
(
v
.
strip
()))
for
v
in
value
.
split
(
','
)
if
v
.
strip
()]
def
convert
(
*
args
):
def
convert
(
*
args
):
"""
"""
Decorator for field to filter conversion routine.
Decorator for field to filter conversion routine.
...
...
flask_admin/model/widgets.py
View file @
12e7b17a
...
@@ -110,6 +110,7 @@ class XEditableWidget(object):
...
@@ -110,6 +110,7 @@ class XEditableWidget(object):
kwargs
[
'data-rows'
]
=
'5'
kwargs
[
'data-rows'
]
=
'5'
elif
field
.
type
==
'BooleanField'
:
elif
field
.
type
==
'BooleanField'
:
kwargs
[
'data-type'
]
=
'select2'
kwargs
[
'data-type'
]
=
'select2'
kwargs
[
'data-value'
]
=
'1'
if
field
.
data
else
''
# data-source = dropdown options
# data-source = dropdown options
kwargs
[
'data-source'
]
=
json
.
dumps
([
kwargs
[
'data-source'
]
=
json
.
dumps
([
{
'value'
:
''
,
'text'
:
gettext
(
'No'
)},
{
'value'
:
''
,
'text'
:
gettext
(
'No'
)},
...
...
flask_admin/static/admin/js/form.js
View file @
12e7b17a
...
@@ -494,15 +494,21 @@
...
@@ -494,15 +494,21 @@
case
'x-editable-boolean'
:
case
'x-editable-boolean'
:
$el
.
editable
({
$el
.
editable
({
params
:
overrideXeditableParams
,
params
:
overrideXeditableParams
,
display
:
function
(
value
,
sourceData
,
response
)
{
display
:
function
(
value
,
response
)
{
// display new boolean value as an icon
// display boolean value as an icon
if
(
response
)
{
if
(
value
==
'1'
)
{
if
(
value
==
'1'
)
{
$
(
this
).
html
(
'<span class="fa fa-check-circle glyphicon glyphicon-ok-circle icon-ok-circle"></span>'
);
$
(
this
).
html
(
'<span class="fa fa-check-circle glyphicon glyphicon-ok-circle icon-ok-circle"></span>'
);
}
else
{
}
else
{
$
(
this
).
html
(
'<span class="fa fa-minus-circle glyphicon glyphicon-minus-sign icon-minus-sign"></span>'
);
$
(
this
).
html
(
'<span class="fa fa-minus-circle glyphicon glyphicon-minus-sign icon-minus-sign"></span>'
);
}
}
}
},
success
:
function
(
response
,
newValue
)
{
// update display
if
(
newValue
==
'1'
)
{
$
(
this
).
html
(
'<span class="fa fa-check-circle glyphicon glyphicon-ok-circle icon-ok-circle"></span>'
);
}
else
{
$
(
this
).
html
(
'<span class="fa fa-minus-circle glyphicon glyphicon-minus-sign icon-minus-sign"></span>'
);
}
}
}
});
});
}
}
...
...
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