Skip to content

Commit 1aa3e54

Browse files
pandafynemesifier
andauthored
[change] Added support for device deactivation feature #251
- Do not allow changing DeviceFirmwareImage of a deactivated device - Disable API operations on deactivated devices Closes #251 --------- Co-authored-by: Federico Capoano <f.capoano@openwisp.io>
1 parent c065323 commit 1aa3e54

File tree

7 files changed

+68
-7
lines changed

7 files changed

+68
-7
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
pip install -U pip wheel setuptools
5555
pip install -U -r requirements-test.txt
5656
pip install -U -e .
57-
pip install ${{ matrix.django-version }}
57+
pip install -U ${{ matrix.django-version }}
5858
sudo npm install -g jshint stylelint
5959
6060
- name: QA checks

openwisp_firmware_upgrader/admin.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from django.utils.translation import gettext_lazy as _
2121
from reversion.admin import VersionAdmin
2222

23-
from openwisp_controller.config.admin import DeviceAdmin
23+
from openwisp_controller.config.admin import DeactivatedDeviceReadOnlyMixin, DeviceAdmin
2424
from openwisp_users.multitenancy import MultitenantAdminMixin, MultitenantOrgFilter
2525
from openwisp_utils.admin import ReadOnlyAdmin, TimeReadonlyAdminMixin
2626

@@ -402,7 +402,9 @@ def get_form_kwargs(self, index):
402402
return kwargs
403403

404404

405-
class DeviceFirmwareInline(MultitenantAdminMixin, admin.StackedInline):
405+
class DeviceFirmwareInline(
406+
MultitenantAdminMixin, DeactivatedDeviceReadOnlyMixin, admin.StackedInline
407+
):
406408
model = DeviceFirmware
407409
formset = DeviceFormSet
408410
form = DeviceFirmwareForm

openwisp_firmware_upgrader/api/views.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.http import Http404
44
from django_filters.rest_framework import DjangoFilterBackend
55
from rest_framework import filters, generics, pagination, serializers, status
6-
from rest_framework.exceptions import NotFound
6+
from rest_framework.exceptions import NotFound, PermissionDenied
77
from rest_framework.request import clone_request
88
from rest_framework.response import Response
99
from rest_framework.utils.serializer_helpers import ReturnDict
@@ -257,6 +257,12 @@ class DeviceFirmwareDetailView(
257257
lookup_url_kwarg = 'pk'
258258
organization_field = 'device__organization'
259259

260+
def get_object(self):
261+
obj = super().get_object()
262+
if self.request.method not in ('GET', 'HEAD') and obj.device.is_deactivated():
263+
raise PermissionDenied
264+
return obj
265+
260266
def get_serializer_context(self):
261267
context = super().get_serializer_context()
262268
context.update({'device_id': self.kwargs['pk']})

openwisp_firmware_upgrader/tests/test_admin.py

+30
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,36 @@ def test_device_firmware_upgrade_without_device_connection(
343343
mocked_func.assert_not_called()
344344
self.assertEqual(response.status_code, 200)
345345

346+
def test_deactivated_firmware_image_inline(self):
347+
self._login()
348+
device = self._create_config(organization=self._get_org()).device
349+
device.deactivate()
350+
response = self.client.get(
351+
reverse('admin:config_device_change', args=[device.id])
352+
)
353+
# Check that it is not possible to add a DeviceFirmwareImage to a
354+
# deactivated device in the admin interface.
355+
self.assertContains(
356+
response,
357+
'<input type="hidden" name="devicefirmware-MAX_NUM_FORMS"'
358+
' value="0" id="id_devicefirmware-MAX_NUM_FORMS">',
359+
)
360+
self._create_device_firmware(device=device)
361+
response = self.client.get(
362+
reverse('admin:config_device_change', args=[device.id])
363+
)
364+
# Ensure that a deactivated device's existing DeviceFirmwareImage
365+
# is displayed as readonly in the admin interface.
366+
self.assertContains(
367+
response,
368+
'<div class="readonly">Test Category v0.1:'
369+
' TP-Link WDR4300 v1 (OpenWrt 19.07 and later)</div>',
370+
)
371+
self.assertNotContains(
372+
response,
373+
'<select name="devicefirmware-0-image" id="id_devicefirmware-0-image">',
374+
)
375+
346376

347377
_mock_updrade = 'openwisp_firmware_upgrader.upgraders.openwrt.OpenWrt.upgrade'
348378
_mock_connect = 'openwisp_controller.connection.models.DeviceConnection.connect'

openwisp_firmware_upgrader/tests/test_api.py

+21
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,27 @@ def test_device_firmware_detail_400(self):
926926
self.assertEqual(r.status_code, 400)
927927
self.assertIn('Invalid pk', r.json()['image'][0])
928928

929+
def test_deactivated_device(self):
930+
device_fw = self._create_device_firmware()
931+
device_fw.device.deactivate()
932+
url = reverse('upgrader:api_devicefirmware_detail', args=[device_fw.device.pk])
933+
934+
with self.subTest('Test retrieving DeviceFirmwareImage'):
935+
response = self.client.get(url)
936+
self.assertEqual(response.status_code, 200)
937+
938+
with self.subTest('Test updating DeviceFirmwareImage'):
939+
response = self.client.put(
940+
url,
941+
data={'image': device_fw.image.pk},
942+
content_type='application/json',
943+
)
944+
self.assertEqual(response.status_code, 403)
945+
946+
with self.subTest('Test deleting DeviceFirmwareImage'):
947+
response = self.client.delete(url)
948+
self.assertEqual(response.status_code, 403)
949+
929950
def test_device_firmware_detail_delete(self):
930951
device_fw = self._create_device_firmware()
931952
self.assertEqual(DeviceFirmware.objects.count(), 1)

openwisp_firmware_upgrader/tests/test_models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -432,15 +432,15 @@ def test_device_fw_not_created_on_device_connection_save(self):
432432
self.assertEqual(DeviceConnection.objects.count(), 1)
433433
self.assertEqual(Device.objects.count(), 1)
434434
self.assertEqual(DeviceFirmware.objects.count(), 0)
435-
d1.delete()
435+
d1.delete(check_deactivated=False)
436436
Credentials.objects.all().delete()
437437

438438
with self.subTest("Device doesn't define model"):
439439
d1 = self._create_device_with_connection(os=self.os, model='')
440440
self.assertEqual(DeviceConnection.objects.count(), 1)
441441
self.assertEqual(Device.objects.count(), 1)
442442
self.assertEqual(DeviceFirmware.objects.count(), 0)
443-
d1.delete()
443+
d1.delete(check_deactivated=False)
444444
Credentials.objects.all().delete()
445445

446446
build1.os = None

openwisp_firmware_upgrader/tests/test_selenium.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,16 @@ def test_restoring_deleted_device(self):
8080
device = self._create_device(
8181
os=self.os, model=image.boards[0], organization=org
8282
)
83-
self._create_config(device=device)
83+
config = self._create_config(device=device)
8484
self.assertEqual(Device.objects.count(), 1)
8585
self.assertEqual(DeviceConnection.objects.count(), 1)
8686
self.assertEqual(DeviceFirmware.objects.count(), 1)
8787

8888
call_command('createinitialrevisions')
8989

9090
self.login()
91+
device.deactivate()
92+
config.set_status_deactivated()
9193
# Delete the device
9294
self.open(
9395
reverse(f'admin:{self.config_app_label}_device_delete', args=[device.id])

0 commit comments

Comments
 (0)