Skip to content

Commit 6aa2a1f

Browse files
authored
[fix] Fixed recovering deleted device with related location #936
Fixes #936
1 parent 211bebc commit 6aa2a1f

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

openwisp_controller/geo/admin.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,38 @@ def _get_floorplan_instance(self):
5656
floorplan.organization_id = self.data.get('organization')
5757
return floorplan
5858

59+
def _get_initial_location(self):
60+
"""
61+
Returns initial location for the device.
62+
63+
Attempts to get the initial location by calling parent class method.
64+
If location is not found (e.g. when recovering a deleted device),
65+
returns None instead of raising Location.DoesNotExist.
66+
67+
Returns:
68+
Location instance or None if location does not exist
69+
"""
70+
try:
71+
return super()._get_initial_location()
72+
except Location.DoesNotExist:
73+
return None
74+
75+
def _get_initial_floorplan(self):
76+
"""
77+
Returns initial floorplan for the device.
78+
79+
Attempts to get the initial floorplan by calling parent class method.
80+
If floorplan is not found (e.g. when recovering a deleted device),
81+
returns None instead of raising FloorPlan.DoesNotExist.
82+
83+
Returns:
84+
FloorPlan instance or None if floorplan does not exist
85+
"""
86+
try:
87+
return super()._get_initial_floorplan()
88+
except FloorPlan.DoesNotExist:
89+
return None
90+
5991

6092
class LocationForm(AbstractLocationForm):
6193
class Meta(AbstractLocationForm.Meta):
@@ -83,6 +115,7 @@ class DeviceLocationInline(
83115

84116
admin.site.register(FloorPlan, FloorPlanAdmin)
85117
admin.site.register(Location, LocationAdmin)
118+
reversion.register(model=Location)
86119

87120

88121
class DeviceLocationFilter(admin.SimpleListFilter):
@@ -105,5 +138,5 @@ def queryset(self, request, queryset):
105138
DeviceAdminExportable.inlines.insert(1, DeviceLocationInline)
106139
DeviceAdminExportable.list_filter.append(DeviceLocationFilter)
107140
DeviceAdminExportable.resource_class = GeoDeviceResource
108-
reversion.register(model=DeviceLocation, follow=['device'])
141+
reversion.register(model=DeviceLocation, follow=['device', 'location'])
109142
DeviceAdminExportable.add_reversion_following(follow=['devicelocation'])

openwisp_controller/tests/test_selenium.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@
1818
Device = load_model('config', 'Device')
1919
DeviceConnection = load_model('connection', 'DeviceConnection')
2020
Location = load_model('geo', 'Location')
21+
FloorPlan = load_model('geo', 'FloorPlan')
2122
DeviceLocation = load_model('geo', 'DeviceLocation')
2223

2324

2425
@tag('selenium_tests')
25-
class TestDeviceConnectionInlineAdmin(
26+
class TestDevice(
2627
CreateConnectionsMixin, TestGeoMixin, SeleniumTestMixin, StaticLiveServerTestCase
2728
):
2829
config_app_label = 'config'
2930
location_model = Location
3031
object_location_model = DeviceLocation
32+
floorplan_model = FloorPlan
3133

3234
def setUp(self):
3335
self.admin = self._create_admin(
@@ -40,10 +42,14 @@ def test_restoring_deleted_device(self, *args):
4042
self._create_credentials(auto_add=True, organization=org)
4143
config = self._create_config(organization=org)
4244
device = config.device
45+
46+
location = self._create_location(organization=org, type='indoor')
47+
floorplan = self._create_floorplan(
48+
location=location,
49+
)
4350
self._create_object_location(
44-
location=self._create_location(
45-
organization=org,
46-
),
51+
location=location,
52+
floorplan=floorplan,
4753
content_object=device,
4854
)
4955
self.assertEqual(device.deviceconnection_set.count(), 1)
@@ -59,9 +65,13 @@ def test_restoring_deleted_device(self, *args):
5965
self.web_driver.find_element(
6066
by=By.XPATH, value='//*[@id="content"]/form/div/input[2]'
6167
).click()
68+
# Delete location object
69+
location.delete()
6270
self.assertEqual(Device.objects.count(), 0)
6371
self.assertEqual(DeviceConnection.objects.count(), 0)
6472
self.assertEqual(DeviceLocation.objects.count(), 0)
73+
self.assertEqual(self.location_model.objects.count(), 0)
74+
self.assertEqual(self.floorplan_model.objects.count(), 0)
6575

6676
version_obj = Version.objects.get_deleted(model=Device).first()
6777

@@ -94,3 +104,8 @@ def test_restoring_deleted_device(self, *args):
94104
self.assertEqual(Device.objects.count(), 1)
95105
self.assertEqual(DeviceConnection.objects.count(), 1)
96106
self.assertEqual(DeviceLocation.objects.count(), 1)
107+
self.assertEqual(self.location_model.objects.count(), 1)
108+
# The FloorPlan object is not recoverable because deleting it
109+
# also removes the associated image from the filesystem,
110+
# which cannot be restored.
111+
self.assertEqual(self.floorplan_model.objects.count(), 0)

0 commit comments

Comments
 (0)