Skip to content

Commit

Permalink
[fix] Show warning message on deleting multiple active devices
Browse files Browse the repository at this point in the history
  • Loading branch information
pandafy committed Jan 6, 2025
1 parent 608cbbf commit 9960afc
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 60 deletions.
52 changes: 37 additions & 15 deletions openwisp_controller/config/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import json
import logging
from collections.abc import Iterable

import reversion
from django import forms
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.admin import helpers
from django.contrib.admin.actions import delete_selected
from django.contrib.admin.models import ADDITION, LogEntry
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import (
Expand Down Expand Up @@ -763,22 +765,42 @@ def deactivate_device(self, request, queryset):
def activate_device(self, request, queryset):
self._change_device_status(request, queryset, 'activate')

def get_deleted_objects(self, objs, request, *args, **kwargs):
# Ensure that all selected devices can be deleted, i.e.
# the device should be flagged as deactivated and if it has
# a config object, it's status should be "deactivated".
active_devices = []
for obj in objs:
if not self.has_delete_permission(request, obj):
active_devices.append(obj)
if active_devices:
return (
active_devices,
{self.model._meta.verbose_name_plural: len(active_devices)},
['active_devices'],
[],
@admin.action(description=delete_selected.short_description, permissions=['delete'])
def delete_selected(self, request, queryset):
response = delete_selected(self, request, queryset)
if not response:
return response
if 'active_devices' in response.context_data.get('perms_lacking', {}):
active_devices = []
for device in queryset.iterator():
if not device.is_deactivated() or (
device._has_config() and not device.config.is_deactivated()
):
active_devices.append(self._get_device_path(device))
response.context_data.update(
{
'active_devices': active_devices,
'perms_lacking': set(),
'title': _('Are you sure?'),
}
)
return super().get_deleted_objects(objs, request, *args, **kwargs)
return response

def get_deleted_objects(self, objs, request, *args, **kwargs):
to_delete, model_count, perms_needed, protected = super().get_deleted_objects(
objs, request, *args, **kwargs
)
if (
isinstance(perms_needed, Iterable)
and len(perms_needed) == 1
and list(perms_needed)[0] == self.model._meta.verbose_name
and objs.filter(_is_deactivated=False).exists()
):
if request.POST.get("post"):
perms_needed = set()
else:
perms_needed = {'active_devices'}
return to_delete, model_count, perms_needed, protected

def get_fields(self, request, obj=None):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#deactivating-warning .warning p {
margin-top: 0px;
}
#main ul.messagelist li.warning ul li {
display: list-item;
padding: 0px;
background: inherit;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

(function ($) {
$(document).ready(function () {
$('#warning-ack').click(function (event) {
event.preventDefault();
$('#deactivating-warning').slideUp('fast');
$('#delete-confirm-container').slideDown('fast');
$('input[name="force_delete"]').val('true');
});
});
})(django.jQuery);
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
{% extends "admin/delete_confirmation.html" %}
{% load i18n %}
{% load i18n static %}

{% block extrastyle %}
{{ block.super }}
<style>
#deactivating-warning .warning p {
margin-top: 0px;
}
</style>
<link rel="stylesheet" type="text/css" href="{% static 'config/css/device-delete-confirmation.css' %}" />
{% endblock extrastyle %}

{% block delete_confirm %}
{% if deactivating_warning %}
<div id="deactivating-warning">
<ul class="messagelist">
<li class="warning">
<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>
<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>
<form>
<input type="submit" class="button danger-btn" id="warning-ack"
value="{% trans "I understand the risks, delete the device" %}">
<a class="button cancel-link">No, take me back</a>
value="{% trans 'I understand the risks, delete the device' %}">
<a class="button cancel-link">{% trans 'No, take me back' %}</a>
</form>
</li>
</ul>
Expand All @@ -46,16 +42,5 @@ <h2>{% translate "Objects" %}</h2>

{% block footer %}
{{ block.super }}
<script>
(function ($) {
$(document).ready(function () {
$('#warning-ack').click(function (event) {
event.preventDefault();
$('#deactivating-warning').slideUp('fast');
$('#delete-confirm-container').slideDown('fast');
$('input[name="force_delete"]').val('true');
});
})
})(django.jQuery);
</script>
<script type="text/javascript" src="{% static 'config/js/device-delete-confirmation.js' %}"></script>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,36 +1,76 @@
{% extends "admin/delete_selected_confirmation.html" %}
{% load i18n l10n admin_urls static %}

{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'config/css/device-delete-confirmation.css' %}" />
{% endblock extrastyle %}

{% block content %}
{% if perms_lacking %}
{% if perms_lacking|first == 'active_devices' %}
<p>{% blocktranslate %}You have selected the following active device{{ model_count | pluralize }} to delete:{% endblocktranslate %}</p>
<ul>{{ deletable_objects|first|unordered_list }}</ul>
<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>
{% else %}
<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>
<ul>{{ perms_lacking|unordered_list }}</ul>
{% endif %}
<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>
<ul>{{ perms_lacking|unordered_list }}</ul>
{% elif protected %}
<p>{% blocktranslate %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktranslate %}</p>
<ul>{{ protected|unordered_list }}</ul>
{% else %}
<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>
{% include "admin/includes/object_delete_summary.html" %}
<h2>{% translate "Objects" %}</h2>
{% for deletable_object in deletable_objects %}
<ul>{{ deletable_object|unordered_list }}</ul>
{% endfor %}
<form method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
{% endfor %}
<input type="hidden" name="action" value="delete_selected">
<input type="hidden" name="post" value="yes">
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
{% if active_devices %}
<div id="deactivating-warning">
<ul class="messagelist">
<li class="warning">
<p>
{% blocktrans count counter=active_devices|length %}
The following device you selected for deletion is not deactivated
(either it is active or its configuration status is still "deactivating"):
{% plural %}
The following devices you selected for deletion are not deactivated
(either they are active or their configuration status is still "deactivating"):
{% endblocktrans %}
</p>
<ul>{{ active_devices|unordered_list }}</ul>
<p>
{% blocktranslate count counter=active_devices|length %}
If you wish to remove the configuration from the device, please wait until its
configuration status changes to "deactivated". Proceeding will delete the device
from OpenWISP without ensuring its configuration has been removed.
{% plural %}
If you wish to remove the configurations from the devices, please wait until their
configuration status change to "deactivated." Proceeding will delete the devices
from OpenWISP without ensuring their configurations have been removed.
{% endblocktranslate %}
</p>
<form>
<input type="submit" class="button danger-btn" id="warning-ack"
value="{% trans "I understand the risks, delete the device" %}">
<a class="button cancel-link">No, take me back</a>
</form>
</li>
</ul>
</div>
{% endif %}
<div id="delete-confirm-container" {% if active_devices %}style="display:none;"{% endif %}>
<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>
{% include "admin/includes/object_delete_summary.html" %}
<h2>{% translate "Objects" %}</h2>
{% for deletable_object in deletable_objects %}
<ul>{{ deletable_object|unordered_list }}</ul>
{% endfor %}
<form method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}">
{% endfor %}
<input type="hidden" name="action" value="delete_selected">
<input type="hidden" name="post" value="yes">
<input type="submit" value="{% translate 'Yes, I’m sure' %}">
<a href="#" class="button cancel-link">{% translate "No, take me back" %}</a>
</div>
</form>
</div>
</form>
{% endif %}
{% endblock %}

{% block footer %}
{{ block.super }}
<script type="text/javascript" src="{% static 'config/js/device-delete-confirmation.js' %}"></script>
{% endblock %}

0 comments on commit 9960afc

Please sign in to comment.