diff --git a/CHANGELOG.md b/CHANGELOG.md index cb93af13..b4dcba4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ Types of changes: - `Fixed`: for any bug fixes. - `Security`: in case of vulnerabilities. +## [x.y.z] + +- Added new download functions to /api/sedfittingresult, replacing view functions: download_chains, download_modelfit, download_percentiles. +- New download URL: /api/sedfittingresult/{id}/download/{download-type}/ +- Fixed download URLs on /api/sedfittingresult using new download functions, rerouted transient download on front-end to these functions. Functionality from front-end is identical. + + ## [1.12.0] ### Added diff --git a/app/api/serializers.py b/app/api/serializers.py index fd209b0a..9d57b4f8 100644 --- a/app/api/serializers.py +++ b/app/api/serializers.py @@ -1,5 +1,6 @@ from host import models from rest_framework import serializers +from django.urls import reverse class CutoutField(serializers.RelatedField): @@ -18,7 +19,6 @@ class Meta: "photometric_class", "processing_status", "added_by" -# "host", ] aliases = serializers.SerializerMethodField() @@ -81,11 +81,62 @@ def get_host(self, obj): class SEDFittingResultSerializer(serializers.ModelSerializer): + chains_file = serializers.SerializerMethodField() + model_file = serializers.SerializerMethodField() + percentiles_file = serializers.SerializerMethodField() class Meta: model = models.SEDFittingResult depth = 1 exclude = ["log_tau_16", "log_tau_50", "log_tau_84", "posterior"] + def to_representation(self, instance): + # """Hardcode download URL""" + ret = super().to_representation(instance) + ret['chains_file'] = self.get_chains_file(instance) + ret['model_file'] = self.get_model_file(instance) + ret['percentiles_file'] = self.get_percentiles_file(instance) + + return ret + + def get_chains_file(self, obj): + request = self.context["request"] + + return request.build_absolute_uri( + reverse( + "sedfittingresult-download", + kwargs={ + "pk": obj.pk, + "file_type": "chains", + }, + ) + ) + + def get_model_file(self, obj): + request = self.context["request"] + + return request.build_absolute_uri( + reverse( + "sedfittingresult-download", + kwargs={ + "pk": obj.pk, + "file_type": "model", + }, + ) + ) + + def get_percentiles_file(self, obj): + request = self.context["request"] + + return request.build_absolute_uri( + reverse( + "sedfittingresult-download", + kwargs={ + "pk": obj.pk, + "file_type": "percentiles", + }, + ) + ) + class CutoutSerializer(serializers.ModelSerializer): class Meta: diff --git a/app/api/views.py b/app/api/views.py index d2445d8f..2a255f0c 100644 --- a/app/api/views.py +++ b/app/api/views.py @@ -13,9 +13,10 @@ from django.shortcuts import render from django_filters.rest_framework import DjangoFilterBackend from django.contrib.auth.decorators import login_required, permission_required +from django.shortcuts import get_object_or_404 from rest_framework import status from rest_framework import viewsets -from rest_framework.decorators import api_view +from rest_framework.decorators import api_view, action from rest_framework.response import Response from host.object_store import ObjectStore from host.models import Aperture @@ -48,6 +49,16 @@ from host.log import get_logger logger = get_logger(__name__) +def stream_download_file(file_path): + # Stream the data file from the S3 bucket + s3 = ObjectStore() + object_key = os.path.join(settings.S3_BASE_PATH, file_path.strip('/')) + filename = os.path.basename(file_path) + obj_stream = s3.stream_object(object_key) + response = StreamingHttpResponse(streaming_content=obj_stream) + response["Content-Disposition"] = f"attachment; filename={filename}" + return response + ############################################################ # Filter Sets @@ -188,6 +199,19 @@ class SEDFittingResultViewSet(viewsets.ReadOnlyModelViewSet): filter_backends = (DjangoFilterBackend,) filterset_class = SEDFittingResultFilter + @action(methods=['get'], detail=True, url_path=r"download/(?P[^/.]+)") + def download(self, request, pk=None, file_type=None): + sed_result = self.get_object() + if file_type == 'chains': + file_field = sed_result.chains_file + elif file_type == 'model': + file_field = sed_result.model_file + elif file_type == 'percentiles': + file_field = sed_result.percentiles_file + else: + return Response({'error':"unknown file type"}, status=400) + + return stream_download_file(file_field.name) class TaskRegisterViewSet(viewsets.ReadOnlyModelViewSet): queryset = TaskRegister.objects.all() diff --git a/app/host/static/robots.txt b/app/host/static/robots.txt index afe0fb4d..978f054c 100644 --- a/app/host/static/robots.txt +++ b/app/host/static/robots.txt @@ -1,9 +1,6 @@ User-agent: * Disallow: /transient/ Disallow: /add/ -Disallow: /download_chains/ -Disallow: /download_modelfit/ -Disallow: /download_percentiles/ Disallow: /retrigger_transient/ Disallow: /reprocess_transient/ Disallow: /report_issue/ diff --git a/app/host/templates/host/sed_card.html b/app/host/templates/host/sed_card.html index 2851d130..c0a1e060 100644 --- a/app/host/templates/host/sed_card.html +++ b/app/host/templates/host/sed_card.html @@ -63,11 +63,6 @@

Spectral Energy Distribution

} {% endif %} - @@ -120,11 +115,6 @@

Spectral Energy Distribution

} {% endif %} - diff --git a/app/host/templates/host/sed_inference_card.html b/app/host/templates/host/sed_inference_card.html index e4f1f917..c9c031ee 100644 --- a/app/host/templates/host/sed_inference_card.html +++ b/app/host/templates/host/sed_inference_card.html @@ -24,16 +24,6 @@

Host SED inference

Local parameter details

Documentation
{% if local_sed_results %} -
@@ -108,16 +98,6 @@
Binned star formation history

Global parameter details

Documentation
{% if global_sed_results %} -
diff --git a/app/host/templates/host/transient_actions.html b/app/host/templates/host/transient_actions.html index bec471cc..b7afd5cd 100644 --- a/app/host/templates/host/transient_actions.html +++ b/app/host/templates/host/transient_actions.html @@ -14,15 +14,15 @@ Export without files (legacy .json) {% if local_sed_results %} - Local SED best-fit model (.npz) - Local parameter confidence intervals (.npz) - Local parameter estimation chains (.npz) + Local SED best-fit model (.npz) + Local parameter confidence intervals (.npz) + Local parameter estimation chains (.npz) {% endif %} {% if global_sed_results %} - Global SED best-fit model (.npz) - Global parameter confidence intervals (.npz) - Global parameter estimation chains (.npz) + Global SED best-fit model (.npz) + Global parameter confidence intervals (.npz) + Global parameter estimation chains (.npz) {% endif %} diff --git a/app/host/urls.py b/app/host/urls.py index f468ecd5..6fef55cf 100644 --- a/app/host/urls.py +++ b/app/host/urls.py @@ -21,21 +21,6 @@ path(f"""{base_path}transients/""", views.transient_list, name="transient_list"), path(f"""{base_path}add/""", views.add_transient, name="add_transient"), path(f"""{base_path}transients//""", views.results, name="results"), - path( - f"""{base_path}download_chains///""", - views.download_chains, - name="download_chains", - ), - path( - f"""{base_path}download_modelfit///""", - views.download_modelfit, - name="download_modelfit", - ), - path( - f"""{base_path}download_percentiles///""", - views.download_percentiles, - name="download_percentiles", - ), path(f"""{base_path}acknowledgements/""", views.acknowledgements, name="acknowledgements"), path(f"""{base_path}team/""", views.team, name="team"), path(f"""{base_path}""", views.home), @@ -72,7 +57,7 @@ router.register(r"cutout", api.views.CutoutViewSet) router.register(r"filter", api.views.FilterViewSet) router.register(r"aperturephotometry", api.views.AperturePhotometryViewSet) -router.register(r"sedfittingresult", api.views.SEDFittingResultViewSet) +router.register(r"sedfittingresult", api.views.SEDFittingResultViewSet, basename="sedfittingresult") router.register(r"taskregister", api.views.TaskRegisterViewSet) router.register(r"task", api.views.TaskViewSet) router.register(r"host", api.views.HostViewSet) diff --git a/app/host/views.py b/app/host/views.py index b052132e..267b8de3 100644 --- a/app/host/views.py +++ b/app/host/views.py @@ -634,41 +634,6 @@ def compile_sed_results(transient, category, scope): return render(request, "results.html", context) -def stream_sed_output_file(file_path): - # Stream the data file from the S3 bucket - s3 = ObjectStore() - object_key = os.path.join(settings.S3_BASE_PATH, file_path.strip('/')) - filename = os.path.basename(file_path) - obj_stream = s3.stream_object(object_key) - response = StreamingHttpResponse(streaming_content=obj_stream) - response["Content-Disposition"] = f"attachment; filename={filename}" - return response - - -@log_usage_metric() -def download_chains(request, slug, aperture_type): - sed_result = get_object_or_404( - SEDFittingResult, transient__name=slug, aperture__type=aperture_type - ) - return stream_sed_output_file(sed_result.chains_file.name) - - -@log_usage_metric() -def download_modelfit(request, slug, aperture_type): - sed_result = get_object_or_404( - SEDFittingResult, transient__name=slug, aperture__type=aperture_type - ) - return stream_sed_output_file(sed_result.model_file.name) - - -@log_usage_metric() -def download_percentiles(request, slug, aperture_type): - sed_result = get_object_or_404( - SEDFittingResult, transient__name=slug, aperture__type=aperture_type - ) - return stream_sed_output_file(sed_result.percentiles_file.name) - - @log_usage_metric() def acknowledgements(request): context = {}