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
00e1c558
Commit
00e1c558
authored
Mar 27, 2016
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1242 from flask-admin/list_row_actions
List row actions
parents
bc0f282f
9fd7637d
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
309 additions
and
67 deletions
+309
-67
base.py
flask_admin/model/base.py
+43
-1
template.py
flask_admin/model/template.py
+113
-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 @
00e1c558
...
@@ -21,7 +21,7 @@ from flask_admin.babel import gettext
...
@@ -21,7 +21,7 @@ from flask_admin.babel import gettext
from
flask_admin.base
import
BaseView
,
expose
from
flask_admin.base
import
BaseView
,
expose
from
flask_admin.form
import
BaseForm
,
FormOpts
,
rules
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.actions
import
ActionsMixin
from
flask_admin.helpers
import
(
get_form_data
,
validate_form_on_submit
,
from
flask_admin.helpers
import
(
get_form_data
,
validate_form_on_submit
,
get_redirect_target
,
flash_errors
)
get_redirect_target
,
flash_errors
)
...
@@ -459,6 +459,24 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -459,6 +459,24 @@ class BaseModelView(BaseView, ActionsMixin):
actions endpoints are accessible.
actions endpoints are accessible.
"""
"""
column_extra_row_actions
=
None
"""
List of row actions (instances of :class:`~flask_admin.model.template.BaseListRowAction`).
Flask-Admin will generate standard per-row actions (edit, delete, etc)
and will append custom actions from this list right after them.
For example::
from flask_admin.model.template import EndpointLinkRowAction, LinkRowAction
class MyModelView(BaseModelView):
column_extra_row_actions = [
LinkRowAction('glyphicon glyphicon-off', 'http://direct.link/?id={row_id}'),
EndpointLinkRowAction('glyphicon glyphicon-test', 'my_view.index_view')
]
"""
simple_list_pager
=
False
simple_list_pager
=
False
"""
"""
Enable or disable simple list pager.
Enable or disable simple list pager.
...
@@ -925,6 +943,29 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -925,6 +943,29 @@ class BaseModelView(BaseView, ActionsMixin):
return
[(
c
,
self
.
get_column_name
(
c
))
for
c
in
columns
]
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`
"""
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_extra_row_actions
or
[])
def
get_details_columns
(
self
):
def
get_details_columns
(
self
):
"""
"""
Returns a list of the model field names in the details view. If
Returns a list of the model field names in the details view. If
...
@@ -1815,6 +1856,7 @@ class BaseModelView(BaseView, ActionsMixin):
...
@@ -1815,6 +1856,7 @@ class BaseModelView(BaseView, ActionsMixin):
list_columns
=
self
.
_list_columns
,
list_columns
=
self
.
_list_columns
,
sortable_columns
=
self
.
_sortable_columns
,
sortable_columns
=
self
.
_sortable_columns
,
editable_columns
=
self
.
column_editable_list
,
editable_columns
=
self
.
column_editable_list
,
list_row_actions
=
self
.
get_list_row_actions
(),
# Pagination
# Pagination
count
=
count
,
count
=
count
,
...
...
flask_admin/model/template.py
View file @
00e1c558
from
jinja2
import
contextfunction
from
flask_admin._compat
import
string_types
,
reduce
from
flask_admin.babel
import
gettext
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__
(
'row_actions.view_row'
,
gettext
(
'View row'
))
class
ViewPopupRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
ViewPopupRowAction
,
self
)
.
__init__
(
'row_actions.view_row_popup'
,
gettext
(
'View row'
))
class
EditRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
EditRowAction
,
self
)
.
__init__
(
'row_actions.edit_row'
,
gettext
(
'Edit row'
))
class
EditPopupRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
EditPopupRowAction
,
self
)
.
__init__
(
'row_actions.edit_row_popup'
,
gettext
(
'Edit row'
))
class
DeleteRowAction
(
TemplateLinkRowAction
):
def
__init__
(
self
):
super
(
DeleteRowAction
,
self
)
.
__init__
(
'row_actions.delete_row'
,
gettext
(
'Edit row'
))
# Macro helper
def
macro
(
name
):
def
macro
(
name
):
'''
'''
Jinja2 macro list column formatter.
Jinja2 macro list column formatter.
...
@@ -14,3 +126,4 @@ def macro(name):
...
@@ -14,3 +126,4 @@ def macro(name):
return
m
(
model
=
model
,
column
=
column
)
return
m
(
model
=
model
,
column
=
column
)
return
inner
return
inner
flask_admin/templates/bootstrap2/admin/model/list.html
View file @
00e1c558
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/model/row_actions.html' as row_actions with context %}
{% block head %}
{% block head %}
{{ super() }}
{{ super() }}
...
@@ -116,40 +117,11 @@
...
@@ -116,40 +117,11 @@
{% block list_row_actions_column scoped %}
{% block list_row_actions_column scoped %}
{% if admin_view.column_display_actions %}
{% if admin_view.column_display_actions %}
<td
class=
"list-buttons-column"
>
<td
class=
"list-buttons-column"
>
{% block list_row_actions scoped %}
{% block list_row_actions scoped %}
{%- if admin_view.can_view_details -%}
{% for action in list_row_actions %}
{%- if admin_view.details_modal -%}
{{ action.render_ctx(get_pk_value(row), row) }}
{{ 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>
') }}
{% endfor %}
{% else %}
{% endblock %}
<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 %}
</td>
</td>
{%- endif -%}
{%- endif -%}
{% endblock %}
{% endblock %}
...
...
flask_admin/templates/bootstrap2/admin/model/row_actions.html
0 → 100644
View file @
00e1c558
{% 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 icon-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 icon-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 @
00e1c558
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/static.html' as admin_static with context%}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/model/layout.html' as model_layout with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/actions.html' as actionlib with context %}
{% import 'admin/model/row_actions.html' as row_actions with context %}
{% block head %}
{% block head %}
{{ super() }}
{{ super() }}
...
@@ -116,38 +117,9 @@
...
@@ -116,38 +117,9 @@
{% if admin_view.column_display_actions %}
{% if admin_view.column_display_actions %}
<td
class=
"list-buttons-column"
>
<td
class=
"list-buttons-column"
>
{% block list_row_actions scoped %}
{% block list_row_actions scoped %}
{%- if admin_view.can_view_details -%}
{% for action in list_row_actions %}
{%- if admin_view.details_modal -%}
{{ action.render_ctx(get_pk_value(row), row) }}
{{ 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>
') }}
{% endfor %}
{% 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 -%}
{% endblock %}
{% endblock %}
</td>
</td>
{%- endif -%}
{%- endif -%}
...
...
flask_admin/templates/bootstrap3/admin/model/row_actions.html
0 → 100644
View file @
00e1c558
{% 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 @
00e1c558
...
@@ -726,3 +726,70 @@ def test_export_csv():
...
@@ -726,3 +726,70 @@ def test_export_csv():
rv
=
client
.
get
(
'/admin/macro_exception_macro_override/export/csv/'
)
rv
=
client
.
get
(
'/admin/macro_exception_macro_override/export/csv/'
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
data
=
rv
.
data
.
decode
(
'utf-8'
)
eq_
(
rv
.
status_code
,
500
)
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_extra_row_actions
=
[
template
.
LinkRowAction
(
'glyphicon glyphicon-off'
,
'http://localhost/?id={row_id}'
),
template
.
EndpointLinkRowAction
(
'glyphicon glyphicon-test'
,
'test1.index_view'
)
])
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