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
26bb7798
Commit
26bb7798
authored
Jun 28, 2018
by
Rad Cirskis
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'upstream/master' into missing-extra-args-_get_list_extra_args
parents
9b987b8f
7fa26ab2
Changes
29
Hide whitespace changes
Inline
Side-by-side
Showing
29 changed files
with
222 additions
and
63 deletions
+222
-63
.travis.yml
.travis.yml
+0
-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
view.py
flask_admin/contrib/peewee/view.py
+36
-14
ajax.py
flask_admin/contrib/sqla/ajax.py
+1
-1
fields.py
flask_admin/contrib/sqla/fields.py
+1
-1
filters.py
flask_admin/contrib/sqla/filters.py
+1
-1
form.py
flask_admin/contrib/sqla/form.py
+6
-2
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
+13
-5
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
setup.py
setup.py
+1
-4
tox.ini
tox.ini
+1
-1
No files found.
.travis.yml
View file @
26bb7798
...
...
@@ -2,10 +2,6 @@ sudo: false
language
:
python
matrix
:
include
:
-
python
:
2.6
env
:
TOX_ENV=py26-WTForms1
-
python
:
2.6
env
:
TOX_ENV=py26-WTForms2
-
python
:
2.7
env
:
TOX_ENV=py27-WTForms1
-
python
:
2.7
...
...
@@ -14,10 +10,6 @@ matrix:
env
:
TOX_ENV=flake8
-
python
:
2.7
env
:
TOX_ENV=docs-html
-
python
:
3.3
env
:
TOX_ENV=py33-WTForms1
-
python
:
3.3
env
:
TOX_ENV=py33-WTForms2
-
python
:
3.4
env
:
TOX_ENV=py34-WTForms1
-
python
:
3.4
...
...
README.rst
View file @
26bb7798
...
...
@@ -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 EXTENSION postgis;
CREATE EXTENSION hstore;
You can also run the tests on multiple environments using *tox*.
...
...
doc/index.rst
View file @
26bb7798
...
...
@@ -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
------------------
...
...
doc/introduction.rst
View file @
26bb7798
...
...
@@ -80,6 +80,9 @@ are a few different ways of approaching this.
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
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
...
...
@@ -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
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
----------------
For a more flexible solution, Flask-Admin lets you define access control rules
...
...
examples/auth-flask-login/app.py
View file @
26bb7798
...
...
@@ -32,12 +32,17 @@ class User(db.Model):
password
=
db
.
Column
(
db
.
String
(
64
))
# Flask-Login integration
# NOTE: is_authenticated, is_active, and is_anonymous
# are methods in Flask-Login < 0.3.0
@
property
def
is_authenticated
(
self
):
return
True
@
property
def
is_active
(
self
):
return
True
@
property
def
is_anonymous
(
self
):
return
False
...
...
examples/auth-mongoengine/app.py
View file @
26bb7798
...
...
@@ -27,12 +27,17 @@ class User(db.Document):
password
=
db
.
StringField
(
max_length
=
64
)
# Flask-Login integration
# NOTE: is_authenticated, is_active, and is_anonymous
# are methods in Flask-Login < 0.3.0
@
property
def
is_authenticated
(
self
):
return
True
@
property
def
is_active
(
self
):
return
True
@
property
def
is_anonymous
(
self
):
return
False
...
...
flask_admin/contrib/geoa/fields.py
View file @
26bb7798
...
...
@@ -8,10 +8,14 @@ from .widgets import LeafletWidget
class
GeoJSONField
(
JSONField
):
widget
=
LeafletWidget
()
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
)
self
.
web_srid
=
4326
self
.
srid
=
srid
...
...
flask_admin/contrib/geoa/form.py
View file @
26bb7798
...
...
@@ -9,4 +9,6 @@ class AdminModelConverter(SQLAAdminConverter):
field_args
[
'geometry_type'
]
=
column
.
type
.
geometry_type
field_args
[
'srid'
]
=
column
.
type
.
srid
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
)
flask_admin/contrib/geoa/typefmt.py
View file @
26bb7798
...
...
@@ -14,6 +14,8 @@ def geom_formatter(view, value):
"data-height"
:
70
,
"data-geometry-type"
:
to_shape
(
value
)
.
geom_type
,
"data-zoom"
:
15
,
"data-tile-layer-url"
:
view
.
tile_layer_url
,
"data-tile-layer-attribution"
:
view
.
tile_layer_attribution
})
if
value
.
srid
is
-
1
:
value
.
srid
=
4326
...
...
flask_admin/contrib/geoa/view.py
View file @
26bb7798
...
...
@@ -5,3 +5,5 @@ from flask_admin.contrib.geoa import form, typefmt
class
ModelView
(
SQLAModelView
):
model_form_converter
=
form
.
AdminModelConverter
column_type_formatters
=
typefmt
.
DEFAULT_FORMATTERS
tile_layer_url
=
None
tile_layer_attribution
=
None
flask_admin/contrib/geoa/widgets.py
View file @
26bb7798
...
...
@@ -23,7 +23,8 @@ class LeafletWidget(TextArea):
"""
def
__init__
(
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
.
height
=
height
self
.
center
=
center
...
...
@@ -31,6 +32,8 @@ class LeafletWidget(TextArea):
self
.
min_zoom
=
min_zoom
self
.
max_zoom
=
max_zoom
self
.
max_bounds
=
max_bounds
self
.
tile_layer_url
=
tile_layer_url
self
.
tile_layer_attribution
=
tile_layer_attribution
def
__call__
(
self
,
field
,
**
kwargs
):
kwargs
.
setdefault
(
'data-role'
,
self
.
data_role
)
...
...
@@ -38,6 +41,10 @@ class LeafletWidget(TextArea):
kwargs
.
setdefault
(
'data-geometry-type'
,
gtype
)
# 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
:
kwargs
[
"data-width"
]
=
self
.
width
if
"data-height"
not
in
kwargs
:
...
...
flask_admin/contrib/peewee/form.py
View file @
26bb7798
from
wtforms
import
fields
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
...
...
@@ -265,7 +270,10 @@ class InlineModelConverter(InlineModelConverterBase):
allow_pk
=
True
,
converter
=
converter
)
prop_name
=
reverse_field
.
related_name
try
:
prop_name
=
reverse_field
.
related_name
except
AttributeError
:
prop_name
=
reverse_field
.
backref
label
=
self
.
get_label
(
info
,
prop_name
)
...
...
flask_admin/contrib/peewee/view.py
View file @
26bb7798
...
...
@@ -234,14 +234,24 @@ class ModelView(BaseModelView):
raise
Exception
(
'Failed to find field for filter:
%
s'
%
name
)
# Check if field is in different model
if
attr
.
model_class
!=
self
.
model
:
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
attr
.
model_class
.
__name__
),
self
.
get_column_name
(
attr
.
name
))
else
:
if
not
isinstance
(
name
,
string_types
):
visible_name
=
self
.
get_column_name
(
attr
.
name
)
try
:
if
attr
.
model_class
!=
self
.
model
:
visible_name
=
'
%
s /
%
s'
%
(
self
.
get_column_name
(
attr
.
model_class
.
__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
)
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
:
visible_name
=
self
.
get_column_name
(
name
)
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__
flt
=
self
.
filter_converter
.
convert
(
type_name
,
...
...
@@ -307,12 +317,20 @@ class ModelView(BaseModelView):
return
create_ajax_loader
(
self
.
model
,
name
,
name
,
options
)
def
_handle_join
(
self
,
query
,
field
,
joins
):
if
field
.
model_class
!=
self
.
model
:
model_name
=
field
.
model_class
.
__name__
try
:
if
field
.
model_class
!=
self
.
model
:
model_name
=
field
.
model_class
.
__name__
if
model_name
not
in
joins
:
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_class
,
JOIN
.
LEFT_OUTER
)
joins
.
add
(
model_name
)
if
model_name
not
in
joins
:
query
=
query
.
join
(
field
.
model
,
JOIN
.
LEFT_OUTER
)
joins
.
add
(
model_name
)
return
query
...
...
@@ -321,8 +339,12 @@ class ModelView(BaseModelView):
field
=
getattr
(
self
.
model
,
sort_field
)
query
=
query
.
order_by
(
field
.
desc
()
if
sort_desc
else
field
.
asc
())
elif
isinstance
(
sort_field
,
Field
):
if
sort_field
.
model_class
!=
self
.
model
:
query
=
self
.
_handle_join
(
query
,
sort_field
,
joins
)
try
:
if
sort_field
.
model_class
!=
self
.
model
:
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
())
...
...
flask_admin/contrib/sqla/ajax.py
View file @
26bb7798
...
...
@@ -69,7 +69,7 @@ class QueryAjaxModelLoader(AjaxModelLoader):
query
=
query
.
filter
(
or_
(
*
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
))
if
self
.
order_by
:
...
...
flask_admin/contrib/sqla/fields.py
View file @
26bb7798
...
...
@@ -296,5 +296,5 @@ class InlineModelFormList(InlineFieldList):
def
get_pk_from_identity
(
obj
):
# 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
)
flask_admin/contrib/sqla/filters.py
View file @
26bb7798
...
...
@@ -373,7 +373,7 @@ class FilterConverter(filters.BaseFilterConverter):
@
filters
.
convert
(
'string'
,
'char'
,
'unicode'
,
'varchar'
,
'tinytext'
,
'text'
,
'mediumtext'
,
'longtext'
,
'unicodetext'
,
'nchar'
,
'nvarchar'
,
'ntext'
)
'nchar'
,
'nvarchar'
,
'ntext'
,
'citext'
)
def
conv_string
(
self
,
column
,
name
,
**
kwargs
):
return
[
f
(
column
,
name
,
**
kwargs
)
for
f
in
self
.
strings
]
...
...
flask_admin/contrib/sqla/form.py
View file @
26bb7798
import
warnings
from
enum
import
Enum
from
wtforms
import
fields
,
validators
from
sqlalchemy
import
Boolean
,
Column
...
...
@@ -9,7 +10,7 @@ from flask_admin.model.form import (converts, ModelConverterBase,
from
flask_admin.model.fields
import
AjaxSelectField
,
AjaxSelectMultipleField
from
flask_admin.model.helpers
import
prettify_name
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
.fields
import
(
QuerySelectField
,
QuerySelectMultipleField
,
...
...
@@ -154,7 +155,9 @@ class AdminModelConverter(ModelConverterBase):
if
len
(
prop
.
columns
)
>
1
:
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
))
return
None
...
...
@@ -279,6 +282,7 @@ class AdminModelConverter(ModelConverterBase):
accepted_values
.
append
(
None
)
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
)
...
...
flask_admin/contrib/sqla/view.py
View file @
26bb7798
...
...
@@ -420,7 +420,9 @@ class ModelView(BaseModelView):
if
len
(
p
.
columns
)
>
1
:
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
))
continue
...
...
@@ -763,6 +765,12 @@ class ModelView(BaseModelView):
if
p
.
mapper
.
class_
==
self
.
model
:
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'
]:
relations
.
add
(
p
.
key
)
...
...
flask_admin/model/base.py
View file @
26bb7798
...
...
@@ -263,6 +263,16 @@ class BaseModelView(BaseView, ActionsMixin):
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
)
"""
Dictionary of value type formatters to be used in the list view.
...
...
@@ -319,6 +329,18 @@ class BaseModelView(BaseView, ActionsMixin):
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
)
"""
Dictionary where key is column name and value is string to display.
...
...
@@ -889,6 +911,9 @@ class BaseModelView(BaseView, ActionsMixin):
if
self
.
column_formatters_export
is
None
:
self
.
column_formatters_export
=
self
.
column_formatters
if
self
.
column_formatters_detail
is
None
:
self
.
column_formatters_detail
=
self
.
column_formatters
# Type formatters
if
self
.
column_type_formatters
is
None
:
self
.
column_type_formatters
=
dict
(
typefmt
.
BASE_FORMATTERS
)
...
...
@@ -896,6 +921,9 @@ class BaseModelView(BaseView, ActionsMixin):
if
self
.
column_type_formatters_export
is
None
:
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
:
self
.
column_descriptions
=
dict
()
...
...
@@ -1518,12 +1546,15 @@ class BaseModelView(BaseView, ActionsMixin):
"""
try
:
self
.
on_model_change
(
form
,
model
,
is_created
)
except
TypeError
:
msg
=
(
'
%
s.on_model_change() now accepts third '
+
'parameter is_created. Please update your code'
)
%
self
.
model
warnings
.
warn
(
msg
)
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 '
+
'parameter is_created. Please update your code'
)
%
self
.
model
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
):
"""
...
...
@@ -1808,6 +1839,26 @@ class BaseModelView(BaseView, ActionsMixin):
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
):
"""
Returns the value to be displayed in export.
...
...
@@ -2108,7 +2159,7 @@ class BaseModelView(BaseView, ActionsMixin):
return
self
.
render
(
template
,
model
=
model
,
details_columns
=
self
.
_details_columns
,
get_value
=
self
.
get_
list
_value
,
get_value
=
self
.
get_
detail
_value
,
return_url
=
return_url
)
@
expose
(
'/delete/'
,
methods
=
(
'POST'
,))
...
...
flask_admin/model/typefmt.py
View file @
26bb7798
...
...
@@ -84,6 +84,13 @@ EXPORT_FORMATTERS = {
dict
:
dict_formatter
,
}
DETAIL_FORMATTERS
=
{
type
(
None
):
empty_formatter
,
list
:
list_formatter
,
dict
:
dict_formatter
,
}
if
Enum
is
not
None
:
BASE_FORMATTERS
[
Enum
]
=
enum_formatter
EXPORT_FORMATTERS
[
Enum
]
=
enum_formatter
DETAIL_FORMATTERS
[
Enum
]
=
enum_formatter
flask_admin/model/widgets.py
View file @
26bb7798
...
...
@@ -72,7 +72,8 @@ class XEditableWidget(object):
field inside of the FieldList (StringField, IntegerField, etc).
"""
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-url'
,
'./ajax/update/'
)
...
...
@@ -91,7 +92,7 @@ class XEditableWidget(object):
return
HTMLString
(
'<a
%
s>
%
s</a>'
%
(
html_params
(
**
kwargs
),
escape
(
kwargs
[
'data-value'
]
))
escape
(
display_value
))
)
def
get_kwargs
(
self
,
field
,
kwargs
):
...
...
@@ -104,7 +105,7 @@ class XEditableWidget(object):
kwargs
[
'data-type'
]
=
'textarea'
kwargs
[
'data-rows'
]
=
'5'
elif
field
.
type
==
'BooleanField'
:
kwargs
[
'data-type'
]
=
'select'
kwargs
[
'data-type'
]
=
'select
2
'
# data-source = dropdown options
kwargs
[
'data-source'
]
=
json
.
dumps
([
{
'value'
:
''
,
'text'
:
gettext
(
'No'
)},
...
...
@@ -112,7 +113,7 @@ class XEditableWidget(object):
])
kwargs
[
'data-role'
]
=
'x-editable-boolean'
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
]
# prepend a blank field to choices if allow_blank = True
...
...
@@ -144,7 +145,7 @@ class XEditableWidget(object):
elif
field
.
type
in
[
'QuerySelectField'
,
'ModelSelectField'
,
'QuerySelectMultipleField'
,
'KeyPropertyField'
]:
# QuerySelectField and ModelSelectField are for relations
kwargs
[
'data-type'
]
=
'select'
kwargs
[
'data-type'
]
=
'select
2
'
choices
=
[]
selected_ids
=
[]
...
...
@@ -162,12 +163,13 @@ class XEditableWidget(object):
kwargs
[
'data-source'
]
=
json
.
dumps
(
choices
)
if
field
.
type
==
'QuerySelectMultipleField'
:
kwargs
[
'data-type'
]
=
'select2'
kwargs
[
'data-role'
]
=
'x-editable-select2-multiple'
# must use id instead of text or prefilled values won't work
separator
=
getattr
(
field
,
'separator'
,
','
)
kwargs
[
'data-value'
]
=
separator
.
join
(
selected_ids
)
else
:
kwargs
[
'data-value'
]
=
text_type
(
selected_ids
[
0
])
else
:
raise
Exception
(
'Unsupported field type:
%
s'
%
(
type
(
field
),))
...
...
flask_admin/static/admin/css/bootstrap2/admin.css
View file @
26bb7798
...
...
@@ -28,7 +28,7 @@
/* List View - fix gap between actions and table */
.model-list
{
position
:
relative
;
position
:
static
;
margin-top
:
-1px
;
z-index
:
999
;
}
...
...
@@ -139,3 +139,7 @@ table.filters tr td {
*/
#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 @
26bb7798
...
...
@@ -28,7 +28,7 @@
/* List View - fix overlapping border between actions and table */
.model-list
{
position
:
relative
;
position
:
static
;
margin-top
:
-1px
;
z-index
:
999
;
}
...
...
flask_admin/static/admin/js/form.js
View file @
26bb7798
...
...
@@ -157,11 +157,19 @@
}
// set up tiles
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
,
{
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
}).
addTo
(
map
);
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
;
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>'
,
maxZoom
:
18
}).
addTo
(
map
);
}
// everything below here is to set up editing, so if we're not editable,
...
...
flask_admin/templates/bootstrap2/admin/base.html
View file @
26bb7798
...
...
@@ -33,7 +33,7 @@
</head>
<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-inner"
>
{% block brand %}
...
...
flask_admin/templates/bootstrap3/admin/base.html
View file @
26bb7798
...
...
@@ -35,7 +35,7 @@
</head>
<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"
>
<!-- Brand and toggle get grouped for better mobile display -->
<div
class=
"navbar-header"
>
...
...
flask_admin/tests/sqla/test_basic.py
View file @
26bb7798
...
...
@@ -2218,6 +2218,34 @@ def test_multipath_joins():
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
():
app
,
db
,
admin
=
setup
()
_
,
Model2
=
create_models
(
db
)
...
...
setup.py
View file @
26bb7798
...
...
@@ -36,9 +36,6 @@ install_requires = [
'wtforms'
]
if
sys
.
version_info
[:
2
]
<
(
2
,
7
):
install_requires
.
append
(
'ordereddict'
)
setup
(
name
=
'Flask-Admin'
,
version
=
grep
(
'__version__'
),
...
...
@@ -76,10 +73,10 @@ setup(
'Programming Language :: Python'
,
'Topic :: Software Development :: Libraries :: Python Modules'
,
'Programming Language :: Python :: 2.7'
,
'Programming Language :: Python :: 2.6'
,
'Programming Language :: Python :: 3.3'
,
'Programming Language :: Python :: 3.4'
,
'Programming Language :: Python :: 3.5'
,
'Programming Language :: Python :: 3.6'
,
],
test_suite
=
'nose.collector'
)
tox.ini
View file @
26bb7798
[tox]
envlist
=
py{2
6,27,33
,34,35,36}-WTForms{1,2}
py{2
7
,34,35,36}-WTForms{1,2}
flake8
docs-html
skipsdist
=
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