Skip to content

Commit f48e1cb

Browse files
committed
Fixes: #19669 - Add an API endpoint to download image attachments
1 parent ffa9a52 commit f48e1cb

File tree

1 file changed

+35
-8
lines changed

1 file changed

+35
-8
lines changed

netbox/extras/api/views.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from django.conf import settings
12
from django.http import Http404
23
from django.shortcuts import get_object_or_404
4+
from django.views.static import serve
35
from django_rq.queues import get_connection
46
from drf_spectacular.utils import extend_schema, extend_schema_view
57
from rest_framework import status
@@ -32,6 +34,7 @@ class ExtrasRootView(APIRootView):
3234
"""
3335
Extras API root view
3436
"""
37+
3538
def get_view_name(self):
3639
return 'Extras'
3740

@@ -40,6 +43,7 @@ def get_view_name(self):
4043
# EventRules
4144
#
4245

46+
4347
class EventRuleViewSet(NetBoxModelViewSet):
4448
metadata_class = ContentTypeMetadata
4549
queryset = EventRule.objects.all()
@@ -51,6 +55,7 @@ class EventRuleViewSet(NetBoxModelViewSet):
5155
# Webhooks
5256
#
5357

58+
5459
class WebhookViewSet(NetBoxModelViewSet):
5560
metadata_class = ContentTypeMetadata
5661
queryset = Webhook.objects.all()
@@ -62,6 +67,7 @@ class WebhookViewSet(NetBoxModelViewSet):
6267
# Custom fields
6368
#
6469

70+
6571
class CustomFieldViewSet(NetBoxModelViewSet):
6672
metadata_class = ContentTypeMetadata
6773
queryset = CustomField.objects.select_related('choice_set')
@@ -89,9 +95,7 @@ def choices(self, request, pk):
8995

9096
# Paginate data
9197
if page := self.paginate_queryset(choices):
92-
data = [
93-
{'id': c[0], 'display': c[1]} for c in page
94-
]
98+
data = [{'id': c[0], 'display': c[1]} for c in page]
9599
else:
96100
data = []
97101

@@ -102,6 +106,7 @@ def choices(self, request, pk):
102106
# Custom links
103107
#
104108

109+
105110
class CustomLinkViewSet(NetBoxModelViewSet):
106111
metadata_class = ContentTypeMetadata
107112
queryset = CustomLink.objects.all()
@@ -113,6 +118,7 @@ class CustomLinkViewSet(NetBoxModelViewSet):
113118
# Export templates
114119
#
115120

121+
116122
class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
117123
metadata_class = ContentTypeMetadata
118124
queryset = ExportTemplate.objects.all()
@@ -124,6 +130,7 @@ class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
124130
# Saved filters
125131
#
126132

133+
127134
class SavedFilterViewSet(NetBoxModelViewSet):
128135
metadata_class = ContentTypeMetadata
129136
queryset = SavedFilter.objects.all()
@@ -135,6 +142,7 @@ class SavedFilterViewSet(NetBoxModelViewSet):
135142
# Table Configs
136143
#
137144

145+
138146
class TableConfigViewSet(NetBoxModelViewSet):
139147
metadata_class = ContentTypeMetadata
140148
queryset = TableConfig.objects.all()
@@ -146,6 +154,7 @@ class TableConfigViewSet(NetBoxModelViewSet):
146154
# Bookmarks
147155
#
148156

157+
149158
class BookmarkViewSet(NetBoxModelViewSet):
150159
metadata_class = ContentTypeMetadata
151160
queryset = Bookmark.objects.all()
@@ -157,6 +166,7 @@ class BookmarkViewSet(NetBoxModelViewSet):
157166
# Notifications & subscriptions
158167
#
159168

169+
160170
class NotificationViewSet(NetBoxModelViewSet):
161171
metadata_class = ContentTypeMetadata
162172
queryset = Notification.objects.all()
@@ -178,6 +188,7 @@ class SubscriptionViewSet(NetBoxModelViewSet):
178188
# Tags
179189
#
180190

191+
181192
class TagViewSet(NetBoxModelViewSet):
182193
queryset = Tag.objects.all()
183194
serializer_class = serializers.TagSerializer
@@ -194,17 +205,30 @@ class TaggedItemViewSet(RetrieveModelMixin, ListModelMixin, BaseViewSet):
194205
# Image attachments
195206
#
196207

208+
197209
class ImageAttachmentViewSet(NetBoxModelViewSet):
198210
metadata_class = ContentTypeMetadata
199211
queryset = ImageAttachment.objects.all()
200212
serializer_class = serializers.ImageAttachmentSerializer
201213
filterset_class = filtersets.ImageAttachmentFilterSet
202214

215+
@action(
216+
methods=['GET'],
217+
detail=True,
218+
url_path='download',
219+
url_name='download',
220+
)
221+
def download(self, request, pk, *args, **kwargs):
222+
obj = get_object_or_404(self.queryset, pk=pk)
223+
# Render and return the elevation as an SVG drawing with the correct content type
224+
return serve(request, obj.image.path, document_root=settings.MEDIA_ROOT)
225+
203226

204227
#
205228
# Journal entries
206229
#
207230

231+
208232
class JournalEntryViewSet(NetBoxModelViewSet):
209233
metadata_class = ContentTypeMetadata
210234
queryset = JournalEntry.objects.all()
@@ -216,6 +240,7 @@ class JournalEntryViewSet(NetBoxModelViewSet):
216240
# Config contexts
217241
#
218242

243+
219244
class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
220245
queryset = ConfigContext.objects.all()
221246
serializer_class = serializers.ConfigContextSerializer
@@ -226,6 +251,7 @@ class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
226251
# Config templates
227252
#
228253

254+
229255
class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
230256
queryset = ConfigTemplate.objects.all()
231257
serializer_class = serializers.ConfigTemplateSerializer
@@ -247,6 +273,7 @@ def render(self, request, pk):
247273
# Scripts
248274
#
249275

276+
250277
@extend_schema_view(
251278
update=extend_schema(request=serializers.ScriptInputSerializer),
252279
partial_update=extend_schema(request=serializers.ScriptInputSerializer),
@@ -287,10 +314,7 @@ def post(self, request, pk):
287314
raise PermissionDenied("This user does not have permission to run scripts.")
288315

289316
script = self._get_script(pk)
290-
input_serializer = serializers.ScriptInputSerializer(
291-
data=request.data,
292-
context={'script': script}
293-
)
317+
input_serializer = serializers.ScriptInputSerializer(data=request.data, context={'script': script})
294318

295319
# Check that at least one RQ worker is running
296320
if not Worker.count(get_connection('default')):
@@ -305,7 +329,7 @@ def post(self, request, pk):
305329
commit=input_serializer.data['commit'],
306330
job_timeout=script.python_class.job_timeout,
307331
schedule_at=input_serializer.validated_data.get('schedule_at'),
308-
interval=input_serializer.validated_data.get('interval')
332+
interval=input_serializer.validated_data.get('interval'),
309333
)
310334
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
311335

@@ -318,10 +342,12 @@ def post(self, request, pk):
318342
# Object types
319343
#
320344

345+
321346
class ObjectTypeViewSet(ReadOnlyModelViewSet):
322347
"""
323348
Read-only list of ObjectTypes.
324349
"""
350+
325351
permission_classes = [IsAuthenticatedOrLoginNotRequired]
326352
queryset = ObjectType.objects.order_by('app_label', 'model')
327353
serializer_class = serializers.ObjectTypeSerializer
@@ -332,6 +358,7 @@ class ObjectTypeViewSet(ReadOnlyModelViewSet):
332358
# User dashboard
333359
#
334360

361+
335362
class DashboardView(RetrieveUpdateDestroyAPIView):
336363
queryset = Dashboard.objects.all()
337364
serializer_class = serializers.DashboardSerializer

0 commit comments

Comments
 (0)