Skip to content

Commit 89a5ef1

Browse files
committed
[fix] Add revision_id filter to RevisionListView and renamed views and serializer for more clarity
1 parent 47e1c70 commit 89a5ef1

File tree

9 files changed

+102
-94
lines changed

9 files changed

+102
-94
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
@@ -1580,15 +1580,24 @@ def test_revision_list_and_restore_api(self):
15801580
self.assertEqual(response.status_code, 200)
15811581
self.assertEqual(response.data['name'], 'change-test-device')
15821582

1583-
path = reverse('config_api:revision_list', args=[model_slug])
1584-
response = self.client.get(path)
1585-
response_json = response.json()
1586-
version_id = response_json[1]["id"]
1587-
self.assertEqual(response.status_code, 200)
1588-
self.assertEqual(len(response_json), 2)
1583+
with self.subTest('Test revision list'):
1584+
path = reverse('config_api:revision_list', args=[model_slug])
1585+
response = self.client.get(path)
1586+
response_json = response.json()
1587+
version_id = response_json[1]["id"]
1588+
revision_id = response_json[1]['revision_id']
1589+
self.assertEqual(response.status_code, 200)
1590+
self.assertEqual(len(response_json), 2)
1591+
1592+
with self.subTest('Test revision list filter by revision id'):
1593+
path = reverse('config_api:revision_list', args=[model_slug])
1594+
response = self.client.get(f'{path}?revision_id={revision_id}')
1595+
response_json = response.json()
1596+
self.assertEqual(response.status_code, 200)
1597+
self.assertEqual(len(response_json), 2)
15891598

1590-
with self.subTest('Test revision detail'):
1591-
path = reverse('config_api:revision_detail', args=[model_slug, version_id])
1599+
with self.subTest('Test version detail'):
1600+
path = reverse('config_api:version_detail', args=[model_slug, version_id])
15921601
response = self.client.get(path)
15931602
self.assertEqual(response.status_code, 200)
15941603
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
@@ -458,7 +458,7 @@ def test_post_deviceconnection_list(self):
458458
'enabled': True,
459459
'failure_reason': '',
460460
}
461-
with self.assertNumQueries(13):
461+
with self.assertNumQueries(23):
462462
response = self.client.post(path, data, content_type='application/json')
463463
self.assertEqual(response.status_code, 201)
464464

@@ -503,7 +503,7 @@ def test_put_devceconnection_detail(self):
503503
'enabled': False,
504504
'failure_reason': '',
505505
}
506-
with self.assertNumQueries(14):
506+
with self.assertNumQueries(23):
507507
response = self.client.put(path, data, content_type='application/json')
508508
self.assertEqual(response.status_code, 200)
509509
self.assertEqual(
@@ -517,7 +517,7 @@ def test_patch_deviceconnectoin_detail(self):
517517
path = reverse('connection_api:deviceconnection_detail', args=(d1, dc.pk))
518518
self.assertEqual(dc.update_strategy, app_settings.UPDATE_STRATEGIES[0][0])
519519
data = {'update_strategy': app_settings.UPDATE_STRATEGIES[1][0]}
520-
with self.assertNumQueries(13):
520+
with self.assertNumQueries(22):
521521
response = self.client.patch(path, data, content_type='application/json')
522522
self.assertEqual(response.status_code, 200)
523523
self.assertEqual(

openwisp_controller/geo/tests/test_api.py

Lines changed: 17 additions & 15 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
)
@@ -695,7 +697,7 @@ def test_create_devicelocation_using_related_ids(self):
695697
floorplan = self._create_floorplan()
696698
location = floorplan.location
697699
url = reverse('geo_api:device_location', args=[device.id])
698-
with self.assertNumQueries(15):
700+
with self.assertNumQueries(26):
699701
response = self.client.put(
700702
url,
701703
data={
@@ -733,7 +735,7 @@ def test_create_devicelocation_location_floorplan(self):
733735
'floorplan.image': self._get_simpleuploadedfile(),
734736
'indoor': ['12.342,23.541'],
735737
}
736-
with self.assertNumQueries(29):
738+
with self.assertNumQueries(40):
737739
response = self.client.put(
738740
url, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
739741
)
@@ -800,7 +802,7 @@ def test_create_devicelocation_only_location(self):
800802
'type': 'indoor',
801803
}
802804
}
803-
with self.assertNumQueries(18):
805+
with self.assertNumQueries(29):
804806
response = self.client.put(url, data=data, content_type='application/json')
805807
self.assertEqual(response.status_code, 201)
806808
self.assertEqual(self.location_model.objects.count(), 1)
@@ -840,7 +842,7 @@ def test_create_devicelocation_existing_location_new_floorplan(self):
840842
'floorplan.image': self._get_simpleuploadedfile(),
841843
'indoor': ['12.342,23.541'],
842844
}
843-
with self.assertNumQueries(23):
845+
with self.assertNumQueries(34):
844846
response = self.client.put(
845847
url, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
846848
)
@@ -863,7 +865,7 @@ def test_update_devicelocation_change_location_outdoor_to_indoor(self):
863865
}
864866
self.assertEqual(device_location.location.type, 'outdoor')
865867
self.assertEqual(device_location.floorplan, None)
866-
with self.assertNumQueries(21):
868+
with self.assertNumQueries(31):
867869
response = self.client.put(
868870
path, encode_multipart(BOUNDARY, data), content_type=MULTIPART_CONTENT
869871
)
@@ -882,7 +884,7 @@ def test_update_devicelocation_patch_indoor(self):
882884
'indoor': '0,0',
883885
}
884886
self.assertEqual(device_location.indoor, '-140.38620,40.369227')
885-
with self.assertNumQueries(10):
887+
with self.assertNumQueries(18):
886888
response = self.client.patch(path, data, content_type='application/json')
887889
self.assertEqual(response.status_code, 200)
888890
device_location.refresh_from_db()
@@ -899,7 +901,7 @@ def test_update_devicelocation_floorplan_related_id(self):
899901
data = {
900902
'floorplan': str(floor2.id),
901903
}
902-
with self.assertNumQueries(12):
904+
with self.assertNumQueries(20):
903905
response = self.client.patch(path, data, content_type='application/json')
904906
self.assertEqual(response.status_code, 200)
905907
device_location.refresh_from_db()
@@ -913,7 +915,7 @@ def test_update_devicelocation_location_related_id(self):
913915
data = {
914916
'location': str(location2.id),
915917
}
916-
with self.assertNumQueries(9):
918+
with self.assertNumQueries(19):
917919
response = self.client.patch(path, data, content_type='application/json')
918920
self.assertEqual(response.status_code, 200)
919921
device_location.refresh_from_db()

0 commit comments

Comments
 (0)