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
add7e01f
Commit
add7e01f
authored
Mar 27, 2016
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Row actions implementation and minor list view refactoring
parent
1e209b0a
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
323 additions
and
67 deletions
+323
-67
base.py
flask_admin/model/base.py
+52
-1
template.py
flask_admin/model/template.py
+118
-0
list.html
flask_admin/templates/bootstrap2/admin/model/list.html
+6
-34
row_actions.html
...k_admin/templates/bootstrap2/admin/model/row_actions.html
+38
-0
list.html
flask_admin/templates/bootstrap3/admin/model/list.html
+4
-32
row_actions.html
...k_admin/templates/bootstrap3/admin/model/row_actions.html
+38
-0
test_model.py
flask_admin/tests/test_model.py
+67
-0
No files found.
flask_admin/model/base.py
View file @
add7e01f
...
...
@@ -21,7 +21,7 @@ from flask_admin.babel import gettext
from
flask_admin.base
import
BaseView
,
expose
from
flask_admin.form
import
BaseForm
,
FormOpts
,
rules
from
flask_admin.model
import
filters
,
typefmt
from
flask_admin.model
import
filters
,
typefmt
,
template
from
flask_admin.actions
import
ActionsMixin
from
flask_admin.helpers
import
(
get_form_data
,
validate_form_on_submit
,
get_redirect_target
,
flash_errors
)
...
...
@@ -459,6 +459,31 @@ class BaseModelView(BaseView, ActionsMixin):
actions endpoints are accessible.
"""
column_list_row_actions
=
None
"""
List of row actions (instances of :class:`~flask_admin.model.template.BaseListRowAction`).
If empty or not set, Flask-Admin will generate default actions (view, edit, etc).
Behaviour of this field is affected by `columns_override_default_row_actions` field
value.
For example::
from flask_admin.model.template import EndpointLinkRowAction
class MyModelView(BaseModelView):
column_list_row_actions = [
EndpointLinkRowAction('.some_view', 'fa fa-cross'),
EndpointLinkRowAction('.another_view', 'fa fa-eye-open'),
]
"""
column_override_default_row_actions
=
False
"""
If set to `True`, will disable default row actions (view, edit, etc) and will only use
items from `column_list_row_actions`.
"""
simple_list_pager
=
False
"""
Enable or disable simple list pager.
...
...
@@ -925,6 +950,31 @@ class BaseModelView(BaseView, ActionsMixin):
return
[(
c
,
self
.
get_column_name
(
c
))
for
c
in
columns
]
def
get_list_row_actions
(
self
):
"""
Return list of row action objects, each is instance of :class:`~flask_admin.model.template.BaseListRowAction`
"""
# TODO: Maybe pre-calculate
actions
=
[]
if
not
self
.
column_override_default_row_actions
:
if
self
.
can_view_details
:
if
self
.
details_modal
:
actions
.
append
(
template
.
ViewPopupRowAction
())
else
:
actions
.
append
(
template
.
ViewRowAction
())
if
self
.
can_edit
:
if
self
.
edit_modal
:
actions
.
append
(
template
.
EditPopupRowAction
())
else
:
actions
.
append
(
template
.
EditRowAction
())
if
self
.
can_delete
:
actions
.
append
(
template
.
DeleteRowAction
())
return
actions
+
(
self
.
column_list_row_actions
or
[])
def
get_details_columns
(
self
):
"""
Returns a list of the model field names in the details view. If
...
...
@@ -1815,6 +1865,7 @@ class BaseModelView(BaseView, ActionsMixin):
list_columns
=
self
.
_list_columns
,
sortable_columns
=
self
.
_sortable_columns
,
editable_columns
=
self
.
column_editable_list
,
list_row_actions
=
self
.
get_list_row_actions
(),
# Pagination
count
=
count
,
...
...
flask_admin/model/template.py
View file @
add7e01f
from
jinja2
import
contextfunction
from
flask_admin._compat
import
string_types
class
BaseListRowAction
(
object
):
def
__init__
(
self
,
title
=
None
):
self
.
title
=
title
def
render
(
self
,
context
,
row_id
,
row
):
raise
NotImplementedError
()
@
contextfunction
def
render_ctx
(
self
,
context
,
row_id
,
row
):
return
self
.
render
(
context
,
row_id
,
row
)
def
_resolve_symbol
(
self
,
context
,
symbol
):
if
'.'
in
symbol
:
parts
=
symbol
.
split
(
'.'
)
m
=
context
.
resolve
(
parts
[
0
])
return
reduce
(
getattr
,
parts
[
1
:],
m
)
else
:
return
context
.
resolve
(
symbol
)
class
LinkRowAction
(
BaseListRowAction
):
def
__init__
(
self
,
icon_class
,
url
,
title
=
None
):
super
(
LinkRowAction
,
self
)
.
__init__
(
title
=
title
)
self
.
url
=
url
self
.
icon_class
=
icon_class
def
render
(
self
,
context
,
row_id
,
row
):
m
=
self
.
_resolve_symbol
(
context
,
'row_actions.link'
)
if
isinstance
(
self
.
url
,
string_types
):
url
=
self
.
url
.
format
(
row_id
=
row_id
)
else
:
url
=
self
.
url
(
self
,
row_id
,
row
)
return
m
(
self
,
url
)
class
EndpointLinkRowAction
(
BaseListRowAction
):
def
__init__
(
self
,
icon_class
,
endpoint
,
title
=
None
,
id_arg
=
'id'
,
url_args
=
None
):
super
(
EndpointLinkRowAction
,
self
)
.
__init__
(
title
=
title
)
self
.
icon_class
=
icon_class
self
.
endpoint
=
endpoint
self
.
id_arg
=
id_arg
self
.
url_args
=
url_args
def
render
(
self
,
context
,
row_id
,
row
):
m
=
self
.
_resolve_symbol
(
context
,
'row_actions.link'
)
get_url
=
self
.
_resolve_symbol
(
context
,
'get_url'
)
kwargs
=
dict
(
self
.
url_args
)
if
self
.
url_args
else
{}
kwargs
[
self
.
id_arg
]
=
row_id
url
=
get_url
(
self
.
endpoint
,
**
kwargs
)
return
m
(
self
,
url
)
class
TemplateLinkRowAction
(
BaseListRowAction
):
def
__init__
(
self
,
template_name
,
title
=
None
):
super
(
TemplateLinkRowAction
,
self
)
.
__init__
(
title
=
title
)
self
.
template_name
=
template_name
def
render
(
self
,
context
,
row_id
,
row
):
m
=
self
.
_resolve_symbol
(
context
,
self
.
template_name
)
return
m
(
self
,
row_id
,
row
)
class
ViewRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
ViewRowAction
,
self
)
.
__init__
(
# TODO: Localize
'row_actions.view_row'
,
'View row'
)
class
ViewPopupRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
ViewPopupRowAction
,
self
)
.
__init__
(
# TODO: Localize
'row_actions.view_row_popup'
,
'View row'
)
class
EditRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
EditRowAction
,
self
)
.
__init__
(
# TODO: Localize
'row_actions.edit_row'
,
'Edit row'
)
class
EditPopupRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
EditPopupRowAction
,
self
)
.
__init__
(
# TODO: Localize
'row_actions.edit_row_popup'
,
'Edit row'
)
class
DeleteRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
# TODO: Pass form
super
(
DeleteRowAction
,
self
)
.
__init__
(
# TODO: Localize
'row_actions.delete_row'
,
'Edit row'
)
# Macro helper
def
macro
(
name
):
'''
Jinja2 macro list column formatter.
...
...
@@ -14,3 +131,4 @@ def macro(name):
return
m
(
model
=
model
,
column
=
column
)
return
inner
flask_admin/templates/bootstrap2/admin/model/list.html
View file @
add7e01f
...
...
@@ -3,6 +3,7 @@
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/model/row_actions.html' as row_actions with context %}
{% block head %}
{{ super() }}
...
...
@@ -116,40 +117,11 @@
{% block list_row_actions_column scoped %}
{% if admin_view.column_display_actions %}
<td
class=
"list-buttons-column"
>
{% block list_row_actions scoped %}
{%- if admin_view.can_view_details -%}
{%- if admin_view.details_modal -%}
{{ lib.add_modal_button(url=get_url('.details_view', id=get_pk_value(row), url=return_url, modal=True), title=_gettext('View Record'), content='
<span
class=
"fa fa-eye glyphicon icon-eye-open"
></span>
') }}
{% else %}
<a
class=
"icon"
href=
"{{ get_url('.details_view', id=get_pk_value(row), url=return_url) }}"
title=
"{{ _gettext('View Record') }}"
>
<span
class=
"fa fa-eye icon-eye-open"
></span>
</a>
{%- endif -%}
{%- endif -%}
{%- if admin_view.can_edit -%}
{%- if admin_view.edit_modal -%}
{{ lib.add_modal_button(url=get_url('.edit_view', id=get_pk_value(row), url=return_url, modal=True), title=_gettext('Edit Record'), content='
<i
class=
"fa fa-pencil icon-pencil"
></i>
') }}
{% else %}
<a
class=
"icon"
href=
"{{ get_url('.edit_view', id=get_pk_value(row), url=return_url) }}"
title=
"{{ _gettext('Edit Record') }}"
>
<i
class=
"fa fa-pencil icon-pencil"
></i>
</a>
{%- endif -%}
{%- endif -%}
{%- if admin_view.can_delete -%}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view') }}"
>
{{ delete_form.id(value=get_pk_value(row)) }}
{{ delete_form.url(value=return_url) }}
{% if delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% elif csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
{% endif %}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete this record?') }}');"
title=
"{{ _gettext('Delete record') }}"
>
<i
class=
"fa fa-trash icon-trash"
></i>
</button>
</form>
{%- endif -%}
{% endblock %}
{% block list_row_actions scoped %}
{% for action in list_row_actions %}
{{ action.render_ctx(get_pk_value(row), row) }}
{% endfor %}
{% endblock %}
</td>
{%- endif -%}
{% endblock %}
...
...
flask_admin/templates/bootstrap2/admin/model/row_actions.html
0 → 100644
View file @
add7e01f
{% import 'admin/lib.html' as lib with context %}
{% macro link(action, url, icon_class=None) %}
<a
class=
"icon"
href=
"{{ url }}"
title=
"{{ action.title or '' }}"
>
<span
class=
"{{ icon_class or action.icon_class }}"
></span>
</a>
{% endmacro %}
{% macro view_row(action, row_id, row) %}
{{ link(action, get_url('.details_view', id=row_id, url=return_url), 'fa fa-eye glyphicon glyphicon-eye-open') }}
{% endmacro %}
{% macro view_row_popup(action, row_id, row) %}
{{ lib.add_modal_button(url=get_url('.details_view', id=row_id, url=return_url, modal=True), title=action.title, content='
<span
class=
"fa fa-eye glyphicon icon-eye-open"
></span>
') }}
{% endmacro %}
{% macro edit_row(action, row_id, row) %}
{{ link(action, get_url('.edit_view', id=row_id, url=return_url), 'fa fa-pencil glyphicon glyphicon-pencil') }}
{% endmacro %}
{% macro edit_row_popup(action, row_id, row) %}
{{ lib.add_modal_button(url=get_url('.edit_view', id=row_id, url=return_url, modal=True), title=action.title, content='
<span
class=
"fa fa-pencil glyphicon icon-pencil"
></span>
') }}
{% endmacro %}
{% macro delete_row(action, row_id, row) %}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view') }}"
>
{{ delete_form.id(value=get_pk_value(row)) }}
{{ delete_form.url(value=return_url) }}
{% if delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% elif csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
{% endif %}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete this record?') }}');"
title=
"Delete record"
>
<span
class=
"fa fa-trash glyphicon icon-trash"
></span>
</button>
</form>
{% endmacro %}
flask_admin/templates/bootstrap3/admin/model/list.html
View file @
add7e01f
...
...
@@ -3,6 +3,7 @@
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/model/row_actions.html' as row_actions with context %}
{% block head %}
{{ super() }}
...
...
@@ -116,38 +117,9 @@
{% if admin_view.column_display_actions %}
<td
class=
"list-buttons-column"
>
{% block list_row_actions scoped %}
{%- if admin_view.can_view_details -%}
{%- if admin_view.details_modal -%}
{{ lib.add_modal_button(url=get_url('.details_view', id=get_pk_value(row), url=return_url, modal=True), title=_gettext('View Record'), content='
<span
class=
"fa fa-eye glyphicon glyphicon-eye-open"
></span>
') }}
{% else %}
<a
class=
"icon"
href=
"{{ get_url('.details_view', id=get_pk_value(row), url=return_url) }}"
title=
"{{ _gettext('View Record') }}"
>
<span
class=
"fa fa-eye glyphicon glyphicon-eye-open"
></span>
</a>
{%- endif -%}
{%- endif -%}
{%- if admin_view.can_edit -%}
{%- if admin_view.edit_modal -%}
{{ lib.add_modal_button(url=get_url('.edit_view', id=get_pk_value(row), url=return_url, modal=True), title=_gettext('Edit Record'), content='
<span
class=
"fa fa-pencil glyphicon glyphicon-pencil"
></span>
') }}
{% else %}
<a
class=
"icon"
href=
"{{ get_url('.edit_view', id=get_pk_value(row), url=return_url) }}"
title=
"{{ _gettext('Edit Record') }}"
>
<span
class=
"fa fa-pencil glyphicon glyphicon-pencil"
></span>
</a>
{%- endif -%}
{%- endif -%}
{%- if admin_view.can_delete -%}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view') }}"
>
{{ delete_form.id(value=get_pk_value(row)) }}
{{ delete_form.url(value=return_url) }}
{% if delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% elif csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
{% endif %}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete this record?') }}');"
title=
"Delete record"
>
<span
class=
"fa fa-trash glyphicon glyphicon-trash"
></span>
</button>
</form>
{%- endif -%}
{% for action in list_row_actions %}
{{ action.render_ctx(get_pk_value(row), row) }}
{% endfor %}
{% endblock %}
</td>
{%- endif -%}
...
...
flask_admin/templates/bootstrap3/admin/model/row_actions.html
0 → 100644
View file @
add7e01f
{% import 'admin/lib.html' as lib with context %}
{% macro link(action, url, icon_class=None) %}
<a
class=
"icon"
href=
"{{ url }}"
title=
"{{ action.title or '' }}"
>
<span
class=
"{{ icon_class or action.icon_class }}"
></span>
</a>
{% endmacro %}
{% macro view_row(action, row_id, row) %}
{{ link(action, get_url('.details_view', id=row_id, url=return_url), 'fa fa-eye glyphicon glyphicon-eye-open') }}
{% endmacro %}
{% macro view_row_popup(action, row_id, row) %}
{{ lib.add_modal_button(url=get_url('.details_view', id=row_id, url=return_url, modal=True), title=action.title, content='
<span
class=
"fa fa-eye glyphicon glyphicon-eye-open"
></span>
') }}
{% endmacro %}
{% macro edit_row(action, row_id, row) %}
{{ link(action, get_url('.edit_view', id=row_id, url=return_url), 'fa fa-pencil glyphicon glyphicon-pencil') }}
{% endmacro %}
{% macro edit_row_popup(action, row_id, row) %}
{{ lib.add_modal_button(url=get_url('.edit_view', id=row_id, url=return_url, modal=True), title=action.title, content='
<span
class=
"fa fa-pencil glyphicon glyphicon-pencil"
></span>
') }}
{% endmacro %}
{% macro delete_row(action, row_id, row) %}
<form
class=
"icon"
method=
"POST"
action=
"{{ get_url('.delete_view') }}"
>
{{ delete_form.id(value=get_pk_value(row)) }}
{{ delete_form.url(value=return_url) }}
{% if delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% elif csrf_token %}
<input
type=
"hidden"
name=
"csrf_token"
value=
"{{ csrf_token() }}"
/>
{% endif %}
<button
onclick=
"return confirm('{{ _gettext('Are you sure you want to delete this record?') }}');"
title=
"Delete record"
>
<span
class=
"fa fa-trash glyphicon glyphicon-trash"
></span>
</button>
</form>
{% endmacro %}
flask_admin/tests/test_model.py
View file @
add7e01f
...
...
@@ -726,3 +726,70 @@ def test_export_csv():
rv
=
client
.
get
(
'/admin/macro_exception_macro_override/export/csv/'
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
eq_
(
rv
.
status_code
,
500
)
def
test_list_row_actions
():
app
,
admin
=
setup
()
client
=
app
.
test_client
()
from
flask_admin.model
import
template
# Test default actions
view
=
MockModelView
(
Model
,
endpoint
=
'test'
)
admin
.
add_view
(
view
)
actions
=
view
.
get_list_row_actions
()
ok_
(
isinstance
(
actions
[
0
],
template
.
EditRowAction
))
ok_
(
isinstance
(
actions
[
1
],
template
.
DeleteRowAction
))
rv
=
client
.
get
(
'/admin/test/'
)
eq_
(
rv
.
status_code
,
200
)
# Test default actions
view
=
MockModelView
(
Model
,
endpoint
=
'test1'
,
can_edit
=
False
,
can_delete
=
False
,
can_view_details
=
True
)
admin
.
add_view
(
view
)
actions
=
view
.
get_list_row_actions
()
eq_
(
len
(
actions
),
1
)
ok_
(
isinstance
(
actions
[
0
],
template
.
ViewRowAction
))
rv
=
client
.
get
(
'/admin/test1/'
)
eq_
(
rv
.
status_code
,
200
)
# Test popups
view
=
MockModelView
(
Model
,
endpoint
=
'test2'
,
can_view_details
=
True
,
details_modal
=
True
,
edit_modal
=
True
)
admin
.
add_view
(
view
)
actions
=
view
.
get_list_row_actions
()
ok_
(
isinstance
(
actions
[
0
],
template
.
ViewPopupRowAction
))
ok_
(
isinstance
(
actions
[
1
],
template
.
EditPopupRowAction
))
ok_
(
isinstance
(
actions
[
2
],
template
.
DeleteRowAction
))
rv
=
client
.
get
(
'/admin/test2/'
)
eq_
(
rv
.
status_code
,
200
)
# Test custom views
view
=
MockModelView
(
Model
,
endpoint
=
'test3'
,
column_list_row_actions
=
[
LinkRowAction
(
'glyphicon glyphicon-off'
,
'http://localhost/?id={row_id}'
),
EndpointLinkRowAction
(
'glyphicon glyphicon-test'
,
'.test1'
)
])
admin
.
add_view
(
view
)
actions
=
view
.
get_list_row_actions
()
ok_
(
isinstance
(
actions
[
0
],
template
.
EditRowAction
))
ok_
(
isinstance
(
actions
[
1
],
template
.
DeleteRowAction
))
ok_
(
isinstance
(
actions
[
2
],
template
.
LinkRowAction
))
ok_
(
isinstance
(
actions
[
3
],
template
.
EndpointLinkRowAction
))
rv
=
client
.
get
(
'/admin/test3/'
)
eq_
(
rv
.
status_code
,
200
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
ok_
(
'glyphicon-off'
in
data
)
ok_
(
'http://localhost/?id='
in
data
)
ok_
(
'glyphicon-test'
in
data
)
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