From 9960afc76ca6361a247abe46df121668ec13106d Mon Sep 17 00:00:00 2001 From: Gagan Deep Date: Mon, 6 Jan 2025 21:48:58 +0530 Subject: [PATCH] [fix] Show warning message on deleting multiple active devices --- openwisp_controller/config/admin.py | 52 +++++++---- .../config/css/device-delete-confirmation.css | 8 ++ .../config/js/device-delete-confirmation.js | 12 +++ .../config/device/delete_confirmation.html | 27 ++---- .../device/delete_selected_confirmation.html | 88 ++++++++++++++----- 5 files changed, 127 insertions(+), 60 deletions(-) create mode 100644 openwisp_controller/config/static/config/css/device-delete-confirmation.css create mode 100644 openwisp_controller/config/static/config/js/device-delete-confirmation.js diff --git a/openwisp_controller/config/admin.py b/openwisp_controller/config/admin.py index 0ada5c859..339560b8c 100644 --- a/openwisp_controller/config/admin.py +++ b/openwisp_controller/config/admin.py @@ -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 ( @@ -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): """ diff --git a/openwisp_controller/config/static/config/css/device-delete-confirmation.css b/openwisp_controller/config/static/config/css/device-delete-confirmation.css new file mode 100644 index 000000000..3f23394b6 --- /dev/null +++ b/openwisp_controller/config/static/config/css/device-delete-confirmation.css @@ -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; +} diff --git a/openwisp_controller/config/static/config/js/device-delete-confirmation.js b/openwisp_controller/config/static/config/js/device-delete-confirmation.js new file mode 100644 index 000000000..7918a64d3 --- /dev/null +++ b/openwisp_controller/config/static/config/js/device-delete-confirmation.js @@ -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); diff --git a/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html b/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html index e5fe5dd3e..6830ae019 100644 --- a/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html +++ b/openwisp_controller/config/templates/admin/config/device/delete_confirmation.html @@ -1,13 +1,9 @@ {% extends "admin/delete_confirmation.html" %} -{% load i18n %} +{% load i18n static %} {% block extrastyle %} {{ block.super }} - + {% endblock extrastyle %} {% block delete_confirm %} @@ -15,11 +11,11 @@
@@ -46,16 +42,5 @@

{% translate "Objects" %}

{% block footer %} {{ block.super }} - + {% endblock %} diff --git a/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html b/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html index a66130f86..382c862e5 100644 --- a/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html +++ b/openwisp_controller/config/templates/admin/config/device/delete_selected_confirmation.html @@ -1,36 +1,76 @@ {% extends "admin/delete_selected_confirmation.html" %} {% load i18n l10n admin_urls static %} +{% block extrastyle %} +{{ block.super }} + +{% endblock extrastyle %} + {% block content %} {% if perms_lacking %} - {% if perms_lacking|first == 'active_devices' %} -

{% blocktranslate %}You have selected the following active device{{ model_count | pluralize }} to delete:{% endblocktranslate %}

- -

{% 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 %}

- {% else %} -

{% 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 %}

- - {% endif %} +

{% 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 %}

+ {% elif protected %}

{% blocktranslate %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktranslate %}

{% else %} -

{% 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 %}

- {% include "admin/includes/object_delete_summary.html" %} -

{% translate "Objects" %}

- {% for deletable_object in deletable_objects %} - - {% endfor %} -
{% csrf_token %} -
- {% for obj in queryset %} - - {% endfor %} - - - - {% translate "No, take me back" %} + {% if active_devices %} +
+
    +
  • +

    + {% 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 %} +

    +
      {{ active_devices|unordered_list }}
    +

    + {% 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 %} +

    + + + No, take me back +
  • + +
+
+ {% endif %} +
+

{% 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 %}

+ {% include "admin/includes/object_delete_summary.html" %} +

{% translate "Objects" %}

+ {% for deletable_object in deletable_objects %} +
    {{ deletable_object|unordered_list }}
+ {% endfor %} +
{% csrf_token %} +
+ {% for obj in queryset %} + + {% endfor %} + + + + {% translate "No, take me back" %} +
+
- {% endif %} {% endblock %} + +{% block footer %} +{{ block.super }} + +{% endblock %}