Skip to content

Commit 9960afc

Browse files
committed
[fix] Show warning message on deleting multiple active devices
1 parent 608cbbf commit 9960afc

File tree

5 files changed

+127
-60
lines changed

5 files changed

+127
-60
lines changed

openwisp_controller/config/admin.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import json
22
import logging
3+
from collections.abc import Iterable
34

45
import reversion
56
from django import forms
67
from django.conf import settings
78
from django.contrib import admin, messages
89
from django.contrib.admin import helpers
10+
from django.contrib.admin.actions import delete_selected
911
from django.contrib.admin.models import ADDITION, LogEntry
1012
from django.contrib.contenttypes.models import ContentType
1113
from django.core.exceptions import (
@@ -763,22 +765,42 @@ def deactivate_device(self, request, queryset):
763765
def activate_device(self, request, queryset):
764766
self._change_device_status(request, queryset, 'activate')
765767

766-
def get_deleted_objects(self, objs, request, *args, **kwargs):
767-
# Ensure that all selected devices can be deleted, i.e.
768-
# the device should be flagged as deactivated and if it has
769-
# a config object, it's status should be "deactivated".
770-
active_devices = []
771-
for obj in objs:
772-
if not self.has_delete_permission(request, obj):
773-
active_devices.append(obj)
774-
if active_devices:
775-
return (
776-
active_devices,
777-
{self.model._meta.verbose_name_plural: len(active_devices)},
778-
['active_devices'],
779-
[],
768+
@admin.action(description=delete_selected.short_description, permissions=['delete'])
769+
def delete_selected(self, request, queryset):
770+
response = delete_selected(self, request, queryset)
771+
if not response:
772+
return response
773+
if 'active_devices' in response.context_data.get('perms_lacking', {}):
774+
active_devices = []
775+
for device in queryset.iterator():
776+
if not device.is_deactivated() or (
777+
device._has_config() and not device.config.is_deactivated()
778+
):
779+
active_devices.append(self._get_device_path(device))
780+
response.context_data.update(
781+
{
782+
'active_devices': active_devices,
783+
'perms_lacking': set(),
784+
'title': _('Are you sure?'),
785+
}
780786
)
781-
return super().get_deleted_objects(objs, request, *args, **kwargs)
787+
return response
788+
789+
def get_deleted_objects(self, objs, request, *args, **kwargs):
790+
to_delete, model_count, perms_needed, protected = super().get_deleted_objects(
791+
objs, request, *args, **kwargs
792+
)
793+
if (
794+
isinstance(perms_needed, Iterable)
795+
and len(perms_needed) == 1
796+
and list(perms_needed)[0] == self.model._meta.verbose_name
797+
and objs.filter(_is_deactivated=False).exists()
798+
):
799+
if request.POST.get("post"):
800+
perms_needed = set()
801+
else:
802+
perms_needed = {'active_devices'}
803+
return to_delete, model_count, perms_needed, protected
782804

783805
def get_fields(self, request, obj=None):
784806
"""
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#deactivating-warning .warning p {
2+
margin-top: 0px;
3+
}
4+
#main ul.messagelist li.warning ul li {
5+
display: list-item;
6+
padding: 0px;
7+
background: inherit;
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
3+
(function ($) {
4+
$(document).ready(function () {
5+
$('#warning-ack').click(function (event) {
6+
event.preventDefault();
7+
$('#deactivating-warning').slideUp('fast');
8+
$('#delete-confirm-container').slideDown('fast');
9+
$('input[name="force_delete"]').val('true');
10+
});
11+
});
12+
})(django.jQuery);
Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
{% extends "admin/delete_confirmation.html" %}
2-
{% load i18n %}
2+
{% load i18n static %}
33

44
{% block extrastyle %}
55
{{ block.super }}
6-
<style>
7-
#deactivating-warning .warning p {
8-
margin-top: 0px;
9-
}
10-
</style>
6+
<link rel="stylesheet" type="text/css" href="{% static 'config/css/device-delete-confirmation.css' %}" />
117
{% endblock extrastyle %}
128

139
{% block delete_confirm %}
1410
{% if deactivating_warning %}
1511
<div id="deactivating-warning">
1612
<ul class="messagelist">
1713
<li class="warning">
18-
<p>{% trans "The device is still in the deactivating state, meaning its configuration is still present on the device. If you wish to remove the configuration from the device, please wait until the config status changes to deactivated. Proceeding will delete the device from OpenWISP without ensuring its configuration has been removed." %}</p>
14+
<p>{% trans 'The device is still in the deactivating state, meaning its configuration is still present on the device. If you wish to remove the configuration from the device, please wait until the config status changes to "deactivated". Proceeding will delete the device from OpenWISP without ensuring its configuration has been removed.' %}</p>
1915
<form>
2016
<input type="submit" class="button danger-btn" id="warning-ack"
21-
value="{% trans "I understand the risks, delete the device" %}">
22-
<a class="button cancel-link">No, take me back</a>
17+
value="{% trans 'I understand the risks, delete the device' %}">
18+
<a class="button cancel-link">{% trans 'No, take me back' %}</a>
2319
</form>
2420
</li>
2521
</ul>
@@ -46,16 +42,5 @@ <h2>{% translate "Objects" %}</h2>
4642

4743
{% block footer %}
4844
{{ block.super }}
49-
<script>
50-
(function ($) {
51-
$(document).ready(function () {
52-
$('#warning-ack').click(function (event) {
53-
event.preventDefault();
54-
$('#deactivating-warning').slideUp('fast');
55-
$('#delete-confirm-container').slideDown('fast');
56-
$('input[name="force_delete"]').val('true');
57-
});
58-
})
59-
})(django.jQuery);
60-
</script>
45+
<script type="text/javascript" src="{% static 'config/js/device-delete-confirmation.js' %}"></script>
6146
{% endblock %}
Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,76 @@
11
{% extends "admin/delete_selected_confirmation.html" %}
22
{% load i18n l10n admin_urls static %}
33

4+
{% block extrastyle %}
5+
{{ block.super }}
6+
<link rel="stylesheet" type="text/css" href="{% static 'config/css/device-delete-confirmation.css' %}" />
7+
{% endblock extrastyle %}
8+
49
{% block content %}
510
{% if perms_lacking %}
6-
{% if perms_lacking|first == 'active_devices' %}
7-
<p>{% blocktranslate %}You have selected the following active device{{ model_count | pluralize }} to delete:{% endblocktranslate %}</p>
8-
<ul>{{ deletable_objects|first|unordered_list }}</ul>
9-
<p>{% blocktrans %}It is required to flag the device as deactivated before deleting the device. If the device has configuration, then wait till the configuration status changes to "deactivated" before deleting the device.{% endblocktrans %}</p>
10-
{% else %}
11-
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktranslate %}</p>
12-
<ul>{{ perms_lacking|unordered_list }}</ul>
13-
{% endif %}
11+
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktranslate %}</p>
12+
<ul>{{ perms_lacking|unordered_list }}</ul>
1413
{% elif protected %}
1514
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktranslate %}</p>
1615
<ul>{{ protected|unordered_list }}</ul>
1716
{% else %}
18-
<p>{% blocktranslate %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktranslate %}</p>
19-
{% include "admin/includes/object_delete_summary.html" %}
20-
<h2>{% translate "Objects" %}</h2>
21-
{% for deletable_object in deletable_objects %}
22-
<ul>{{ deletable_object|unordered_list }}</ul>
23-
{% endfor %}
24-
<form method="post">{% csrf_token %}
25-
<div>
26-
{% for obj in queryset %}
27-
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
28-
{% endfor %}
29-
<input type="hidden" name="action" value="delete_selected">
30-
<input type="hidden" name="post" value="yes">
31-
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
32-
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
17+
{% if active_devices %}
18+
<div id="deactivating-warning">
19+
<ul class="messagelist">
20+
<li class="warning">
21+
<p>
22+
{% blocktrans count counter=active_devices|length %}
23+
The following device you selected for deletion is not deactivated
24+
(either it is active or its configuration status is still "deactivating"):
25+
{% plural %}
26+
The following devices you selected for deletion are not deactivated
27+
(either they are active or their configuration status is still "deactivating"):
28+
{% endblocktrans %}
29+
</p>
30+
<ul>{{ active_devices|unordered_list }}</ul>
31+
<p>
32+
{% blocktranslate count counter=active_devices|length %}
33+
If you wish to remove the configuration from the device, please wait until its
34+
configuration status changes to "deactivated". Proceeding will delete the device
35+
from OpenWISP without ensuring its configuration has been removed.
36+
{% plural %}
37+
If you wish to remove the configurations from the devices, please wait until their
38+
configuration status change to "deactivated." Proceeding will delete the devices
39+
from OpenWISP without ensuring their configurations have been removed.
40+
{% endblocktranslate %}
41+
</p>
42+
<form>
43+
<input type="submit" class="button danger-btn" id="warning-ack"
44+
value="{% trans "I understand the risks, delete the device" %}">
45+
<a class="button cancel-link">No, take me back</a>
46+
</form>
47+
</li>
48+
</ul>
49+
</div>
50+
{% endif %}
51+
<div id="delete-confirm-container" {% if active_devices %}style="display:none;"{% endif %}>
52+
<p>{% blocktranslate %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktranslate %}</p>
53+
{% include "admin/includes/object_delete_summary.html" %}
54+
<h2>{% translate "Objects" %}</h2>
55+
{% for deletable_object in deletable_objects %}
56+
<ul>{{ deletable_object|unordered_list }}</ul>
57+
{% endfor %}
58+
<form method="post">{% csrf_token %}
59+
<div>
60+
{% for obj in queryset %}
61+
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
62+
{% endfor %}
63+
<input type="hidden" name="action" value="delete_selected">
64+
<input type="hidden" name="post" value="yes">
65+
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
66+
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
67+
</div>
68+
</form>
3369
</div>
34-
</form>
3570
{% endif %}
3671
{% endblock %}
72+
73+
{% block footer %}
74+
{{ block.super }}
75+
<script type="text/javascript" src="{% static 'config/js/device-delete-confirmation.js' %}"></script>
76+
{% endblock %}

0 commit comments

Comments
 (0)