33from django .core .exceptions import ObjectDoesNotExist
44from django .db .models import F , Q
55from django .http import Http404
6+ from django .shortcuts import get_list_or_404
67from django .urls .base import reverse
78from django_filters .rest_framework import DjangoFilterBackend
89from rest_framework import pagination , serializers , status
1920
2021from openwisp_users .api .permissions import DjangoModelPermissions
2122
22- from ...mixins import ProtectedAPIMixin
23+ from ...mixins import AutoRevisionMixin , ProtectedAPIMixin
2324from .filters import (
2425 DeviceGroupListFilter ,
2526 DeviceListFilter ,
2627 DeviceListFilterBackend ,
27- ReversionFilter ,
2828 TemplateListFilter ,
2929 VPNListFilter ,
3030)
@@ -53,28 +53,30 @@ class ListViewPagination(pagination.PageNumberPagination):
5353 max_page_size = 100
5454
5555
56- class TemplateListCreateView (ProtectedAPIMixin , ListCreateAPIView ):
56+ class TemplateListCreateView (ProtectedAPIMixin , AutoRevisionMixin , ListCreateAPIView ):
5757 serializer_class = TemplateSerializer
5858 queryset = Template .objects .prefetch_related ('tags' ).order_by ('-created' )
5959 pagination_class = ListViewPagination
6060 filter_backends = [DjangoFilterBackend ]
6161 filterset_class = TemplateListFilter
6262
6363
64- class TemplateDetailView (ProtectedAPIMixin , RetrieveUpdateDestroyAPIView ):
64+ class TemplateDetailView (
65+ ProtectedAPIMixin , AutoRevisionMixin , RetrieveUpdateDestroyAPIView
66+ ):
6567 serializer_class = TemplateSerializer
6668 queryset = Template .objects .all ()
6769
6870
69- class VpnListCreateView (ProtectedAPIMixin , ListCreateAPIView ):
71+ class VpnListCreateView (ProtectedAPIMixin , AutoRevisionMixin , ListCreateAPIView ):
7072 serializer_class = VpnSerializer
7173 queryset = Vpn .objects .select_related ('subnet' ).order_by ('-created' )
7274 pagination_class = ListViewPagination
7375 filter_backends = [DjangoFilterBackend ]
7476 filterset_class = VPNListFilter
7577
7678
77- class VpnDetailView (ProtectedAPIMixin , RetrieveUpdateDestroyAPIView ):
79+ class VpnDetailView (ProtectedAPIMixin , AutoRevisionMixin , RetrieveUpdateDestroyAPIView ):
7880 serializer_class = VpnSerializer
7981 queryset = Vpn .objects .all ()
8082
@@ -87,7 +89,7 @@ def has_object_permission(self, request, view, obj):
8789 return perm and not obj .is_deactivated ()
8890
8991
90- class DeviceListCreateView (ProtectedAPIMixin , ListCreateAPIView ):
92+ class DeviceListCreateView (ProtectedAPIMixin , AutoRevisionMixin , ListCreateAPIView ):
9193 """
9294 Templates: Templates flagged as required will be added automatically
9395 to the `config` of a device and cannot be unassigned.
@@ -102,7 +104,9 @@ class DeviceListCreateView(ProtectedAPIMixin, ListCreateAPIView):
102104 filterset_class = DeviceListFilter
103105
104106
105- class DeviceDetailView (ProtectedAPIMixin , RetrieveUpdateDestroyAPIView ):
107+ class DeviceDetailView (
108+ ProtectedAPIMixin , AutoRevisionMixin , RetrieveUpdateDestroyAPIView
109+ ):
106110 """
107111 Templates: Templates flagged as _required_ will be added automatically
108112 to the `config` of a device and cannot be unassigned.
@@ -117,7 +121,7 @@ def perform_destroy(self, instance):
117121 instance .delete (check_deactivated = (not force_deletion ))
118122
119123
120- class DeviceActivateView (ProtectedAPIMixin , GenericAPIView ):
124+ class DeviceActivateView (ProtectedAPIMixin , AutoRevisionMixin , GenericAPIView ):
121125 serializer_class = serializers .Serializer
122126 queryset = Device .objects .filter (_is_deactivated = True )
123127
@@ -130,7 +134,7 @@ def post(self, request, *args, **kwargs):
130134 return Response (serializer .data , status = status .HTTP_200_OK )
131135
132136
133- class DeviceDeactivateView (ProtectedAPIMixin , GenericAPIView ):
137+ class DeviceDeactivateView (ProtectedAPIMixin , AutoRevisionMixin , GenericAPIView ):
134138 serializer_class = serializers .Serializer
135139 queryset = Device .objects .filter (_is_deactivated = False )
136140
@@ -143,15 +147,19 @@ def post(self, request, *args, **kwargs):
143147 return Response (serializer .data , status = status .HTTP_200_OK )
144148
145149
146- class DeviceGroupListCreateView (ProtectedAPIMixin , ListCreateAPIView ):
150+ class DeviceGroupListCreateView (
151+ ProtectedAPIMixin , AutoRevisionMixin , ListCreateAPIView
152+ ):
147153 serializer_class = DeviceGroupSerializer
148154 queryset = DeviceGroup .objects .prefetch_related ('templates' ).order_by ('-created' )
149155 pagination_class = ListViewPagination
150156 filter_backends = [DjangoFilterBackend ]
151157 filterset_class = DeviceGroupListFilter
152158
153159
154- class DeviceGroupDetailView (ProtectedAPIMixin , RetrieveUpdateDestroyAPIView ):
160+ class DeviceGroupDetailView (
161+ ProtectedAPIMixin , AutoRevisionMixin , RetrieveUpdateDestroyAPIView
162+ ):
155163 serializer_class = DeviceGroupSerializer
156164 queryset = DeviceGroup .objects .select_related ('organization' ).order_by ('-created' )
157165
@@ -165,7 +173,7 @@ def get_cached_devicegroup_args_rewrite(cls, org_slugs, common_name):
165173 return url
166174
167175
168- class DeviceGroupCommonName (ProtectedAPIMixin , RetrieveAPIView ):
176+ class DeviceGroupCommonName (ProtectedAPIMixin , AutoRevisionMixin , RetrieveAPIView ):
169177 serializer_class = DeviceGroupSerializer
170178 queryset = DeviceGroup .objects .select_related ('organization' ).order_by ('-created' )
171179 # Not setting lookup_field makes DRF raise error. but it is not used
@@ -282,39 +290,55 @@ def certificate_delete_invalidates_cache(cls, organization_id, common_name):
282290 cls .get_device_group .invalidate (cls , org_slug , common_name )
283291
284292
285- class ReversionListView (ProtectedAPIMixin , ListAPIView ):
293+ class RevisionListView (ProtectedAPIMixin , AutoRevisionMixin , ListAPIView ):
286294 serializer_class = ReversionSerializer
287- queryset = Version .objects .select_related ('revision' ).order_by (
288- '-revision__date_created'
289- )
290- filter_backends = [DjangoFilterBackend ]
291- filterset_class = ReversionFilter
295+
296+ def get_queryset (self ):
297+ model_slug = self .kwargs .get ('model_slug' ).lower ()
298+ return (
299+ Version .objects .select_related ('revision' )
300+ .filter (content_type__model = model_slug )
301+ .order_by ('-revision__date_created' )
302+ )
292303
293304
294- class ReversionDetailView (ProtectedAPIMixin , RetrieveAPIView ):
305+ class RevisionDetailView (ProtectedAPIMixin , RetrieveAPIView ):
295306 serializer_class = ReversionSerializer
296- queryset = Version .objects .select_related ('revision' ).order_by (
297- '-revision__date_created'
298- )
299- lookup_field = 'pk'
307+
308+ def get_queryset (self ):
309+ model_slug = self .kwargs .get ('model_slug' ).lower ()
310+ return (
311+ Version .objects .select_related ('revision' )
312+ .filter (content_type__model = model_slug )
313+ .order_by ('-revision__date_created' )
314+ )
300315
301316
302- class ReversionRestoreView (ProtectedAPIMixin , GenericAPIView ):
317+ class RevisionRestoreView (ProtectedAPIMixin , GenericAPIView ):
303318 serializer_class = serializers .Serializer
304- queryset = Version .objects .select_related ('revision' ).order_by (
305- '-revision__date_created'
306- )
319+
320+ def get_queryset (self ):
321+ model_slug = self .kwargs .get ('model_slug' ).lower ()
322+ return (
323+ Version .objects .select_related ('revision' )
324+ .filter (content_type__model = model_slug )
325+ .order_by ('-revision__date_created' )
326+ )
307327
308328 def post (self , request , * args , ** kwargs ):
309- version = self .get_object ()
329+ qs = self .get_queryset ()
330+ versions = get_list_or_404 (qs , revision_id = kwargs ['pk' ])
310331 with reversion .create_revision ():
311- version .revert ()
332+ for version in versions :
333+ version .revert ()
312334 reversion .set_user (request .user )
313335 reversion .set_comment (
314- f"Restored to previous revision: { version . revision_id } "
336+ f"Restored to previous revision: { self . kwargs . get ( 'pk' ) } "
315337 )
316338
317- serializer = ReversionSerializer (version , context = self .get_serializer_context ())
339+ serializer = ReversionSerializer (
340+ versions , many = True , context = self .get_serializer_context ()
341+ )
318342 return Response (serializer .data , status = status .HTTP_200_OK )
319343
320344
@@ -329,6 +353,6 @@ def post(self, request, *args, **kwargs):
329353devicegroup_list = DeviceGroupListCreateView .as_view ()
330354devicegroup_detail = DeviceGroupDetailView .as_view ()
331355devicegroup_commonname = DeviceGroupCommonName .as_view ()
332- reversion_list = ReversionListView .as_view ()
333- reversion_detail = ReversionDetailView .as_view ()
334- reversion_restore = ReversionRestoreView .as_view ()
356+ revision_list = RevisionListView .as_view ()
357+ revision_detail = RevisionDetailView .as_view ()
358+ revision_restore = RevisionRestoreView .as_view ()
0 commit comments