diff --git a/openwisp_controller/geo/admin.py b/openwisp_controller/geo/admin.py index cb283c6b2..5c82dae27 100644 --- a/openwisp_controller/geo/admin.py +++ b/openwisp_controller/geo/admin.py @@ -56,6 +56,38 @@ def _get_floorplan_instance(self): floorplan.organization_id = self.data.get('organization') return floorplan + def _get_initial_location(self): + """ + Returns initial location for the device. + + Attempts to get the initial location by calling parent class method. + If location is not found (e.g. when recovering a deleted device), + returns None instead of raising Location.DoesNotExist. + + Returns: + Location instance or None if location does not exist + """ + try: + return super()._get_initial_location() + except Location.DoesNotExist: + return None + + def _get_initial_floorplan(self): + """ + Returns initial floorplan for the device. + + Attempts to get the initial floorplan by calling parent class method. + If floorplan is not found (e.g. when recovering a deleted device), + returns None instead of raising FloorPlan.DoesNotExist. + + Returns: + FloorPlan instance or None if floorplan does not exist + """ + try: + return super()._get_initial_floorplan() + except FloorPlan.DoesNotExist: + return None + class LocationForm(AbstractLocationForm): class Meta(AbstractLocationForm.Meta): @@ -83,6 +115,7 @@ class DeviceLocationInline( admin.site.register(FloorPlan, FloorPlanAdmin) admin.site.register(Location, LocationAdmin) +reversion.register(model=Location) class DeviceLocationFilter(admin.SimpleListFilter): @@ -105,5 +138,5 @@ def queryset(self, request, queryset): DeviceAdminExportable.inlines.insert(1, DeviceLocationInline) DeviceAdminExportable.list_filter.append(DeviceLocationFilter) DeviceAdminExportable.resource_class = GeoDeviceResource -reversion.register(model=DeviceLocation, follow=['device']) +reversion.register(model=DeviceLocation, follow=['device', 'location']) DeviceAdminExportable.add_reversion_following(follow=['devicelocation']) diff --git a/openwisp_controller/tests/test_selenium.py b/openwisp_controller/tests/test_selenium.py index fd451114f..13b52f5de 100644 --- a/openwisp_controller/tests/test_selenium.py +++ b/openwisp_controller/tests/test_selenium.py @@ -18,16 +18,18 @@ Device = load_model('config', 'Device') DeviceConnection = load_model('connection', 'DeviceConnection') Location = load_model('geo', 'Location') +FloorPlan = load_model('geo', 'FloorPlan') DeviceLocation = load_model('geo', 'DeviceLocation') @tag('selenium_tests') -class TestDeviceConnectionInlineAdmin( +class TestDevice( CreateConnectionsMixin, TestGeoMixin, SeleniumTestMixin, StaticLiveServerTestCase ): config_app_label = 'config' location_model = Location object_location_model = DeviceLocation + floorplan_model = FloorPlan def setUp(self): self.admin = self._create_admin( @@ -40,10 +42,14 @@ def test_restoring_deleted_device(self, *args): self._create_credentials(auto_add=True, organization=org) config = self._create_config(organization=org) device = config.device + + location = self._create_location(organization=org, type='indoor') + floorplan = self._create_floorplan( + location=location, + ) self._create_object_location( - location=self._create_location( - organization=org, - ), + location=location, + floorplan=floorplan, content_object=device, ) self.assertEqual(device.deviceconnection_set.count(), 1) @@ -59,9 +65,13 @@ def test_restoring_deleted_device(self, *args): self.web_driver.find_element( by=By.XPATH, value='//*[@id="content"]/form/div/input[2]' ).click() + # Delete location object + location.delete() self.assertEqual(Device.objects.count(), 0) self.assertEqual(DeviceConnection.objects.count(), 0) self.assertEqual(DeviceLocation.objects.count(), 0) + self.assertEqual(self.location_model.objects.count(), 0) + self.assertEqual(self.floorplan_model.objects.count(), 0) version_obj = Version.objects.get_deleted(model=Device).first() @@ -94,3 +104,8 @@ def test_restoring_deleted_device(self, *args): self.assertEqual(Device.objects.count(), 1) self.assertEqual(DeviceConnection.objects.count(), 1) self.assertEqual(DeviceLocation.objects.count(), 1) + self.assertEqual(self.location_model.objects.count(), 1) + # The FloorPlan object is not recoverable because deleting it + # also removes the associated image from the filesystem, + # which cannot be restored. + self.assertEqual(self.floorplan_model.objects.count(), 0)