Fixes: #19669 - Add an API endpoint to download image attachments

This commit is contained in:
Daniel Sheppard 2025-07-24 14:16:02 -05:00
parent ffa9a52667
commit f48e1cb534

View File

@ -1,5 +1,7 @@
from django.conf import settings
from django.http import Http404
from django.shortcuts import get_object_or_404
from django.views.static import serve
from django_rq.queues import get_connection
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import status
@ -32,6 +34,7 @@ class ExtrasRootView(APIRootView):
"""
Extras API root view
"""
def get_view_name(self):
return 'Extras'
@ -40,6 +43,7 @@ class ExtrasRootView(APIRootView):
# EventRules
#
class EventRuleViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = EventRule.objects.all()
@ -51,6 +55,7 @@ class EventRuleViewSet(NetBoxModelViewSet):
# Webhooks
#
class WebhookViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = Webhook.objects.all()
@ -62,6 +67,7 @@ class WebhookViewSet(NetBoxModelViewSet):
# Custom fields
#
class CustomFieldViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = CustomField.objects.select_related('choice_set')
@ -89,9 +95,7 @@ class CustomFieldChoiceSetViewSet(NetBoxModelViewSet):
# Paginate data
if page := self.paginate_queryset(choices):
data = [
{'id': c[0], 'display': c[1]} for c in page
]
data = [{'id': c[0], 'display': c[1]} for c in page]
else:
data = []
@ -102,6 +106,7 @@ class CustomFieldChoiceSetViewSet(NetBoxModelViewSet):
# Custom links
#
class CustomLinkViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = CustomLink.objects.all()
@ -113,6 +118,7 @@ class CustomLinkViewSet(NetBoxModelViewSet):
# Export templates
#
class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ExportTemplate.objects.all()
@ -124,6 +130,7 @@ class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
# Saved filters
#
class SavedFilterViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = SavedFilter.objects.all()
@ -135,6 +142,7 @@ class SavedFilterViewSet(NetBoxModelViewSet):
# Table Configs
#
class TableConfigViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = TableConfig.objects.all()
@ -146,6 +154,7 @@ class TableConfigViewSet(NetBoxModelViewSet):
# Bookmarks
#
class BookmarkViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = Bookmark.objects.all()
@ -157,6 +166,7 @@ class BookmarkViewSet(NetBoxModelViewSet):
# Notifications & subscriptions
#
class NotificationViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = Notification.objects.all()
@ -178,6 +188,7 @@ class SubscriptionViewSet(NetBoxModelViewSet):
# Tags
#
class TagViewSet(NetBoxModelViewSet):
queryset = Tag.objects.all()
serializer_class = serializers.TagSerializer
@ -194,17 +205,30 @@ class TaggedItemViewSet(RetrieveModelMixin, ListModelMixin, BaseViewSet):
# Image attachments
#
class ImageAttachmentViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = ImageAttachment.objects.all()
serializer_class = serializers.ImageAttachmentSerializer
filterset_class = filtersets.ImageAttachmentFilterSet
@action(
methods=['GET'],
detail=True,
url_path='download',
url_name='download',
)
def download(self, request, pk, *args, **kwargs):
obj = get_object_or_404(self.queryset, pk=pk)
# Render and return the elevation as an SVG drawing with the correct content type
return serve(request, obj.image.path, document_root=settings.MEDIA_ROOT)
#
# Journal entries
#
class JournalEntryViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata
queryset = JournalEntry.objects.all()
@ -216,6 +240,7 @@ class JournalEntryViewSet(NetBoxModelViewSet):
# Config contexts
#
class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
queryset = ConfigContext.objects.all()
serializer_class = serializers.ConfigContextSerializer
@ -226,6 +251,7 @@ class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
# Config templates
#
class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
queryset = ConfigTemplate.objects.all()
serializer_class = serializers.ConfigTemplateSerializer
@ -247,6 +273,7 @@ class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxMo
# Scripts
#
@extend_schema_view(
update=extend_schema(request=serializers.ScriptInputSerializer),
partial_update=extend_schema(request=serializers.ScriptInputSerializer),
@ -287,10 +314,7 @@ class ScriptViewSet(ModelViewSet):
raise PermissionDenied("This user does not have permission to run scripts.")
script = self._get_script(pk)
input_serializer = serializers.ScriptInputSerializer(
data=request.data,
context={'script': script}
)
input_serializer = serializers.ScriptInputSerializer(data=request.data, context={'script': script})
# Check that at least one RQ worker is running
if not Worker.count(get_connection('default')):
@ -305,7 +329,7 @@ class ScriptViewSet(ModelViewSet):
commit=input_serializer.data['commit'],
job_timeout=script.python_class.job_timeout,
schedule_at=input_serializer.validated_data.get('schedule_at'),
interval=input_serializer.validated_data.get('interval')
interval=input_serializer.validated_data.get('interval'),
)
serializer = serializers.ScriptDetailSerializer(script, context={'request': request})
@ -318,10 +342,12 @@ class ScriptViewSet(ModelViewSet):
# Object types
#
class ObjectTypeViewSet(ReadOnlyModelViewSet):
"""
Read-only list of ObjectTypes.
"""
permission_classes = [IsAuthenticatedOrLoginNotRequired]
queryset = ObjectType.objects.order_by('app_label', 'model')
serializer_class = serializers.ObjectTypeSerializer
@ -332,6 +358,7 @@ class ObjectTypeViewSet(ReadOnlyModelViewSet):
# User dashboard
#
class DashboardView(RetrieveUpdateDestroyAPIView):
queryset = Dashboard.objects.all()
serializer_class = serializers.DashboardSerializer