1
1
import logging
2
2
3
- from django .contrib .contenttypes .models import ContentType
4
3
from django .core .exceptions import ObjectDoesNotExist , PermissionDenied
5
4
from django .db import transaction
6
5
from django .db .models import ProtectedError
7
- from django . http import Http404
6
+ from rest_framework import mixins as drf_mixins
8
7
from rest_framework .response import Response
9
- from rest_framework .viewsets import ModelViewSet
8
+ from rest_framework .viewsets import GenericViewSet
10
9
11
- from extras .models import ExportTemplate
12
- from netbox .api .exceptions import SerializerNotFound
13
- from netbox .constants import NESTED_SERIALIZER_PREFIX
14
- from utilities .api import get_serializer_for_model
15
10
from utilities .exceptions import AbortRequest
16
- from .mixins import *
11
+ from . import mixins
17
12
18
13
__all__ = (
14
+ 'NetBoxReadOnlyModelViewSet' ,
19
15
'NetBoxModelViewSet' ,
20
16
)
21
17
30
26
}
31
27
32
28
33
- class NetBoxModelViewSet ( BulkUpdateModelMixin , BulkDestroyModelMixin , ObjectValidationMixin , ModelViewSet ):
29
+ class BaseViewSet ( GenericViewSet ):
34
30
"""
35
- Extend DRF's ModelViewSet to support bulk update and delete functions .
31
+ Base class for all API ViewSets. This is responsible for the enforcement of object-based permissions .
36
32
"""
37
- brief = False
38
- brief_prefetch_fields = []
33
+ def initial ( self , request , * args , ** kwargs ):
34
+ super (). initial ( request , * args , ** kwargs )
39
35
36
+ # Restrict the view's QuerySet to allow only the permitted objects
37
+ if request .user .is_authenticated :
38
+ if action := HTTP_ACTIONS [request .method ]:
39
+ self .queryset = self .queryset .restrict (request .user , action )
40
+
41
+
42
+ class NetBoxReadOnlyModelViewSet (
43
+ mixins .BriefModeMixin ,
44
+ mixins .CustomFieldsMixin ,
45
+ mixins .ExportTemplatesMixin ,
46
+ drf_mixins .RetrieveModelMixin ,
47
+ drf_mixins .ListModelMixin ,
48
+ BaseViewSet
49
+ ):
50
+ pass
51
+
52
+
53
+ class NetBoxModelViewSet (
54
+ mixins .BulkUpdateModelMixin ,
55
+ mixins .BulkDestroyModelMixin ,
56
+ mixins .ObjectValidationMixin ,
57
+ mixins .BriefModeMixin ,
58
+ mixins .CustomFieldsMixin ,
59
+ mixins .ExportTemplatesMixin ,
60
+ drf_mixins .CreateModelMixin ,
61
+ drf_mixins .RetrieveModelMixin ,
62
+ drf_mixins .UpdateModelMixin ,
63
+ drf_mixins .DestroyModelMixin ,
64
+ drf_mixins .ListModelMixin ,
65
+ BaseViewSet
66
+ ):
67
+ """
68
+ Extend DRF's ModelViewSet to support bulk update and delete functions.
69
+ """
40
70
def get_object_with_snapshot (self ):
41
71
"""
42
72
Save a pre-change snapshot of the object immediately after retrieving it. This snapshot will be used to
@@ -48,71 +78,14 @@ def get_object_with_snapshot(self):
48
78
return obj
49
79
50
80
def get_serializer (self , * args , ** kwargs ):
51
-
52
81
# If a list of objects has been provided, initialize the serializer with many=True
53
82
if isinstance (kwargs .get ('data' , {}), list ):
54
83
kwargs ['many' ] = True
55
84
56
85
return super ().get_serializer (* args , ** kwargs )
57
86
58
- def get_serializer_class (self ):
59
- logger = logging .getLogger ('netbox.api.views.ModelViewSet' )
60
-
61
- # If using 'brief' mode, find and return the nested serializer for this model, if one exists
62
- if self .brief :
63
- logger .debug ("Request is for 'brief' format; initializing nested serializer" )
64
- try :
65
- serializer = get_serializer_for_model (self .queryset .model , prefix = NESTED_SERIALIZER_PREFIX )
66
- logger .debug (f"Using serializer { serializer } " )
67
- return serializer
68
- except SerializerNotFound :
69
- logger .debug (f"Nested serializer for { self .queryset .model } not found!" )
70
-
71
- # Fall back to the hard-coded serializer class
72
- logger .debug (f"Using serializer { self .serializer_class } " )
73
- return self .serializer_class
74
-
75
- def get_serializer_context (self ):
76
- """
77
- For models which support custom fields, populate the `custom_fields` context.
78
- """
79
- context = super ().get_serializer_context ()
80
-
81
- if hasattr (self .queryset .model , 'custom_fields' ):
82
- content_type = ContentType .objects .get_for_model (self .queryset .model )
83
- context .update ({
84
- 'custom_fields' : content_type .custom_fields .all (),
85
- })
86
-
87
- return context
88
-
89
- def get_queryset (self ):
90
- # If using brief mode, clear all prefetches from the queryset and append only brief_prefetch_fields (if any)
91
- if self .brief :
92
- return super ().get_queryset ().prefetch_related (None ).prefetch_related (* self .brief_prefetch_fields )
93
-
94
- return super ().get_queryset ()
95
-
96
- def initialize_request (self , request , * args , ** kwargs ):
97
- # Check if brief=True has been passed
98
- if request .method == 'GET' and request .GET .get ('brief' ):
99
- self .brief = True
100
-
101
- return super ().initialize_request (request , * args , ** kwargs )
102
-
103
- def initial (self , request , * args , ** kwargs ):
104
- super ().initial (request , * args , ** kwargs )
105
-
106
- if not request .user .is_authenticated :
107
- return
108
-
109
- # Restrict the view's QuerySet to allow only the permitted objects
110
- action = HTTP_ACTIONS [request .method ]
111
- if action :
112
- self .queryset = self .queryset .restrict (request .user , action )
113
-
114
87
def dispatch (self , request , * args , ** kwargs ):
115
- logger = logging .getLogger ('netbox.api.views.ModelViewSet ' )
88
+ logger = logging .getLogger (f 'netbox.api.views.{ self . __class__ . __name__ } ' )
116
89
117
90
try :
118
91
return super ().dispatch (request , * args , ** kwargs )
@@ -136,21 +109,11 @@ def dispatch(self, request, *args, **kwargs):
136
109
** kwargs
137
110
)
138
111
139
- def list (self , request , * args , ** kwargs ):
140
- # Overrides ListModelMixin to allow processing ExportTemplates.
141
- if 'export' in request .GET :
142
- content_type = ContentType .objects .get_for_model (self .get_serializer_class ().Meta .model )
143
- et = ExportTemplate .objects .filter (content_types = content_type , name = request .GET ['export' ]).first ()
144
- if et is None :
145
- raise Http404
146
- queryset = self .filter_queryset (self .get_queryset ())
147
- return et .render_to_response (queryset )
148
-
149
- return super ().list (request , * args , ** kwargs )
112
+ # Creates
150
113
151
114
def perform_create (self , serializer ):
152
115
model = self .queryset .model
153
- logger = logging .getLogger ('netbox.api.views.ModelViewSet ' )
116
+ logger = logging .getLogger (f 'netbox.api.views.{ self . __class__ . __name__ } ' )
154
117
logger .info (f"Creating new { model ._meta .verbose_name } " )
155
118
156
119
# Enforce object-level permissions on save()
@@ -161,14 +124,16 @@ def perform_create(self, serializer):
161
124
except ObjectDoesNotExist :
162
125
raise PermissionDenied ()
163
126
127
+ # Updates
128
+
164
129
def update (self , request , * args , ** kwargs ):
165
130
# Hotwire get_object() to ensure we save a pre-change snapshot
166
131
self .get_object = self .get_object_with_snapshot
167
132
return super ().update (request , * args , ** kwargs )
168
133
169
134
def perform_update (self , serializer ):
170
135
model = self .queryset .model
171
- logger = logging .getLogger ('netbox.api.views.ModelViewSet ' )
136
+ logger = logging .getLogger (f 'netbox.api.views.{ self . __class__ . __name__ } ' )
172
137
logger .info (f"Updating { model ._meta .verbose_name } { serializer .instance } (PK: { serializer .instance .pk } )" )
173
138
174
139
# Enforce object-level permissions on save()
@@ -179,14 +144,16 @@ def perform_update(self, serializer):
179
144
except ObjectDoesNotExist :
180
145
raise PermissionDenied ()
181
146
147
+ # Deletes
148
+
182
149
def destroy (self , request , * args , ** kwargs ):
183
150
# Hotwire get_object() to ensure we save a pre-change snapshot
184
151
self .get_object = self .get_object_with_snapshot
185
152
return super ().destroy (request , * args , ** kwargs )
186
153
187
154
def perform_destroy (self , instance ):
188
155
model = self .queryset .model
189
- logger = logging .getLogger ('netbox.api.views.ModelViewSet ' )
156
+ logger = logging .getLogger (f 'netbox.api.views.{ self . __class__ . __name__ } ' )
190
157
logger .info (f"Deleting { model ._meta .verbose_name } { instance } (PK: { instance .pk } )" )
191
158
192
159
return super ().perform_destroy (instance )
0 commit comments