Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/sqla/admin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ class PostAdmin(ModelView):
column_default_sort = ("date", True)
create_modal = True
edit_modal = True
can_view_details = True
details_modal = True

column_sortable_list = [
"id",
"title",
Expand Down
20 changes: 19 additions & 1 deletion flask_admin/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2431,8 +2431,19 @@ def edit_view(self) -> T_RESPONSE | str:
else:
template = self.edit_template

if self.can_delete:
delete_form = self.delete_form()
else:
delete_form = None

return self.render(
template, model=model, form=form, form_opts=form_opts, return_url=return_url
template,
model=model,
form=form,
form_opts=form_opts,
return_url=return_url,
delete_form=delete_form,
get_pk_value=self.get_pk_value,
)

@expose("/details/")
Expand Down Expand Up @@ -2460,12 +2471,19 @@ def details_view(self) -> T_RESPONSE | str:
else:
template = self.details_template

if self.can_delete:
delete_form = self.delete_form()
else:
delete_form = None

return self.render(
template,
model=model,
details_columns=self._details_columns,
get_value=self.get_detail_value,
return_url=return_url,
delete_form=delete_form,
get_pk_value=self.get_pk_value,
)

@expose("/delete/", methods=("POST",))
Expand Down
72 changes: 65 additions & 7 deletions flask_admin/templates/bootstrap4/admin/lib.html
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ <h3>{{ text }}</h3>
{% endmacro %}

{% macro form_tag(form=None, action=None) %}


<div class="d-flex col justify-content-end mt-3 mb-0">
{{ render_delete_form() }}
</div>


<form action="{{ action or '' }}" method="POST" role="form" class="admin-form" enctype="multipart/form-data">
<fieldset>
{{ caller() }}
Expand All @@ -213,13 +220,13 @@ <h3>{{ text }}</h3>

{% macro render_form_buttons(cancel_url, extra=None, is_modal=False) %}
{% if is_modal %}
<input type="submit" class="btn btn-primary" value="{{ _gettext('Save') }}" />
{% if extra %}
{{ extra }}
{% endif %}
{% if cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-danger" role="button" {% if is_modal %}data-dismiss="modal"{% endif %}>{{ _gettext('Cancel') }}</a>
{% endif %}
<input type="submit" class="btn btn-primary" value="{{ _gettext('Save') }}" />
{% if extra %}
{{ extra }}
{% endif %}
{% if cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-danger" role="button" {% if is_modal %}data-dismiss="modal"{% endif %}>{{ _gettext('Cancel') }}</a>
{% endif %}
{% else %}
<hr>
<div class="form-group">
Expand All @@ -236,6 +243,57 @@ <h3>{{ text }}</h3>
{% endif %}
{% endmacro %}


{% macro render_delete_form() -%}

{% if delete_form is defined %}

<form class="icon" method="POST" action="{{ get_url('.delete_view') }}">

{{ delete_form.id(value=get_pk_value(model)) }}
{{ delete_form.url(value=return_url) }}
{% if delete_form.csrf_token is defined and delete_form.csrf_token %}
{{ delete_form.csrf_token }}
{% elif csrf_token is defined and csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% endif %}

<button class="btn btn-outline-danger" id="btn-delete"
data-msg="{{ _gettext('Are you sure you want to delete this record?') }}"
title="{{ _gettext('Delete Record') }}">
<span class="fa fa-trash glyphicon glyphicon-trash"></span>
{{ _gettext('Delete') }}
</button>
</form>

<script {{ admin_csp_nonce_attribute }} >

function deleteButtonHandler() {
$('#btn-delete').on('click', function (e)
{
const msg = $(this).data('msg');
return faHelpers.safeConfirm(msg);
});
}

document.addEventListener('DOMContentLoaded', function()
{
//console.log("DOM is fully loaded and parsed");
deleteButtonHandler();
});

$('#fa_modal_window').on('shown.bs.modal', function (e)
{
//console.log("Modal shown");
deleteButtonHandler();
});

</script>

{% endif %}

{% endmacro %}

{% macro render_form(form, cancel_url, extra=None, form_opts=None, action=None, is_modal=False) -%}
{% call form_tag(action=action) %}
{{ render_form_fields(form, form_opts=form_opts) }}
Expand Down
18 changes: 12 additions & 6 deletions flask_admin/templates/bootstrap4/admin/model/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@
</ul>
{% endblock %}

{% block details_search %}
<div class="form-inline fa_filter_container col-lg-6">
<label for="fa_filter">{{ _gettext('Filter') }}</label>
<input id="fa_filter" type="text" class="ml-3 form-control">
</div>
{% block details_actions %}
<div class="form-inline fa_filter_container d-flex justify-content-between">
{% block details_search %}
<div class="form-inline">
<label for="fa_filter">{{ _gettext('Filter') }}</label>
<input id="fa_filter" type="text" class="ml-3 form-control">
</div>
{% endblock %}
{% block details_delete %}
{{ lib.render_delete_form() }}
{% endblock %}
</div>
{% endblock %}

{% block details_table %}
<table class="table table-hover table-bordered searchable">
{% for c, name in details_columns %}
Expand Down
16 changes: 12 additions & 4 deletions flask_admin/templates/bootstrap4/admin/model/modals/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ <h5 class="modal-title">{{ _gettext('View Record') + ' #' + request.args.get('id
</div>

<div class="modal-body">
{% block details_search %}
<div class="form-inline fa_filter_container col-lg-6">
<label for="fa_filter">{{ _gettext('Filter') }}</label>
<input id="fa_filter" type="text" class="ml-3 form-control">

{% block details_actions %}
<div class="form-inline fa_filter_container col d-flex justify-content-between">
{% block details_search %}
<div class="form-inline">
<label for="fa_filter">{{ _gettext('Filter') }}</label>
<input id="fa_filter" type="text" class="ml-3 form-control">
</div>
{% endblock %}
{% block details_delete %}
{{ lib.render_delete_form() }}
{% endblock %}
</div>
{% endblock %}

Expand Down
47 changes: 47 additions & 0 deletions flask_admin/tests/sqla/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3310,3 +3310,50 @@ class MyModelView(CustomModelView):
assert match_page_title_and_icon(
data, "Local Files", '<i class="fa fa-folder"></i>'
)


@pytest.mark.parametrize(
"with_delete, modal", [(True, True), (True, False), (False, True), (False, False)]
)
def test_del_btn_in_edit_and_details(app, db, admin, with_delete, modal):
with app.app_context():
Model1, Model2 = create_models(db)
db.session.add_all(
[
Model1("record-1"),
Model1("record-2"),
]
)
db.session.commit()

class MyModelView(CustomModelView):
can_edit = True
can_view_details = True

can_delete = with_delete
edit_modal = modal
details_modal = modal

# test column_list with a list of strings
view = MyModelView(
Model1,
db.session,
name="Without Modal",
)
admin.add_view(view)

client = app.test_client()

rv = client.get("/admin/model1/details/?id=2")
data = rv.data.decode("utf-8")
if with_delete:
assert "btn-delete" in data
else:
assert "btn-delete" not in data

rv = client.get("/admin/model1/edit/?id=2")
data = rv.data.decode("utf-8")
if with_delete:
assert "btn-delete" in data
else:
assert "btn-delete" not in data