Skip to content

Commit 4bb281f

Browse files
committed
[fix] Add revision_id filter to RevisionListView and renamed views and serializer for more clarity
1 parent 73d5145 commit 4bb281f

File tree

9 files changed

+104
-96
lines changed

9 files changed

+104
-96
lines changed

openwisp_controller/config/api/filters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ class Meta(BaseConfigAPIFilter.Meta):
146146

147147

148148
class ReversionFilter(BaseConfigAPIFilter):
149-
model = filters.CharFilter(field_name="content_type__model", lookup_expr="iexact")
149+
model = filters.CharFilter(field_name='content_type__model', lookup_expr='iexact')
150150

151151
def _set_valid_filterform_labels(self):
152-
self.filters["model"].label = _("Model")
152+
self.filters['model'].label = _('Model')
153153

154154
def __init__(self, *args, **kwargs):
155155
super().__init__(*args, **kwargs)

openwisp_controller/config/api/serializers.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,13 @@ def update(self, instance, validated_data):
379379
return instance
380380

381381

382-
class ReversionSerializer(BaseSerializer):
383-
user_id = serializers.SerializerMethodField()
382+
class VersionSerializer(BaseSerializer):
383+
user_id = serializers.CharField(source='revision.user_id', read_only=True)
384384
date_created = serializers.DateTimeField(
385385
source='revision.date_created', read_only=True
386386
)
387387
comment = serializers.CharField(source='revision.comment', read_only=True)
388-
content_type = serializers.SerializerMethodField()
388+
content_type = serializers.CharField(source='revision.content_type', read_only=True)
389389

390390
class Meta:
391391
model = Version
@@ -402,9 +402,3 @@ class Meta:
402402
'date_created',
403403
'comment',
404404
]
405-
406-
def get_user_id(self, obj):
407-
return getattr(obj.revision, 'user_id', None)
408-
409-
def get_content_type(self, obj):
410-
return getattr(obj.content_type, 'model', None)

openwisp_controller/config/api/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ def get_api_urls(api_views):
2020
),
2121
path(
2222
"controller/<str:model>/revision/<str:pk>/",
23-
api_views.revision_detail,
24-
name="revision_detail",
23+
api_views.version_detail,
24+
name="version_detail",
2525
),
2626
path(
2727
"controller/<str:model>/revision/<str:pk>/restore/",

openwisp_controller/config/api/views.py

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import reversion
22
from cache_memoize import cache_memoize
33
from django.core.exceptions import ObjectDoesNotExist
4+
from django.db import transaction
45
from django.db.models import F, Q
56
from django.http import Http404
67
from django.shortcuts import get_list_or_404
@@ -32,8 +33,8 @@
3233
DeviceDetailSerializer,
3334
DeviceGroupSerializer,
3435
DeviceListSerializer,
35-
ReversionSerializer,
3636
TemplateSerializer,
37+
VersionSerializer,
3738
VpnSerializer,
3839
)
3940

@@ -302,53 +303,55 @@ def certificate_delete_invalidates_cache(cls, organization_id, common_name):
302303
cls.get_device_group.invalidate(cls, org_slug, common_name)
303304

304305

305-
class RevisionListView(ProtectedAPIMixin, AutoRevisionMixin, ListAPIView):
306-
serializer_class = ReversionSerializer
306+
class RevisionListView(ProtectedAPIMixin, ListAPIView):
307+
serializer_class = VersionSerializer
308+
queryset = Version.objects.select_related('revision').order_by(
309+
'-revision__date_created'
310+
)
307311

308312
def get_queryset(self):
309-
model_slug = self.kwargs.get('model').lower()
310-
return (
311-
Version.objects.select_related('revision')
312-
.filter(content_type__model=model_slug)
313-
.order_by('-revision__date_created')
314-
)
315-
316-
317-
class RevisionDetailView(ProtectedAPIMixin, RetrieveAPIView):
318-
serializer_class = ReversionSerializer
313+
model = self.kwargs.get('model').lower()
314+
queryset = self.queryset.filter(content_type__model=model)
315+
revision_id = self.request.query_params.get('revision_id')
316+
if revision_id:
317+
queryset = queryset.filter(revision_id=revision_id)
318+
return self.queryset.filter(content_type__model=model)
319+
320+
321+
class VersionDetailView(ProtectedAPIMixin, RetrieveAPIView):
322+
serializer_class = VersionSerializer
323+
queryset = Version.objects.select_related('revision').order_by(
324+
'-revision__date_created'
325+
)
319326

320327
def get_queryset(self):
321-
model_slug = self.kwargs.get('model').lower()
322-
return (
323-
Version.objects.select_related('revision')
324-
.filter(content_type__model=model_slug)
325-
.order_by('-revision__date_created')
326-
)
328+
model = self.kwargs.get('model').lower()
329+
return self.queryset.filter(content_type__model=model)
327330

328331

329332
class RevisionRestoreView(ProtectedAPIMixin, GenericAPIView):
330333
serializer_class = serializers.Serializer
334+
queryset = Version.objects.select_related('revision').order_by(
335+
'-revision__date_created'
336+
)
331337

332338
def get_queryset(self):
333-
model_slug = self.kwargs.get('model').lower()
334-
return (
335-
Version.objects.select_related('revision')
336-
.filter(content_type__model=model_slug)
337-
.order_by('-revision__date_created')
338-
)
339+
model = self.kwargs.get('model').lower()
340+
return self.queryset.filter(content_type__model=model)
339341

340342
def post(self, request, *args, **kwargs):
341343
qs = self.get_queryset()
342344
versions = get_list_or_404(qs, revision_id=kwargs['pk'])
343-
with reversion.create_revision():
344-
for version in versions:
345-
version.revert()
346-
reversion.set_user(request.user)
347-
reversion.set_comment(
348-
f"Restored to previous revision: {self.kwargs.get('pk')}"
349-
)
350-
351-
serializer = ReversionSerializer(
345+
with transaction.atomic():
346+
with reversion.create_revision():
347+
for version in versions:
348+
version.revert()
349+
reversion.set_user(request.user)
350+
reversion.set_comment(
351+
f"Restored to previous revision: {self.kwargs.get('pk')}"
352+
)
353+
354+
serializer = VersionSerializer(
352355
versions, many=True, context=self.get_serializer_context()
353356
)
354357
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -366,5 +369,5 @@ def post(self, request, *args, **kwargs):
366369
devicegroup_detail = DeviceGroupDetailView.as_view()
367370
devicegroup_commonname = DeviceGroupCommonName.as_view()
368371
revision_list = RevisionListView.as_view()
369-
revision_detail = RevisionDetailView.as_view()
372+
version_detail = VersionDetailView.as_view()
370373
revision_restore = RevisionRestoreView.as_view()

openwisp_controller/config/tests/test_api.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,15 +1650,24 @@ def test_revision_list_and_restore_api(self):
16501650
self.assertEqual(response.status_code, 200)
16511651
self.assertEqual(response.data['name'], 'change-test-device')
16521652

1653-
path = reverse('config_api:revision_list', args=[model_slug])
1654-
response = self.client.get(path)
1655-
response_json = response.json()
1656-
version_id = response_json[1]["id"]
1657-
self.assertEqual(response.status_code, 200)
1658-
self.assertEqual(len(response_json), 2)
1653+
with self.subTest('Test revision list'):
1654+
path = reverse('config_api:revision_list', args=[model_slug])
1655+
response = self.client.get(path)
1656+
response_json = response.json()
1657+
version_id = response_json[1]["id"]
1658+
revision_id = response_json[1]['revision_id']
1659+
self.assertEqual(response.status_code, 200)
1660+
self.assertEqual(len(response_json), 2)
1661+
1662+
with self.subTest('Test revision list filter by revision id'):
1663+
path = reverse('config_api:revision_list', args=[model_slug])
1664+
response = self.client.get(f'{path}?revision_id={revision_id}')
1665+
response_json = response.json()
1666+
self.assertEqual(response.status_code, 200)
1667+
self.assertEqual(len(response_json), 2)
16591668

1660-
with self.subTest('Test revision detail'):
1661-
path = reverse('config_api:revision_detail', args=[model_slug, version_id])
1669+
with self.subTest('Test version detail'):
1670+
path = reverse('config_api:version_detail', args=[model_slug, version_id])
16621671
response = self.client.get(path)
16631672
self.assertEqual(response.status_code, 200)
16641673
self.assertEqual(response.json()['id'], version_id)

openwisp_controller/connection/tests/test_api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ def test_post_deviceconnection_list(self):
494494
"enabled": True,
495495
"failure_reason": "",
496496
}
497-
with self.assertNumQueries(13):
497+
with self.assertNumQueries(23):
498498
response = self.client.post(path, data, content_type="application/json")
499499
self.assertEqual(response.status_code, 201)
500500

@@ -539,7 +539,7 @@ def test_put_devceconnection_detail(self):
539539
"enabled": False,
540540
"failure_reason": "",
541541
}
542-
with self.assertNumQueries(14):
542+
with self.assertNumQueries(23):
543543
response = self.client.put(path, data, content_type="application/json")
544544
self.assertEqual(response.status_code, 200)
545545
self.assertEqual(
@@ -553,7 +553,7 @@ def test_patch_deviceconnectoin_detail(self):
553553
path = reverse("connection_api:deviceconnection_detail", args=(d1, dc.pk))
554554
self.assertEqual(dc.update_strategy, app_settings.UPDATE_STRATEGIES[0][0])
555555
data = {"update_strategy": app_settings.UPDATE_STRATEGIES[1][0]}
556-
with self.assertNumQueries(13):
556+
with self.assertNumQueries(22):
557557
response = self.client.patch(path, data, content_type="application/json")
558558
self.assertEqual(response.status_code, 200)
559559
self.assertEqual(

openwisp_controller/geo/tests/test_api.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import uuid
44

55
from django.contrib.auth import get_user_model
6+
from django.contrib.contenttypes.models import ContentType
67
from django.contrib.gis.geos import Point
78
from django.test import TestCase
89
from django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart
@@ -300,6 +301,7 @@ class TestGeoApi(
300301
def setUp(self):
301302
admin = self._create_admin()
302303
self.client.force_login(admin)
304+
ContentType.objects.clear_cache()
303305

304306
def _create_device_location(self, **kwargs):
305307
options = dict()
@@ -494,7 +496,7 @@ def test_post_location_list(self):
494496
"address": "Via del Corso, Roma, Italia",
495497
"geometry": coords,
496498
}
497-
with self.assertNumQueries(9):
499+
with self.assertNumQueries(13):
498500
response = self.client.post(path, data, content_type="application/json")
499501
self.assertEqual(response.status_code, 201)
500502

@@ -525,7 +527,7 @@ def test_put_location_detail(self):
525527
"address": "Via del Corso, Roma, Italia",
526528
"geometry": coords,
527529
}
528-
with self.assertNumQueries(6):
530+
with self.assertNumQueries(10):
529531
response = self.client.put(path, data, content_type="application/json")
530532
self.assertEqual(response.status_code, 200)
531533
self.assertEqual(response.data["organization"], org1.pk)
@@ -536,7 +538,7 @@ def test_patch_location_detail(self):
536538
self.assertEqual(l1.name, "test-location")
537539
path = reverse("geo_api:detail_location", args=[l1.pk])
538540
data = {"name": "change-test-location"}
539-
with self.assertNumQueries(5):
541+
with self.assertNumQueries(9):
540542
response = self.client.patch(path, data, content_type="application/json")
541543
self.assertEqual(response.status_code, 200)
542544
self.assertEqual(response.data["name"], "change-test-location")
@@ -566,7 +568,7 @@ def test_patch_floorplan_detail_api(self):
566568
fl = self._create_floorplan(location=l1)
567569
path = reverse("geo_api:detail_location", args=[l1.pk])
568570
data = {"floorplan": {"floor": 13}}
569-
with self.assertNumQueries(13):
571+
with self.assertNumQueries(17):
570572
response = self.client.patch(path, data, content_type="application/json")
571573
self.assertEqual(response.status_code, 200)
572574
fl.refresh_from_db()
@@ -577,7 +579,7 @@ def test_change_location_type_to_outdoor_api(self):
577579
self._create_floorplan(location=l1)
578580
path = reverse("geo_api:detail_location", args=[l1.pk])
579581
data = {"type": "outdoor"}
580-
with self.assertNumQueries(9):
582+
with self.assertNumQueries(13):
581583
response = self.client.patch(path, data, content_type="application/json")
582584
self.assertEqual(response.status_code, 200)
583585
self.assertEqual(response.data["floorplan"], [])
@@ -603,7 +605,7 @@ def test_create_location_with_floorplan(self):
603605
"floorplan.floor": ["23"],
604606
"floorplan.image": [fl_image],
605607
}
606-
with self.assertNumQueries(16):
608+
with self.assertNumQueries(20):
607609
response = self.client.post(path, data, format="multipart")
608610
self.assertEqual(response.status_code, 201)
609611
self.assertEqual(Location.objects.count(), 1)
@@ -627,7 +629,7 @@ def test_create_new_floorplan_with_put_location_api(self):
627629
"floorplan.floor": "23",
628630
"floorplan.image": fl_image,
629631
}
630-
with self.assertNumQueries(16):
632+
with self.assertNumQueries(20):
631633
response = self.client.put(
632634
path, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
633635
)
@@ -722,7 +724,7 @@ def test_create_devicelocation_using_related_ids(self):
722724
floorplan = self._create_floorplan()
723725
location = floorplan.location
724726
url = reverse("geo_api:device_location", args=[device.id])
725-
with self.assertNumQueries(18):
727+
with self.assertNumQueries(26):
726728
response = self.client.put(
727729
url,
728730
data={
@@ -760,7 +762,7 @@ def test_create_devicelocation_location_floorplan(self):
760762
"floorplan.image": self._get_simpleuploadedfile(),
761763
"indoor": ["12.342,23.541"],
762764
}
763-
with self.assertNumQueries(32):
765+
with self.assertNumQueries(40):
764766
response = self.client.put(
765767
url, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
766768
)
@@ -827,8 +829,8 @@ def test_create_devicelocation_only_location(self):
827829
"type": "indoor",
828830
}
829831
}
830-
with self.assertNumQueries(21):
831-
response = self.client.put(url, data=data, content_type="application/json")
832+
with self.assertNumQueries(29):
833+
response = self.client.put(url, data=data, content_type='application/json')
832834
self.assertEqual(response.status_code, 201)
833835
self.assertEqual(self.location_model.objects.count(), 1)
834836
self.assertEqual(self.object_location_model.objects.count(), 1)
@@ -867,7 +869,7 @@ def test_create_devicelocation_existing_location_new_floorplan(self):
867869
"floorplan.image": self._get_simpleuploadedfile(),
868870
"indoor": ["12.342,23.541"],
869871
}
870-
with self.assertNumQueries(26):
872+
with self.assertNumQueries(34):
871873
response = self.client.put(
872874
url, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
873875
)
@@ -890,7 +892,7 @@ def test_update_devicelocation_change_location_outdoor_to_indoor(self):
890892
}
891893
self.assertEqual(device_location.location.type, "outdoor")
892894
self.assertEqual(device_location.floorplan, None)
893-
with self.assertNumQueries(23):
895+
with self.assertNumQueries(31):
894896
response = self.client.put(
895897
path, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
896898
)
@@ -909,7 +911,7 @@ def test_update_devicelocation_patch_indoor(self):
909911
"indoor": "0,0",
910912
}
911913
self.assertEqual(device_location.indoor, "-140.38620,40.369227")
912-
with self.assertNumQueries(12):
914+
with self.assertNumQueries(18):
913915
response = self.client.patch(path, data, content_type="application/json")
914916
self.assertEqual(response.status_code, 200)
915917
device_location.refresh_from_db()
@@ -926,8 +928,8 @@ def test_update_devicelocation_floorplan_related_id(self):
926928
data = {
927929
"floorplan": str(floor2.id),
928930
}
929-
with self.assertNumQueries(14):
930-
response = self.client.patch(path, data, content_type="application/json")
931+
with self.assertNumQueries(20):
932+
response = self.client.patch(path, data, content_type='application/json')
931933
self.assertEqual(response.status_code, 200)
932934
device_location.refresh_from_db()
933935
self.assertEqual(device_location.floorplan, floor2)
@@ -940,7 +942,7 @@ def test_update_devicelocation_location_related_id(self):
940942
data = {
941943
"location": str(location2.id),
942944
}
943-
with self.assertNumQueries(11):
945+
with self.assertNumQueries(19):
944946
response = self.client.patch(path, data, content_type="application/json")
945947
self.assertEqual(response.status_code, 200)
946948
device_location.refresh_from_db()

0 commit comments

Comments
 (0)