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
90aea66a
Commit
90aea66a
authored
Jun 28, 2018
by
Rad Cirskis
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'upstream/master' into composite-keys
parents
9d2f4d31
7fa26ab2
Changes
34
Show whitespace changes
Inline
Side-by-side
Showing
34 changed files
with
284 additions
and
81 deletions
+284
-81
.travis.yml
.travis.yml
+4
-8
README.rst
README.rst
+1
-0
index.rst
doc/index.rst
+1
-1
introduction.rst
doc/introduction.rst
+3
-3
app.py
examples/auth-flask-login/app.py
+5
-0
app.py
examples/auth-mongoengine/app.py
+5
-0
fields.py
flask_admin/contrib/geoa/fields.py
+6
-2
form.py
flask_admin/contrib/geoa/form.py
+2
-0
typefmt.py
flask_admin/contrib/geoa/typefmt.py
+2
-0
view.py
flask_admin/contrib/geoa/view.py
+2
-0
widgets.py
flask_admin/contrib/geoa/widgets.py
+8
-1
form.py
flask_admin/contrib/peewee/form.py
+10
-2
tools.py
flask_admin/contrib/peewee/tools.py
+2
-2
view.py
flask_admin/contrib/peewee/view.py
+43
-21
ajax.py
flask_admin/contrib/sqla/ajax.py
+1
-1
fields.py
flask_admin/contrib/sqla/fields.py
+2
-2
filters.py
flask_admin/contrib/sqla/filters.py
+1
-1
form.py
flask_admin/contrib/sqla/form.py
+8
-4
tools.py
flask_admin/contrib/sqla/tools.py
+4
-4
view.py
flask_admin/contrib/sqla/view.py
+9
-1
base.py
flask_admin/model/base.py
+57
-6
typefmt.py
flask_admin/model/typefmt.py
+7
-0
widgets.py
flask_admin/model/widgets.py
+8
-6
admin.css
flask_admin/static/admin/css/bootstrap2/admin.css
+5
-1
admin.css
flask_admin/static/admin/css/bootstrap3/admin.css
+1
-1
form.js
flask_admin/static/admin/js/form.js
+15
-6
rediscli.js
flask_admin/static/admin/js/rediscli.js
+1
-0
base.html
flask_admin/templates/bootstrap2/admin/base.html
+1
-1
base.html
flask_admin/templates/bootstrap3/admin/base.html
+1
-1
test_basic.py
flask_admin/tests/sqla/test_basic.py
+28
-0
test_postgres.py
flask_admin/tests/sqla/test_postgres.py
+37
-0
requirements-dev.txt
requirements-dev.txt
+1
-0
setup.py
setup.py
+1
-4
tox.ini
tox.ini
+2
-2
No files found.
.travis.yml
View file @
90aea66a
...
@@ -2,10 +2,6 @@ sudo: false
...
@@ -2,10 +2,6 @@ sudo: false
language
:
python
language
:
python
matrix
:
matrix
:
include
:
include
:
-
python
:
2.6
env
:
TOX_ENV=py26-WTForms1
-
python
:
2.6
env
:
TOX_ENV=py26-WTForms2
-
python
:
2.7
-
python
:
2.7
env
:
TOX_ENV=py27-WTForms1
env
:
TOX_ENV=py27-WTForms1
-
python
:
2.7
-
python
:
2.7
...
@@ -14,10 +10,6 @@ matrix:
...
@@ -14,10 +10,6 @@ matrix:
env
:
TOX_ENV=flake8
env
:
TOX_ENV=flake8
-
python
:
2.7
-
python
:
2.7
env
:
TOX_ENV=docs-html
env
:
TOX_ENV=docs-html
-
python
:
3.3
env
:
TOX_ENV=py33-WTForms1
-
python
:
3.3
env
:
TOX_ENV=py33-WTForms2
-
python
:
3.4
-
python
:
3.4
env
:
TOX_ENV=py34-WTForms1
env
:
TOX_ENV=py34-WTForms1
-
python
:
3.4
-
python
:
3.4
...
@@ -33,6 +25,10 @@ matrix:
...
@@ -33,6 +25,10 @@ matrix:
addons
:
addons
:
postgresql
:
"
9.4"
postgresql
:
"
9.4"
apt
:
packages
:
-
postgresql-9.4-postgis-2.4
-
postgresql-9.4-postgis-2.4-scripts
services
:
services
:
-
postgresql
-
postgresql
...
...
README.rst
View file @
90aea66a
...
@@ -93,6 +93,7 @@ For all the tests to pass successfully, you'll need Postgres & MongoDB to be run
...
@@ -93,6 +93,7 @@ For all the tests to pass successfully, you'll need Postgres & MongoDB to be run
CREATE DATABASE flask_admin_test;
CREATE DATABASE flask_admin_test;
CREATE EXTENSION postgis;
CREATE EXTENSION postgis;
CREATE EXTENSION hstore;
You can also run the tests on multiple environments using *tox*.
You can also run the tests on multiple environments using *tox*.
...
...
doc/index.rst
View file @
90aea66a
...
@@ -41,7 +41,7 @@ Support
...
@@ -41,7 +41,7 @@ Support
****
****
Python 2.
6 - 2.7 and 3.3 - 3.4
.
Python 2.
7 and 3.3 or higher
.
Indices And Tables
Indices And Tables
------------------
------------------
...
...
doc/introduction.rst
View file @
90aea66a
...
@@ -80,6 +80,9 @@ are a few different ways of approaching this.
...
@@ -80,6 +80,9 @@ are a few different ways of approaching this.
HTTP Basic Auth
HTTP Basic Auth
---------------
---------------
Unfortunately, there is no easy way of applying HTTP Basic Auth just to your admin
interface.
The simplest form of authentication is HTTP Basic Auth. It doesn't interfere
The simplest form of authentication is HTTP Basic Auth. It doesn't interfere
with your database models, and it doesn't require you to write any new view logic or
with your database models, and it doesn't require you to write any new view logic or
template code. So it's great for when you're deploying something that's still
template code. So it's great for when you're deploying something that's still
...
@@ -88,9 +91,6 @@ under development, before you want the whole world to see it.
...
@@ -88,9 +91,6 @@ under development, before you want the whole world to see it.
Have a look at `Flask-BasicAuth <https://flask-basicauth.readthedocs.io/>`_ to see just how
Have a look at `Flask-BasicAuth <https://flask-basicauth.readthedocs.io/>`_ to see just how
easy it is to put your whole application behind HTTP Basic Auth.
easy it is to put your whole application behind HTTP Basic Auth.
Unfortunately, there is no easy way of applying HTTP Basic Auth just to your admin
interface.
Rolling Your Own
Rolling Your Own
----------------
----------------
For a more flexible solution, Flask-Admin lets you define access control rules
For a more flexible solution, Flask-Admin lets you define access control rules
...
...
examples/auth-flask-login/app.py
View file @
90aea66a
...
@@ -32,12 +32,17 @@ class User(db.Model):
...
@@ -32,12 +32,17 @@ class User(db.Model):
password
=
db
.
Column
(
db
.
String
(
64
))
password
=
db
.
Column
(
db
.
String
(
64
))
# Flask-Login integration
# Flask-Login integration
# NOTE: is_authenticated, is_active, and is_anonymous
# are methods in Flask-Login < 0.3.0
@
property
def
is_authenticated
(
self
):
def
is_authenticated
(
self
):
return
True
return
True
@
property
def
is_active
(
self
):
def
is_active
(
self
):
return
True
return
True
@
property
def
is_anonymous
(
self
):
def
is_anonymous
(
self
):
return
False
return
False
...
...
examples/auth-mongoengine/app.py
View file @
90aea66a
...
@@ -27,12 +27,17 @@ class User(db.Document):
...
@@ -27,12 +27,17 @@ class User(db.Document):
password
=
db
.
StringField
(
max_length
=
64
)
password
=
db
.
StringField
(
max_length
=
64
)
# Flask-Login integration
# Flask-Login integration
# NOTE: is_authenticated, is_active, and is_anonymous
# are methods in Flask-Login < 0.3.0
@
property
def
is_authenticated
(
self
):
def
is_authenticated
(
self
):
return
True
return
True
@
property
def
is_active
(
self
):
def
is_active
(
self
):
return
True
return
True
@
property
def
is_anonymous
(
self
):
def
is_anonymous
(
self
):
return
False
return
False
...
...
flask_admin/contrib/geoa/fields.py
View file @
90aea66a
...
@@ -8,10 +8,14 @@ from .widgets import LeafletWidget
...
@@ -8,10 +8,14 @@ from .widgets import LeafletWidget
class
GeoJSONField
(
JSONField
):
class
GeoJSONField
(
JSONField
):
widget
=
LeafletWidget
()
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
geometry_type
=
"GEOMETRY"
,
def
__init__
(
self
,
label
=
None
,
validators
=
None
,
geometry_type
=
"GEOMETRY"
,
srid
=
'-1'
,
session
=
None
,
**
kwargs
):
srid
=
'-1'
,
session
=
None
,
tile_layer_url
=
None
,
tile_layer_attribution
=
None
,
**
kwargs
):
self
.
widget
=
LeafletWidget
(
tile_layer_url
=
tile_layer_url
,
tile_layer_attribution
=
tile_layer_attribution
)
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
...
...
flask_admin/contrib/geoa/form.py
View file @
90aea66a
...
@@ -9,4 +9,6 @@ class AdminModelConverter(SQLAAdminConverter):
...
@@ -9,4 +9,6 @@ class AdminModelConverter(SQLAAdminConverter):
field_args
[
'geometry_type'
]
=
column
.
type
.
geometry_type
field_args
[
'geometry_type'
]
=
column
.
type
.
geometry_type
field_args
[
'srid'
]
=
column
.
type
.
srid
field_args
[
'srid'
]
=
column
.
type
.
srid
field_args
[
'session'
]
=
self
.
session
field_args
[
'session'
]
=
self
.
session
field_args
[
'tile_layer_url'
]
=
self
.
view
.
tile_layer_url
field_args
[
'tile_layer_attribution'
]
=
self
.
view
.
tile_layer_attribution
return
GeoJSONField
(
**
field_args
)
return
GeoJSONField
(
**
field_args
)
flask_admin/contrib/geoa/typefmt.py
View file @
90aea66a
...
@@ -14,6 +14,8 @@ def geom_formatter(view, value):
...
@@ -14,6 +14,8 @@ def geom_formatter(view, value):
"data-height"
:
70
,
"data-height"
:
70
,
"data-geometry-type"
:
to_shape
(
value
)
.
geom_type
,
"data-geometry-type"
:
to_shape
(
value
)
.
geom_type
,
"data-zoom"
:
15
,
"data-zoom"
:
15
,
"data-tile-layer-url"
:
view
.
tile_layer_url
,
"data-tile-layer-attribution"
:
view
.
tile_layer_attribution
})
})
if
value
.
srid
is
-
1
:
if
value
.
srid
is
-
1
:
value
.
srid
=
4326
value
.
srid
=
4326
...
...
flask_admin/contrib/geoa/view.py
View file @
90aea66a
...
@@ -5,3 +5,5 @@ from flask_admin.contrib.geoa import form, typefmt
...
@@ -5,3 +5,5 @@ from flask_admin.contrib.geoa import form, typefmt
class
ModelView
(
SQLAModelView
):
class
ModelView
(
SQLAModelView
):
model_form_converter
=
form
.
AdminModelConverter
model_form_converter
=
form
.
AdminModelConverter
column_type_formatters
=
typefmt
.
DEFAULT_FORMATTERS
column_type_formatters
=
typefmt
.
DEFAULT_FORMATTERS
tile_layer_url
=
None
tile_layer_attribution
=
None
flask_admin/contrib/geoa/widgets.py
View file @
90aea66a
...
@@ -23,7 +23,8 @@ class LeafletWidget(TextArea):
...
@@ -23,7 +23,8 @@ class LeafletWidget(TextArea):
"""
"""
def
__init__
(
def
__init__
(
self
,
width
=
'auto'
,
height
=
350
,
center
=
None
,
self
,
width
=
'auto'
,
height
=
350
,
center
=
None
,
zoom
=
None
,
min_zoom
=
None
,
max_zoom
=
None
,
max_bounds
=
None
):
zoom
=
None
,
min_zoom
=
None
,
max_zoom
=
None
,
max_bounds
=
None
,
tile_layer_url
=
None
,
tile_layer_attribution
=
None
):
self
.
width
=
width
self
.
width
=
width
self
.
height
=
height
self
.
height
=
height
self
.
center
=
center
self
.
center
=
center
...
@@ -31,6 +32,8 @@ class LeafletWidget(TextArea):
...
@@ -31,6 +32,8 @@ class LeafletWidget(TextArea):
self
.
min_zoom
=
min_zoom
self
.
min_zoom
=
min_zoom
self
.
max_zoom
=
max_zoom
self
.
max_zoom
=
max_zoom
self
.
max_bounds
=
max_bounds
self
.
max_bounds
=
max_bounds
self
.
tile_layer_url
=
tile_layer_url
self
.
tile_layer_attribution
=
tile_layer_attribution
def
__call__
(
self
,
field
,
**
kwargs
):
def
__call__
(
self
,
field
,
**
kwargs
):
kwargs
.
setdefault
(
'data-role'
,
self
.
data_role
)
kwargs
.
setdefault
(
'data-role'
,
self
.
data_role
)
...
@@ -38,6 +41,10 @@ class LeafletWidget(TextArea):
...
@@ -38,6 +41,10 @@ class LeafletWidget(TextArea):
kwargs
.
setdefault
(
'data-geometry-type'
,
gtype
)
kwargs
.
setdefault
(
'data-geometry-type'
,
gtype
)
# set optional values from constructor
# set optional values from constructor
if
self
.
tile_layer_url
:
kwargs
[
'data-tile-layer-url'
]
=
self
.
tile_layer_url
if
self
.
tile_layer_attribution
:
kwargs
[
'data-tile-layer-attribution'
]
=
self
.
tile_layer_attribution
if
"data-width"
not
in
kwargs
:
if
"data-width"
not
in
kwargs
:
kwargs
[
"data-width"
]
=
self
.
width
kwargs
[
"data-width"
]
=
self
.
width
if
"data-height"
not
in
kwargs
:
if
"data-height"
not
in
kwargs
:
...
...
flask_admin/contrib/peewee/form.py
View file @
90aea66a
from
wtforms
import
fields
from
wtforms
import
fields
from
peewee
import
(
CharField
,
DateTimeField
,
DateField
,
TimeField
,
from
peewee
import
(
CharField
,
DateTimeField
,
DateField
,
TimeField
,
PrimaryKeyField
,
ForeignKeyField
,
BaseModel
)
PrimaryKeyField
,
ForeignKeyField
)
try
:
from
peewee
import
BaseModel
except
ImportError
:
from
peewee
import
ModelBase
as
BaseModel
from
wtfpeewee.orm
import
ModelConverter
,
model_form
from
wtfpeewee.orm
import
ModelConverter
,
model_form
...
@@ -265,7 +270,10 @@ class InlineModelConverter(InlineModelConverterBase):
...
@@ -265,7 +270,10 @@ class InlineModelConverter(InlineModelConverterBase):
allow_pk
=
True
,
allow_pk
=
True
,
converter
=
converter
)
converter
=
converter
)
try
:
prop_name
=
reverse_field
.
related_name
prop_name
=
reverse_field
.
related_name
except
AttributeError
:
prop_name
=
reverse_field
.
backref
label
=
self
.
get_label
(
info
,
prop_name
)
label
=
self
.
get_label
(
info
,
prop_name
)
...
...
flask_admin/contrib/peewee/tools.py
View file @
90aea66a
...
@@ -14,8 +14,8 @@ def parse_like_term(term):
...
@@ -14,8 +14,8 @@ def parse_like_term(term):
def
get_meta_fields
(
model
):
def
get_meta_fields
(
model
):
try
:
if
hasattr
(
model
.
_meta
,
'sorted_fields'
)
:
fields
=
model
.
_meta
.
sorted_fields
fields
=
model
.
_meta
.
sorted_fields
e
xcept
AttributeError
:
e
lse
:
fields
=
model
.
_meta
.
get_fields
()
fields
=
model
.
_meta
.
get_fields
()
return
fields
return
fields
flask_admin/contrib/peewee/view.py
View file @
90aea66a
...
@@ -2,18 +2,18 @@ import logging
...
@@ -2,18 +2,18 @@ import logging
from
flask
import
flash
from
flask
import
flash
from
flask_admin._compat
import
string_types
,
iteritems
from
flask_admin._compat
import
string_types
from
flask_admin.babel
import
gettext
,
ngettext
,
lazy_gettext
from
flask_admin.babel
import
gettext
,
ngettext
,
lazy_gettext
from
flask_admin.model
import
BaseModelView
from
flask_admin.model
import
BaseModelView
from
flask_admin.model.form
import
create_editable_list_form
from
flask_admin.model.form
import
create_editable_list_form
from
peewee
import
PrimaryKeyField
,
ForeignKeyField
,
Field
,
CharField
,
TextField
from
peewee
import
JOIN
,
PrimaryKeyField
,
ForeignKeyField
,
Field
,
CharField
,
TextField
from
flask_admin.actions
import
action
from
flask_admin.actions
import
action
from
flask_admin.contrib.peewee
import
filters
from
flask_admin.contrib.peewee
import
filters
from
.form
import
get_form
,
CustomModelConverter
,
InlineModelConverter
,
save_inline
from
.form
import
get_form
,
CustomModelConverter
,
InlineModelConverter
,
save_inline
from
.tools
import
get_primary_key
,
parse_like_term
from
.tools
import
get_
meta_fields
,
get_
primary_key
,
parse_like_term
from
.ajax
import
create_ajax_loader
from
.ajax
import
create_ajax_loader
# Set up logger
# Set up logger
...
@@ -176,7 +176,9 @@ class ModelView(BaseModelView):
...
@@ -176,7 +176,9 @@ class ModelView(BaseModelView):
if
model
is
None
:
if
model
is
None
:
model
=
self
.
model
model
=
self
.
model
return
iteritems
(
model
.
_meta
.
fields
)
return
(
(
field
.
name
,
field
)
for
field
in
get_meta_fields
(
model
))
def
scaffold_pk
(
self
):
def
scaffold_pk
(
self
):
return
get_primary_key
(
self
.
model
)
return
get_primary_key
(
self
.
model
)
...
@@ -217,10 +219,8 @@ class ModelView(BaseModelView):
...
@@ -217,10 +219,8 @@ class ModelView(BaseModelView):
if
isinstance
(
p
,
string_types
):
if
isinstance
(
p
,
string_types
):
p
=
getattr
(
self
.
model
,
p
)
p
=
getattr
(
self
.
model
,
p
)
field_type
=
type
(
p
)
# Check type
# Check type
if
(
field_type
!=
CharField
and
field_type
!=
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
)
...
@@ -238,6 +238,7 @@ class ModelView(BaseModelView):
...
@@ -238,6 +238,7 @@ class ModelView(BaseModelView):
raise
Exception
(
'Failed to find field for filter:
%
s'
%
name
)
raise
Exception
(
'Failed to find field for filter:
%
s'
%
name
)
# Check if field is in different model
# Check if field is in different model
try
:
if
attr
.
model_class
!=
self
.
model
:
if
attr
.
model_class
!=
self
.
model
:
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
attr
.
model_class
.
__name__
),
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
attr
.
model_class
.
__name__
),
self
.
get_column_name
(
attr
.
name
))
self
.
get_column_name
(
attr
.
name
))
...
@@ -246,6 +247,15 @@ class ModelView(BaseModelView):
...
@@ -246,6 +247,15 @@ class ModelView(BaseModelView):
visible_name
=
self
.
get_column_name
(
attr
.
name
)
visible_name
=
self
.
get_column_name
(
attr
.
name
)
else
:
else
:
visible_name
=
self
.
get_column_name
(
name
)
visible_name
=
self
.
get_column_name
(
name
)
except
AttributeError
:
if
attr
.
model
!=
self
.
model
:
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
attr
.
model
.
__name__
),
self
.
get_column_name
(
attr
.
name
))
else
:
if
not
isinstance
(
name
,
string_types
):
visible_name
=
self
.
get_column_name
(
attr
.
name
)
else
:
visible_name
=
self
.
get_column_name
(
name
)
type_name
=
type
(
attr
)
.
__name__
type_name
=
type
(
attr
)
.
__name__
flt
=
self
.
filter_converter
.
convert
(
type_name
,
flt
=
self
.
filter_converter
.
convert
(
type_name
,
...
@@ -311,11 +321,19 @@ class ModelView(BaseModelView):
...
@@ -311,11 +321,19 @@ class ModelView(BaseModelView):
return
create_ajax_loader
(
self
.
model
,
name
,
name
,
options
)
return
create_ajax_loader
(
self
.
model
,
name
,
name
,
options
)
def
_handle_join
(
self
,
query
,
field
,
joins
):
def
_handle_join
(
self
,
query
,
field
,
joins
):
try
:
if
field
.
model_class
!=
self
.
model
:
if
field
.
model_class
!=
self
.
model
:
model_name
=
field
.
model_class
.
__name__
model_name
=
field
.
model_class
.
__name__
if
model_name
not
in
joins
:
if
model_name
not
in
joins
:
query
=
query
.
join
(
field
.
model_class
)
query
=
query
.
join
(
field
.
model_class
,
JOIN
.
LEFT_OUTER
)
joins
.
add
(
model_name
)
except
AttributeError
:
if
field
.
model
!=
self
.
model
:
model_name
=
field
.
model
.
__name__
if
model_name
not
in
joins
:
query
=
query
.
join
(
field
.
model
,
JOIN
.
LEFT_OUTER
)
joins
.
add
(
model_name
)
joins
.
add
(
model_name
)
return
query
return
query
...
@@ -325,8 +343,12 @@ class ModelView(BaseModelView):
...
@@ -325,8 +343,12 @@ class ModelView(BaseModelView):
field
=
getattr
(
self
.
model
,
sort_field
)
field
=
getattr
(
self
.
model
,
sort_field
)
query
=
query
.
order_by
(
field
.
desc
()
if
sort_desc
else
field
.
asc
())
query
=
query
.
order_by
(
field
.
desc
()
if
sort_desc
else
field
.
asc
())
elif
isinstance
(
sort_field
,
Field
):
elif
isinstance
(
sort_field
,
Field
):
try
:
if
sort_field
.
model_class
!=
self
.
model
:
if
sort_field
.
model_class
!=
self
.
model
:
query
=
self
.
_handle_join
(
query
,
sort_field
,
joins
)
query
=
self
.
_handle_join
(
query
,
sort_field
,
joins
)
except
AttributeError
:
if
sort_field
.
model
!=
self
.
model
:
query
=
self
.
_handle_join
(
query
,
sort_field
,
joins
)
query
=
query
.
order_by
(
sort_field
.
desc
()
if
sort_desc
else
sort_field
.
asc
())
query
=
query
.
order_by
(
sort_field
.
desc
()
if
sort_desc
else
sort_field
.
asc
())
...
...
flask_admin/contrib/sqla/ajax.py
View file @
90aea66a
...
@@ -69,7 +69,7 @@ class QueryAjaxModelLoader(AjaxModelLoader):
...
@@ -69,7 +69,7 @@ class QueryAjaxModelLoader(AjaxModelLoader):
query
=
query
.
filter
(
or_
(
*
filters
))
query
=
query
.
filter
(
or_
(
*
filters
))
if
self
.
filters
:
if
self
.
filters
:
filters
=
[
"
%
s.
%
s"
%
(
self
.
model
.
__name__
.
lower
(),
value
)
for
value
in
self
.
filters
]
filters
=
[
"
%
s.
%
s"
%
(
self
.
model
.
__
table
name__
.
lower
(),
value
)
for
value
in
self
.
filters
]
query
=
query
.
filter
(
and_
(
*
filters
))
query
=
query
.
filter
(
and_
(
*
filters
))
if
self
.
order_by
:
if
self
.
order_by
:
...
...
flask_admin/contrib/sqla/fields.py
View file @
90aea66a
...
@@ -276,7 +276,7 @@ class InlineModelFormList(InlineFieldList):
...
@@ -276,7 +276,7 @@ class InlineModelFormList(InlineFieldList):
# Handle request data
# Handle request data
for
field
in
self
.
entries
:
for
field
in
self
.
entries
:
field_id
=
field
.
get_pk
(
)
field_id
=
str
(
field
.
get_pk
()
)
is_created
=
field_id
not
in
pk_map
is_created
=
field_id
not
in
pk_map
if
not
is_created
:
if
not
is_created
:
...
@@ -296,5 +296,5 @@ class InlineModelFormList(InlineFieldList):
...
@@ -296,5 +296,5 @@ class InlineModelFormList(InlineFieldList):
def
get_pk_from_identity
(
obj
):
def
get_pk_from_identity
(
obj
):
# TODO: Remove me
# TODO: Remove me
cls
,
key
=
identity_key
(
instance
=
obj
)
key
=
identity_key
(
instance
=
obj
)[
1
]
return
u':'
.
join
(
text_type
(
x
)
for
x
in
key
)
return
u':'
.
join
(
text_type
(
x
)
for
x
in
key
)
flask_admin/contrib/sqla/filters.py
View file @
90aea66a
...
@@ -373,7 +373,7 @@ class FilterConverter(filters.BaseFilterConverter):
...
@@ -373,7 +373,7 @@ class FilterConverter(filters.BaseFilterConverter):
@
filters
.
convert
(
'string'
,
'char'
,
'unicode'
,
'varchar'
,
'tinytext'
,
@
filters
.
convert
(
'string'
,
'char'
,
'unicode'
,
'varchar'
,
'tinytext'
,
'text'
,
'mediumtext'
,
'longtext'
,
'unicodetext'
,
'text'
,
'mediumtext'
,
'longtext'
,
'unicodetext'
,
'nchar'
,
'nvarchar'
,
'ntext'
)
'nchar'
,
'nvarchar'
,
'ntext'
,
'citext'
)
def
conv_string
(
self
,
column
,
name
,
**
kwargs
):
def
conv_string
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
strings
]
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
strings
]
...
...
flask_admin/contrib/sqla/form.py
View file @
90aea66a
import
warnings
import
warnings
from
enum
import
Enum
from
wtforms
import
fields
,
validators
from
wtforms
import
fields
,
validators
from
sqlalchemy
import
Boolean
,
Column
from
sqlalchemy
import
Boolean
,
Column
...
@@ -9,7 +10,7 @@ from flask_admin.model.form import (converts, ModelConverterBase,
...
@@ -9,7 +10,7 @@ from flask_admin.model.form import (converts, ModelConverterBase,
from
flask_admin.model.fields
import
AjaxSelectField
,
AjaxSelectMultipleField
from
flask_admin.model.fields
import
AjaxSelectField
,
AjaxSelectMultipleField
from
flask_admin.model.helpers
import
prettify_name
from
flask_admin.model.helpers
import
prettify_name
from
flask_admin._backwards
import
get_property
from
flask_admin._backwards
import
get_property
from
flask_admin._compat
import
iteritems
from
flask_admin._compat
import
iteritems
,
text_type
from
.validators
import
Unique
from
.validators
import
Unique
from
.fields
import
(
QuerySelectField
,
QuerySelectMultipleField
,
from
.fields
import
(
QuerySelectField
,
QuerySelectMultipleField
,
...
@@ -154,7 +155,9 @@ class AdminModelConverter(ModelConverterBase):
...
@@ -154,7 +155,9 @@ class AdminModelConverter(ModelConverterBase):
if
len
(
prop
.
columns
)
>
1
:
if
len
(
prop
.
columns
)
>
1
:
columns
=
filter_foreign_columns
(
model
.
__table__
,
prop
.
columns
)
columns
=
filter_foreign_columns
(
model
.
__table__
,
prop
.
columns
)
if
len
(
columns
)
>
1
:
if
len
(
columns
)
==
0
:
return
None
elif
len
(
columns
)
>
1
:
warnings
.
warn
(
'Can not convert multiple-column properties (
%
s.
%
s)'
%
(
model
,
prop
.
key
))
warnings
.
warn
(
'Can not convert multiple-column properties (
%
s.
%
s)'
%
(
model
,
prop
.
key
))
return
None
return
None
...
@@ -264,7 +267,7 @@ class AdminModelConverter(ModelConverterBase):
...
@@ -264,7 +267,7 @@ class AdminModelConverter(ModelConverterBase):
@
classmethod
@
classmethod
def
_string_common
(
cls
,
column
,
field_args
,
**
extra
):
def
_string_common
(
cls
,
column
,
field_args
,
**
extra
):
if
isinstance
(
column
.
type
.
length
,
int
)
and
column
.
type
.
length
:
if
hasattr
(
column
.
type
,
'length'
)
and
isinstance
(
column
.
type
.
length
,
int
)
and
column
.
type
.
length
:
field_args
[
'validators'
]
.
append
(
validators
.
Length
(
max
=
column
.
type
.
length
))
field_args
[
'validators'
]
.
append
(
validators
.
Length
(
max
=
column
.
type
.
length
))
@
converts
(
'String'
)
# includes VARCHAR, CHAR, and Unicode
@
converts
(
'String'
)
# includes VARCHAR, CHAR, and Unicode
...
@@ -279,6 +282,7 @@ class AdminModelConverter(ModelConverterBase):
...
@@ -279,6 +282,7 @@ class AdminModelConverter(ModelConverterBase):
accepted_values
.
append
(
None
)
accepted_values
.
append
(
None
)
field_args
[
'validators'
]
.
append
(
validators
.
AnyOf
(
accepted_values
))
field_args
[
'validators'
]
.
append
(
validators
.
AnyOf
(
accepted_values
))
field_args
[
'coerce'
]
=
lambda
v
:
v
.
name
if
isinstance
(
v
,
Enum
)
else
text_type
(
v
)
return
form
.
Select2Field
(
**
field_args
)
return
form
.
Select2Field
(
**
field_args
)
...
@@ -290,7 +294,7 @@ class AdminModelConverter(ModelConverterBase):
...
@@ -290,7 +294,7 @@ class AdminModelConverter(ModelConverterBase):
self
.
_string_common
(
column
=
column
,
field_args
=
field_args
,
**
extra
)
self
.
_string_common
(
column
=
column
,
field_args
=
field_args
,
**
extra
)
return
fields
.
StringField
(
**
field_args
)
return
fields
.
StringField
(
**
field_args
)
@
converts
(
'Text'
,
'LargeBinary'
,
'Binary'
)
# includes UnicodeText
@
converts
(
'Text'
,
'LargeBinary'
,
'Binary'
,
'CIText'
)
# includes UnicodeText
def
conv_Text
(
self
,
field_args
,
**
extra
):
def
conv_Text
(
self
,
field_args
,
**
extra
):
self
.
_string_common
(
field_args
=
field_args
,
**
extra
)
self
.
_string_common
(
field_args
=
field_args
,
**
extra
)
return
fields
.
TextAreaField
(
**
field_args
)
return
fields
.
TextAreaField
(
**
field_args
)
...
...
flask_admin/contrib/sqla/tools.py
View file @
90aea66a
...
@@ -75,14 +75,14 @@ def tuple_operator_in(model_pk, ids):
...
@@ -75,14 +75,14 @@ def tuple_operator_in(model_pk, ids):
The returning operator can be used within a filter(), as it is just an or_ operator
The returning operator can be used within a filter(), as it is just an or_ operator
"""
"""
l
=
[]
ands
=
[]
for
id
in
ids
:
for
id
in
ids
:
k
=
[]
k
=
[]
for
i
in
range
(
len
(
model_pk
)):
for
i
in
range
(
len
(
model_pk
)):
k
.
append
(
eq
(
model_pk
[
i
],
id
[
i
]))
k
.
append
(
eq
(
model_pk
[
i
],
id
[
i
]))
l
.
append
(
and_
(
*
k
))
ands
.
append
(
and_
(
*
k
))
if
len
(
l
)
>=
1
:
if
len
(
ands
)
>=
1
:
return
or_
(
*
l
)
return
or_
(
*
ands
)
else
:
else
:
return
None
return
None
...
...
flask_admin/contrib/sqla/view.py
View file @
90aea66a
...
@@ -420,7 +420,9 @@ class ModelView(BaseModelView):
...
@@ -420,7 +420,9 @@ class ModelView(BaseModelView):
if
len
(
p
.
columns
)
>
1
:
if
len
(
p
.
columns
)
>
1
:
filtered
=
tools
.
filter_foreign_columns
(
self
.
model
.
__table__
,
p
.
columns
)
filtered
=
tools
.
filter_foreign_columns
(
self
.
model
.
__table__
,
p
.
columns
)
if
len
(
filtered
)
>
1
:
if
len
(
filtered
)
==
0
:
continue
elif
len
(
filtered
)
>
1
:
warnings
.
warn
(
'Can not convert multiple-column properties (
%
s.
%
s)'
%
(
self
.
model
,
p
.
key
))
warnings
.
warn
(
'Can not convert multiple-column properties (
%
s.
%
s)'
%
(
self
.
model
,
p
.
key
))
continue
continue
...
@@ -763,6 +765,12 @@ class ModelView(BaseModelView):
...
@@ -763,6 +765,12 @@ class ModelView(BaseModelView):
if
p
.
mapper
.
class_
==
self
.
model
:
if
p
.
mapper
.
class_
==
self
.
model
:
continue
continue
# Check if it is pointing to a differnet bind
source_bind
=
getattr
(
self
.
model
,
'__bind_key__'
,
None
)
target_bind
=
getattr
(
p
.
mapper
.
class_
,
'__bind_key__'
,
None
)
if
source_bind
!=
target_bind
:
continue
if
p
.
direction
.
name
in
[
'MANYTOONE'
,
'MANYTOMANY'
]:
if
p
.
direction
.
name
in
[
'MANYTOONE'
,
'MANYTOMANY'
]:
relations
.
add
(
p
.
key
)
relations
.
add
(
p
.
key
)
...
...
flask_admin/model/base.py
View file @
90aea66a
...
@@ -263,6 +263,16 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -263,6 +263,16 @@ class BaseModelView(BaseView, ActionsMixin):
that macros are not supported.
that macros are not supported.
"""
"""
column_formatters_detail
=
None
"""
Dictionary of list view column formatters to be used for the detail view.
Defaults to column_formatters when set to None.
Functions the same way as column_formatters except
that macros are not supported.
"""
column_type_formatters
=
ObsoleteAttr
(
'column_type_formatters'
,
'list_type_formatters'
,
None
)
column_type_formatters
=
ObsoleteAttr
(
'column_type_formatters'
,
'list_type_formatters'
,
None
)
"""
"""
Dictionary of value type formatters to be used in the list view.
Dictionary of value type formatters to be used in the list view.
...
@@ -319,6 +329,18 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -319,6 +329,18 @@ class BaseModelView(BaseView, ActionsMixin):
Functions the same way as column_type_formatters.
Functions the same way as column_type_formatters.
"""
"""
column_type_formatters_detail
=
None
"""
Dictionary of value type formatters to be used in the detail view.
By default, two types are formatted:
1. ``None`` will be displayed as an empty string
2. ``list`` will be joined using ', '
Functions the same way as column_type_formatters.
"""
column_labels
=
ObsoleteAttr
(
'column_labels'
,
'rename_columns'
,
None
)
column_labels
=
ObsoleteAttr
(
'column_labels'
,
'rename_columns'
,
None
)
"""
"""
Dictionary where key is column name and value is string to display.
Dictionary where key is column name and value is string to display.
...
@@ -889,6 +911,9 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -889,6 +911,9 @@ class BaseModelView(BaseView, ActionsMixin):
if
self
.
column_formatters_export
is
None
:
if
self
.
column_formatters_export
is
None
:
self
.
column_formatters_export
=
self
.
column_formatters
self
.
column_formatters_export
=
self
.
column_formatters
if
self
.
column_formatters_detail
is
None
:
self
.
column_formatters_detail
=
self
.
column_formatters
# Type formatters
# Type formatters
if
self
.
column_type_formatters
is
None
:
if
self
.
column_type_formatters
is
None
:
self
.
column_type_formatters
=
dict
(
typefmt
.
BASE_FORMATTERS
)
self
.
column_type_formatters
=
dict
(
typefmt
.
BASE_FORMATTERS
)
...
@@ -896,6 +921,9 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -896,6 +921,9 @@ class BaseModelView(BaseView, ActionsMixin):
if
self
.
column_type_formatters_export
is
None
:
if
self
.
column_type_formatters_export
is
None
:
self
.
column_type_formatters_export
=
dict
(
typefmt
.
EXPORT_FORMATTERS
)
self
.
column_type_formatters_export
=
dict
(
typefmt
.
EXPORT_FORMATTERS
)
if
self
.
column_type_formatters_detail
is
None
:
self
.
column_type_formatters_detail
=
dict
(
typefmt
.
DETAIL_FORMATTERS
)
if
self
.
column_descriptions
is
None
:
if
self
.
column_descriptions
is
None
:
self
.
column_descriptions
=
dict
()
self
.
column_descriptions
=
dict
()
...
@@ -1518,12 +1546,15 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1518,12 +1546,15 @@ class BaseModelView(BaseView, ActionsMixin):
"""
"""
try
:
try
:
self
.
on_model_change
(
form
,
model
,
is_created
)
self
.
on_model_change
(
form
,
model
,
is_created
)
except
TypeError
:
except
TypeError
as
e
:
if
re
.
match
(
r'on_model_change\(\) takes .* 3 .* arguments .* 4 .* given .*'
,
e
.
message
):
msg
=
(
'
%
s.on_model_change() now accepts third '
+
msg
=
(
'
%
s.on_model_change() now accepts third '
+
'parameter is_created. Please update your code'
)
%
self
.
model
'parameter is_created. Please update your code'
)
%
self
.
model
warnings
.
warn
(
msg
)
warnings
.
warn
(
msg
)
self
.
on_model_change
(
form
,
model
)
self
.
on_model_change
(
form
,
model
)
else
:
raise
def
after_model_change
(
self
,
form
,
model
,
is_created
):
def
after_model_change
(
self
,
form
,
model
,
is_created
):
"""
"""
...
@@ -1803,6 +1834,26 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1803,6 +1834,26 @@ class BaseModelView(BaseView, ActionsMixin):
self
.
column_type_formatters
,
self
.
column_type_formatters
,
)
)
@
contextfunction
def
get_detail_value
(
self
,
context
,
model
,
name
):
"""
Returns the value to be displayed in the detail view
:param context:
:py:class:`jinja2.runtime.Context`
:param model:
Model instance
:param name:
Field name
"""
return
self
.
_get_list_value
(
context
,
model
,
name
,
self
.
column_formatters_detail
,
self
.
column_type_formatters_detail
,
)
def
get_export_value
(
self
,
model
,
name
):
def
get_export_value
(
self
,
model
,
name
):
"""
"""
Returns the value to be displayed in export.
Returns the value to be displayed in export.
...
@@ -2103,7 +2154,7 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -2103,7 +2154,7 @@ class BaseModelView(BaseView, ActionsMixin):
return
self
.
render
(
template
,
return
self
.
render
(
template
,
model
=
model
,
model
=
model
,
details_columns
=
self
.
_details_columns
,
details_columns
=
self
.
_details_columns
,
get_value
=
self
.
get_
list
_value
,
get_value
=
self
.
get_
detail
_value
,
return_url
=
return_url
)
return_url
=
return_url
)
@
expose
(
'/delete/'
,
methods
=
(
'POST'
,))
@
expose
(
'/delete/'
,
methods
=
(
'POST'
,))
...
...
flask_admin/model/typefmt.py
View file @
90aea66a
...
@@ -84,6 +84,13 @@ EXPORT_FORMATTERS = {
...
@@ -84,6 +84,13 @@ EXPORT_FORMATTERS = {
dict
:
dict_formatter
,
dict
:
dict_formatter
,
}
}
DETAIL_FORMATTERS
=
{
type
(
None
):
empty_formatter
,
list
:
list_formatter
,
dict
:
dict_formatter
,
}
if
Enum
is
not
None
:
if
Enum
is
not
None
:
BASE_FORMATTERS
[
Enum
]
=
enum_formatter
BASE_FORMATTERS
[
Enum
]
=
enum_formatter
EXPORT_FORMATTERS
[
Enum
]
=
enum_formatter
EXPORT_FORMATTERS
[
Enum
]
=
enum_formatter
DETAIL_FORMATTERS
[
Enum
]
=
enum_formatter
flask_admin/model/widgets.py
View file @
90aea66a
...
@@ -72,7 +72,8 @@ class XEditableWidget(object):
...
@@ -72,7 +72,8 @@ class XEditableWidget(object):
field inside of the FieldList (StringField, IntegerField, etc).
field inside of the FieldList (StringField, IntegerField, etc).
"""
"""
def
__call__
(
self
,
field
,
**
kwargs
):
def
__call__
(
self
,
field
,
**
kwargs
):
kwargs
.
setdefault
(
'data-value'
,
kwargs
.
pop
(
'display_value'
,
''
))
display_value
=
kwargs
.
pop
(
'display_value'
,
''
)
kwargs
.
setdefault
(
'data-value'
,
display_value
)
kwargs
.
setdefault
(
'data-role'
,
'x-editable'
)
kwargs
.
setdefault
(
'data-role'
,
'x-editable'
)
kwargs
.
setdefault
(
'data-url'
,
'./ajax/update/'
)
kwargs
.
setdefault
(
'data-url'
,
'./ajax/update/'
)
...
@@ -91,7 +92,7 @@ class XEditableWidget(object):
...
@@ -91,7 +92,7 @@ class XEditableWidget(object):
return
HTMLString
(
return
HTMLString
(
'<a
%
s>
%
s</a>'
%
(
html_params
(
**
kwargs
),
'<a
%
s>
%
s</a>'
%
(
html_params
(
**
kwargs
),
escape
(
kwargs
[
'data-value'
]
))
escape
(
display_value
))
)
)
def
get_kwargs
(
self
,
field
,
kwargs
):
def
get_kwargs
(
self
,
field
,
kwargs
):
...
@@ -104,7 +105,7 @@ class XEditableWidget(object):
...
@@ -104,7 +105,7 @@ class XEditableWidget(object):
kwargs
[
'data-type'
]
=
'textarea'
kwargs
[
'data-type'
]
=
'textarea'
kwargs
[
'data-rows'
]
=
'5'
kwargs
[
'data-rows'
]
=
'5'
elif
field
.
type
==
'BooleanField'
:
elif
field
.
type
==
'BooleanField'
:
kwargs
[
'data-type'
]
=
'select'
kwargs
[
'data-type'
]
=
'select
2
'
# 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'
)},
...
@@ -112,7 +113,7 @@ class XEditableWidget(object):
...
@@ -112,7 +113,7 @@ class XEditableWidget(object):
])
])
kwargs
[
'data-role'
]
=
'x-editable-boolean'
kwargs
[
'data-role'
]
=
'x-editable-boolean'
elif
field
.
type
in
[
'Select2Field'
,
'SelectField'
]:
elif
field
.
type
in
[
'Select2Field'
,
'SelectField'
]:
kwargs
[
'data-type'
]
=
'select'
kwargs
[
'data-type'
]
=
'select
2
'
choices
=
[{
'value'
:
x
,
'text'
:
y
}
for
x
,
y
in
field
.
choices
]
choices
=
[{
'value'
:
x
,
'text'
:
y
}
for
x
,
y
in
field
.
choices
]
# prepend a blank field to choices if allow_blank = True
# prepend a blank field to choices if allow_blank = True
...
@@ -144,7 +145,7 @@ class XEditableWidget(object):
...
@@ -144,7 +145,7 @@ class XEditableWidget(object):
elif
field
.
type
in
[
'QuerySelectField'
,
'ModelSelectField'
,
elif
field
.
type
in
[
'QuerySelectField'
,
'ModelSelectField'
,
'QuerySelectMultipleField'
,
'KeyPropertyField'
]:
'QuerySelectMultipleField'
,
'KeyPropertyField'
]:
# QuerySelectField and ModelSelectField are for relations
# QuerySelectField and ModelSelectField are for relations
kwargs
[
'data-type'
]
=
'select'
kwargs
[
'data-type'
]
=
'select
2
'
choices
=
[]
choices
=
[]
selected_ids
=
[]
selected_ids
=
[]
...
@@ -162,12 +163,13 @@ class XEditableWidget(object):
...
@@ -162,12 +163,13 @@ class XEditableWidget(object):
kwargs
[
'data-source'
]
=
json
.
dumps
(
choices
)
kwargs
[
'data-source'
]
=
json
.
dumps
(
choices
)
if
field
.
type
==
'QuerySelectMultipleField'
:
if
field
.
type
==
'QuerySelectMultipleField'
:
kwargs
[
'data-type'
]
=
'select2'
kwargs
[
'data-role'
]
=
'x-editable-select2-multiple'
kwargs
[
'data-role'
]
=
'x-editable-select2-multiple'
# must use id instead of text or prefilled values won't work
# must use id instead of text or prefilled values won't work
separator
=
getattr
(
field
,
'separator'
,
','
)
separator
=
getattr
(
field
,
'separator'
,
','
)
kwargs
[
'data-value'
]
=
separator
.
join
(
selected_ids
)
kwargs
[
'data-value'
]
=
separator
.
join
(
selected_ids
)
else
:
kwargs
[
'data-value'
]
=
text_type
(
selected_ids
[
0
])
else
:
else
:
raise
Exception
(
'Unsupported field type:
%
s'
%
(
type
(
field
),))
raise
Exception
(
'Unsupported field type:
%
s'
%
(
type
(
field
),))
...
...
flask_admin/static/admin/css/bootstrap2/admin.css
View file @
90aea66a
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
/* List View - fix gap between actions and table */
/* List View - fix gap between actions and table */
.model-list
{
.model-list
{
position
:
relative
;
position
:
static
;
margin-top
:
-1px
;
margin-top
:
-1px
;
z-index
:
999
;
z-index
:
999
;
}
}
...
@@ -139,3 +139,7 @@ table.filters tr td {
...
@@ -139,3 +139,7 @@ table.filters tr td {
*/
*/
#no-more-tables
td
:before
{
content
:
attr
(
data-title
);
}
#no-more-tables
td
:before
{
content
:
attr
(
data-title
);
}
}
}
.editable-input
.select2-container
{
min-width
:
220px
;
}
flask_admin/static/admin/css/bootstrap3/admin.css
View file @
90aea66a
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
/* List View - fix overlapping border between actions and table */
/* List View - fix overlapping border between actions and table */
.model-list
{
.model-list
{
position
:
relative
;
position
:
static
;
margin-top
:
-1px
;
margin-top
:
-1px
;
z-index
:
999
;
z-index
:
999
;
}
}
...
...
flask_admin/static/admin/js/form.js
View file @
90aea66a
...
@@ -157,11 +157,19 @@
...
@@ -157,11 +157,19 @@
}
}
// set up tiles
// set up tiles
if
(
$el
.
data
(
'tile-layer-url'
)){
var
attribution
=
$el
.
data
(
'tile-layer-attribution'
)
||
''
L
.
tileLayer
(
'//'
+
$el
.
data
(
'tile-layer-url'
),
{
attribution
:
attribution
,
maxZoom
:
18
}).
addTo
(
map
)
}
else
{
var
mapboxVersion
=
window
.
MAPBOX_ACCESS_TOKEN
?
4
:
3
;
var
mapboxVersion
=
window
.
MAPBOX_ACCESS_TOKEN
?
4
:
3
;
L
.
tileLayer
(
'//{s}.tiles.mapbox.com/v'
+
mapboxVersion
+
'/'
+
MAPBOX_MAP_ID
+
'/{z}/{x}/{y}.png?access_token='
+
window
.
MAPBOX_ACCESS_TOKEN
,
{
L
.
tileLayer
(
'//{s}.tiles.mapbox.com/v'
+
mapboxVersion
+
'/'
+
MAPBOX_MAP_ID
+
'/{z}/{x}/{y}.png?access_token='
+
window
.
MAPBOX_ACCESS_TOKEN
,
{
attribution
:
'Map data © <a href="//openstreetmap.org">OpenStreetMap</a> contributors, <a href="//creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="//mapbox.com">Mapbox</a>'
,
attribution
:
'Map data © <a href="//openstreetmap.org">OpenStreetMap</a> contributors, <a href="//creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="//mapbox.com">Mapbox</a>'
,
maxZoom
:
18
maxZoom
:
18
}).
addTo
(
map
);
}).
addTo
(
map
);
}
// everything below here is to set up editing, so if we're not editable,
// everything below here is to set up editing, so if we're not editable,
...
@@ -451,7 +459,8 @@
...
@@ -451,7 +459,8 @@
params
:
overrideXeditableParams
,
params
:
overrideXeditableParams
,
combodate
:
{
combodate
:
{
// prevent minutes from showing in 5 minute increments
// prevent minutes from showing in 5 minute increments
minuteStep
:
1
minuteStep
:
1
,
maxYear
:
2030
,
}
}
});
});
return
true
;
return
true
;
...
...
flask_admin/static/admin/js/rediscli.js
View file @
90aea66a
...
@@ -79,6 +79,7 @@ var RedisCli = function(postUrl) {
...
@@ -79,6 +79,7 @@ var RedisCli = function(postUrl) {
sendCommand
(
val
);
sendCommand
(
val
);
$input
.
val
(
''
);
$input
.
val
(
''
);
return
false
;
}
}
function
onKeyPress
(
e
)
{
function
onKeyPress
(
e
)
{
...
...
flask_admin/templates/bootstrap2/admin/base.html
View file @
90aea66a
...
@@ -33,7 +33,7 @@
...
@@ -33,7 +33,7 @@
</head>
</head>
<body>
<body>
{% block page_body %}
{% block page_body %}
<div
class=
"container"
>
<div
class=
"container
{%if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}
"
>
<div
class=
"navbar"
>
<div
class=
"navbar"
>
<div
class=
"navbar-inner"
>
<div
class=
"navbar-inner"
>
{% block brand %}
{% block brand %}
...
...
flask_admin/templates/bootstrap3/admin/base.html
View file @
90aea66a
...
@@ -35,7 +35,7 @@
...
@@ -35,7 +35,7 @@
</head>
</head>
<body>
<body>
{% block page_body %}
{% block page_body %}
<div
class=
"container"
>
<div
class=
"container
{%if config.get('FLASK_ADMIN_FLUID_LAYOUT', False) %}-fluid{% endif %}
"
>
<nav
class=
"navbar navbar-default"
role=
"navigation"
>
<nav
class=
"navbar navbar-default"
role=
"navigation"
>
<!-- Brand and toggle get grouped for better mobile display -->
<!-- Brand and toggle get grouped for better mobile display -->
<div
class=
"navbar-header"
>
<div
class=
"navbar-header"
>
...
...
flask_admin/tests/sqla/test_basic.py
View file @
90aea66a
...
@@ -2218,6 +2218,34 @@ def test_multipath_joins():
...
@@ -2218,6 +2218,34 @@ def test_multipath_joins():
eq_
(
rv
.
status_code
,
200
)
eq_
(
rv
.
status_code
,
200
)
def
test_different_bind_joins
():
app
,
db
,
admin
=
setup
()
app
.
config
[
'SQLALCHEMY_BINDS'
]
=
{
'other'
:
'sqlite:///'
}
class
Model1
(
db
.
Model
):
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
val1
=
db
.
Column
(
db
.
String
(
20
))
class
Model2
(
db
.
Model
):
__bind_key__
=
'other'
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
)
val1
=
db
.
Column
(
db
.
String
(
20
))
first_id
=
db
.
Column
(
db
.
Integer
,
db
.
ForeignKey
(
Model1
.
id
))
first
=
db
.
relationship
(
Model1
)
db
.
create_all
()
view
=
CustomModelView
(
Model2
,
db
.
session
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/model2/'
)
eq_
(
rv
.
status_code
,
200
)
def
test_model_default
():
def
test_model_default
():
app
,
db
,
admin
=
setup
()
app
,
db
,
admin
=
setup
()
_
,
Model2
=
create_models
(
db
)
_
,
Model2
=
create_models
(
db
)
...
...
flask_admin/tests/sqla/test_postgres.py
View file @
90aea66a
...
@@ -4,6 +4,7 @@ from . import setup_postgres
...
@@ -4,6 +4,7 @@ from . import setup_postgres
from
.test_basic
import
CustomModelView
from
.test_basic
import
CustomModelView
from
sqlalchemy.dialects.postgresql
import
HSTORE
,
JSON
from
sqlalchemy.dialects.postgresql
import
HSTORE
,
JSON
from
citext
import
CIText
def
test_hstore
():
def
test_hstore
():
...
@@ -75,3 +76,39 @@ def test_json():
...
@@ -75,3 +76,39 @@ def test_json():
data
=
rv
.
data
.
decode
(
'utf-8'
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'json_test'
in
data
)
ok_
(
'json_test'
in
data
)
ok_
(
'>{"test_key1": "test_value1"}<'
in
data
)
ok_
(
'>{"test_key1": "test_value1"}<'
in
data
)
def
test_citext
():
app
,
db
,
admin
=
setup_postgres
()
class
CITextModel
(
db
.
Model
):
id
=
db
.
Column
(
db
.
Integer
,
primary_key
=
True
,
autoincrement
=
True
)
citext_test
=
db
.
Column
(
CIText
)
db
.
engine
.
execute
(
'CREATE EXTENSION IF NOT EXISTS citext'
)
db
.
create_all
()
view
=
CustomModelView
(
CITextModel
,
db
.
session
)
admin
.
add_view
(
view
)
client
=
app
.
test_client
()
rv
=
client
.
get
(
'/admin/citextmodel/'
)
eq_
(
rv
.
status_code
,
200
)
rv
=
client
.
post
(
'/admin/citextmodel/new/'
,
data
=
{
'citext_test'
:
'Foo'
,
})
eq_
(
rv
.
status_code
,
302
)
rv
=
client
.
get
(
'/admin/citextmodel/'
)
eq_
(
rv
.
status_code
,
200
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'citext_test'
in
data
)
ok_
(
'Foo'
in
data
)
rv
=
client
.
get
(
'/admin/citextmodel/edit/?id=1'
)
eq_
(
rv
.
status_code
,
200
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'name="citext_test"'
in
data
)
ok_
(
'>Foo<'
in
data
)
requirements-dev.txt
View file @
90aea66a
...
@@ -14,3 +14,4 @@ psycopg2
...
@@ -14,3 +14,4 @@ psycopg2
nose
nose
coveralls
coveralls
pylint
pylint
sqlalchemy-citext
setup.py
View file @
90aea66a
...
@@ -36,9 +36,6 @@ install_requires = [
...
@@ -36,9 +36,6 @@ install_requires = [
'wtforms'
'wtforms'
]
]
if
sys
.
version_info
[:
2
]
<
(
2
,
7
):
install_requires
.
append
(
'ordereddict'
)
setup
(
setup
(
name
=
'Flask-Admin'
,
name
=
'Flask-Admin'
,
version
=
grep
(
'__version__'
),
version
=
grep
(
'__version__'
),
...
@@ -76,10 +73,10 @@ setup(
...
@@ -76,10 +73,10 @@ setup(
'Programming Language :: Python'
,
'Programming Language :: Python'
,
'Topic :: Software Development :: Libraries :: Python Modules'
,
'Topic :: Software Development :: Libraries :: Python Modules'
,
'Programming Language :: Python :: 2.7'
,
'Programming Language :: Python :: 2.7'
,
'Programming Language :: Python :: 2.6'
,
'Programming Language :: Python :: 3.3'
,
'Programming Language :: Python :: 3.3'
,
'Programming Language :: Python :: 3.4'
,
'Programming Language :: Python :: 3.4'
,
'Programming Language :: Python :: 3.5'
,
'Programming Language :: Python :: 3.5'
,
'Programming Language :: Python :: 3.6'
,
],
],
test_suite
=
'nose.collector'
test_suite
=
'nose.collector'
)
)
tox.ini
View file @
90aea66a
[tox]
[tox]
envlist
=
envlist
=
py{2
6,27,33
,34,35,36}-WTForms{1,2}
py{2
7
,34,35,36}-WTForms{1,2}
flake8
flake8
docs-html
docs-html
skipsdist
=
true
skipsdist
=
true
...
@@ -8,7 +8,7 @@ skip_missing_interpreters = true
...
@@ -8,7 +8,7 @@ skip_missing_interpreters = true
[flake8]
[flake8]
max_line_length
=
120
max_line_length
=
120
ignore
=
E402
ignore
=
E402
,E722
[testenv]
[testenv]
usedevelop
=
true
usedevelop
=
true
...
...
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