Skip to content

Commit 6c08f7f

Browse files
[feature] Added filter by location (name/UUID), floorplan (UUID) to device list API #1158
Added new filters to the device list REST API endpoint: - search by location name - filter by exact location UUID - filter by exact floorplan UUID Closes #1158 --------- Co-authored-by: Federico Capoano <f.capoano@openwisp.io>
1 parent cbdb3c6 commit 6c08f7f

2 files changed

Lines changed: 82 additions & 7 deletions

File tree

openwisp_controller/geo/api/filters.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,34 @@ class DeviceListFilter(BaseDeviceListFilter):
1010
# Using filter query param name `with_geo`
1111
# which is similar to admin filter
1212
with_geo = filters.BooleanFilter(
13-
field_name="devicelocation", method="filter_devicelocation"
13+
field_name="devicelocation",
14+
method="filter_devicelocation",
15+
label=_("Has geographic location set?"),
16+
)
17+
location__name = filters.CharFilter(
18+
field_name="devicelocation__location__name",
19+
lookup_expr="icontains",
20+
label=_("Location name"),
21+
)
22+
location = filters.UUIDFilter(
23+
field_name="devicelocation__location", label=_("Location UUID")
24+
)
25+
floorplan = filters.UUIDFilter(
26+
field_name="devicelocation__floorplan", label=_("Floor plan UUID")
1427
)
15-
16-
def _set_valid_filterform_lables(self):
17-
super()._set_valid_filterform_lables()
18-
self.filters["with_geo"].label = _("Has geographic location set?")
1928

2029
def filter_devicelocation(self, queryset, name, value):
2130
# Returns list of device that have devicelocation objects
2231
return queryset.exclude(devicelocation__isnull=value)
2332

2433
class Meta:
2534
model = BaseDeviceListFilter.Meta.model
26-
fields = BaseDeviceListFilter.Meta.fields[:]
27-
fields.insert(fields.index("created"), "with_geo")
35+
geo_fields = [
36+
"with_geo",
37+
"location__name",
38+
"location",
39+
"floorplan",
40+
]
41+
fields = BaseDeviceListFilter.Meta.fields
42+
index_created = fields.index("created")
43+
fields[index_created:index_created] = geo_fields

openwisp_controller/geo/tests/test_api.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,65 @@ def _assert_device_list_with_geo_filter(response=None, device=None):
10031003
r2 = self.client.get(f"{path}?with_geo=true")
10041004
_assert_device_list_with_geo_filter(response=r2, device=device_b)
10051005

1006+
def test_filter_devices_by_location(self):
1007+
org = self._create_org()
1008+
device_1 = self._create_device(
1009+
name="device-1", mac_address="00:11:22:33:44:55", organization=org
1010+
)
1011+
device_2 = self._create_device(
1012+
name="device-2", mac_address="00:11:22:33:44:66", organization=org
1013+
)
1014+
location_1 = self._create_location(name="location-1 findme", organization=org)
1015+
location_2 = self._create_location(name="location-2", organization=org)
1016+
self._create_device_location(content_object=device_1, location=location_1)
1017+
self._create_device_location(content_object=device_2, location=location_2)
1018+
path = reverse("config_api:device_list")
1019+
with self.subTest("filter by location UUID"):
1020+
response = self.client.get(f"{path}?location={location_1.pk}")
1021+
self.assertEqual(response.status_code, 200)
1022+
self.assertEqual(response.data["count"], 1)
1023+
self.assertEqual(response.data["results"][0]["id"], str(device_1.pk))
1024+
with self.subTest("filter by location name"):
1025+
response = self.client.get(f"{path}?location__name=FIND")
1026+
self.assertEqual(response.status_code, 200)
1027+
self.assertEqual(response.data["count"], 1)
1028+
self.assertEqual(response.data["results"][0]["id"], str(device_1.pk))
1029+
with self.subTest("filter by wrong location name, expect zero results"):
1030+
response = self.client.get(f"{path}?location__name=WRONG")
1031+
self.assertEqual(response.status_code, 200)
1032+
self.assertEqual(response.data["count"], 0)
1033+
1034+
def test_filter_devices_by_floorplan_uuid(self):
1035+
org = self._create_org()
1036+
device_1 = self._create_device(
1037+
name="device-1", mac_address="00:11:22:33:44:55", organization=org
1038+
)
1039+
device_2 = self._create_device(
1040+
name="device-2", mac_address="00:11:22:33:44:66", organization=org
1041+
)
1042+
location = self._create_location(
1043+
name="location", type="indoor", organization=org
1044+
)
1045+
floorplan_1 = self._create_floorplan(location=location, floor=1)
1046+
floorplan_2 = self._create_floorplan(location=location, floor=2)
1047+
self._create_device_location(
1048+
content_object=device_1,
1049+
location=location,
1050+
floorplan=floorplan_1,
1051+
indoor="-1,-2",
1052+
)
1053+
self._create_device_location(
1054+
content_object=device_2,
1055+
location=location,
1056+
floorplan=floorplan_2,
1057+
indoor="-3,-4",
1058+
)
1059+
path = reverse("config_api:device_list")
1060+
response = self.client.get(f"{path}?floorplan={floorplan_1.pk}")
1061+
self.assertEqual(response.status_code, 200)
1062+
self.assertEqual(response.data["count"], 1)
1063+
self.assertEqual(response.data["results"][0]["id"], str(device_1.pk))
1064+
10061065
def test_deactivated_device(self):
10071066
floorplan = self._create_floorplan()
10081067
device_location = self._create_object_location(

0 commit comments

Comments
 (0)