Skip to content

Commit 1754156

Browse files
authored
Merge branch 'openwisp:master' into feature/828-api-indoor-map-coordinates
2 parents 611f852 + 129a42f commit 1754156

39 files changed

+3233
-2291
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ jobs:
7474
sudo apt -qq -y install sqlite3 gdal-bin libproj-dev \
7575
libgeos-dev libspatialite-dev spatialite-bin \
7676
libsqlite3-mod-spatialite
77-
sudo npm install -g jshint stylelint
77+
sudo npm install -g prettier
7878
pip install -U pip wheel setuptools
7979
pip install -U -r requirements-test.txt
8080
pip install -U -e .

.github/workflows/pypi.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ jobs:
2929
- name: Build package
3030
run: python -m build
3131
- name: Publish package distributions to PyPI
32-
uses: pypa/[email protected].2
32+
uses: pypa/[email protected].4

.jshintrc

-19
This file was deleted.

.jshintignore .prettierignore

File renamed without changes.

.stylelintrc.json

-20
This file was deleted.

CHANGES.rst

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ Version 1.2.0 [Unreleased]
66

77
Work in progress.
88

9+
Version 1.1.1 [2025-01-31]
10+
--------------------------
11+
12+
Bugfixes
13+
~~~~~~~~
14+
15+
- Fixed `recovering deleted device with related location
16+
<https://github.com/openwisp/openwisp-controller/issues/936>`__.
17+
- Fixed `deleting device with “deactivating” config status
18+
<https://github.com/openwisp/openwisp-controller/issues/949>`__.
19+
- Fixed `conversion of MAC address to uppercase format
20+
<https://github.com/openwisp/openwisp-controller/issues/922>`__ with the
21+
openwisp-config agent.
22+
- Fixed `updating templates with invalid configurations to prevent
23+
ValidationError due to cache invalidation mechanism
24+
<https://github.com/openwisp/openwisp-controller/pull/948>`__.
25+
926
Version 1.1.0 [2024-11-22]
1027
--------------------------
1128

README.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
openwisp-controller
22
===================
33

4-
.. image:: https://github.com/openwisp/openwisp-controller/workflows/OpenWISP%20Controller%20CI%20Build/badge.svg?branch=master
5-
:target: https://github.com/openwisp/openwisp-controller/actions?query=workflow%3A%22OpenWISP+Controller+CI+Build%22
4+
.. image:: https://github.com/openwisp/openwisp-controller/actions/workflows/ci.yml/badge.svg
5+
:target: https://github.com/openwisp/openwisp-controller/actions/workflows/ci.yml
66
:alt: CI build status
77

88
.. image:: https://coveralls.io/repos/openwisp/openwisp-controller/badge.svg

docs/developer/installation.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Install development dependencies:
6464
6565
pip install -e .
6666
pip install -r requirements-test.txt
67-
sudo npm install -g jshint stylelint
67+
sudo npm install -g prettier
6868
6969
Install WebDriver for Chromium for your browser version from
7070
https://chromedriver.chromium.org/home and Extract ``chromedriver`` to one

docs/user/device-config-status.rst

+8
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,11 @@ scheduled to be removed from the device.
3636
The device has been deactivated. The configuration applied through
3737
OpenWISP has been removed, and any other operation to manage the device
3838
will be prevented or rejected.
39+
40+
.. note::
41+
42+
If a device becomes unreachable (e.g., lost, stolen, or
43+
decommissioned) before it can be properly deactivated, you can still
44+
force the deletion from OpenWISP by hitting the delete button in the
45+
device detail page after having deactivated the device or by using the
46+
bulk delete action from the device list page.

openwisp_controller/config/admin.py

+48-17
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 (
@@ -566,8 +568,6 @@ def has_delete_permission(self, request, obj=None):
566568
perm = super().has_delete_permission(request)
567569
if not obj:
568570
return perm
569-
if obj._has_config():
570-
perm = perm and obj.config.is_deactivated()
571571
return perm and obj.is_deactivated()
572572

573573
def save_form(self, request, form, change):
@@ -765,22 +765,42 @@ def deactivate_device(self, request, queryset):
765765
def activate_device(self, request, queryset):
766766
self._change_device_status(request, queryset, 'activate')
767767

768-
def get_deleted_objects(self, objs, request, *args, **kwargs):
769-
# Ensure that all selected devices can be deleted, i.e.
770-
# the device should be flagged as deactivated and if it has
771-
# a config object, it's status should be "deactivated".
772-
active_devices = []
773-
for obj in objs:
774-
if not self.has_delete_permission(request, obj):
775-
active_devices.append(obj)
776-
if active_devices:
777-
return (
778-
active_devices,
779-
{self.model._meta.verbose_name_plural: len(active_devices)},
780-
['active_devices'],
781-
[],
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+
}
782786
)
783-
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
784804

785805
def get_fields(self, request, obj=None):
786806
"""
@@ -900,6 +920,17 @@ def recover_view(self, request, version_id, extra_context=None):
900920
request._recover_view = True
901921
return super().recover_view(request, version_id, extra_context)
902922

923+
def delete_view(self, request, object_id, extra_context=None):
924+
extra_context = extra_context or {}
925+
obj = self.get_object(request, object_id)
926+
if obj and obj._has_config() and not obj.config.is_deactivated():
927+
extra_context['deactivating_warning'] = True
928+
return super().delete_view(request, object_id, extra_context)
929+
930+
def delete_model(self, request, obj):
931+
force_delete = request.POST.get('force_delete') == 'true'
932+
obj.delete(check_deactivated=not force_delete)
933+
903934
def get_inlines(self, request, obj):
904935
inlines = super().get_inlines(request, obj)
905936
# this only makes sense in existing devices

openwisp_controller/config/api/views.py

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ class DeviceDetailView(ProtectedAPIMixin, RetrieveUpdateDestroyAPIView):
107107
queryset = Device.objects.select_related('config', 'group', 'organization')
108108
permission_classes = ProtectedAPIMixin.permission_classes + (DevicePermission,)
109109

110+
def perform_destroy(self, instance):
111+
force_deletion = self.request.query_params.get('force', None) == 'true'
112+
instance.delete(check_deactivated=(not force_deletion))
113+
110114

111115
class DeviceActivateView(ProtectedAPIMixin, GenericAPIView):
112116
serializer_class = serializers.Serializer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
}
9+
ul.messagelist li {
10+
font-size: unset;
11+
}

0 commit comments

Comments
 (0)