Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 23 additions & 1 deletion app/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from host import models
from rest_framework import serializers
from django.urls import reverse


class CutoutField(serializers.RelatedField):
Expand All @@ -18,7 +19,6 @@ class Meta:
"photometric_class",
"processing_status",
"added_by"
# "host",
]

aliases = serializers.SerializerMethodField()
Expand Down Expand Up @@ -79,13 +79,35 @@ def get_transient(self, obj):
def get_host(self, obj):
return obj.host.name if obj.host else None

class DownloadURLField(serializers.SerializerMethodField):
#Like SerializerMethodField, but also passes self.field_name to the method, avoid hardcoding per-field wrappers.
def to_representation(self, obj):
method = getattr(self.parent, self.method_name)
return method(obj, self.field_name)

class SEDFittingResultSerializer(serializers.ModelSerializer):
chains_file = DownloadURLField(method_name='get_download_file')
model_file = DownloadURLField(method_name='get_download_file')
percentiles_file = DownloadURLField(method_name='get_download_file')
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_download_file(instance, "chains")
ret['model_file'] = self.get_download_file(instance, "model")
ret['percentiles_file'] = self.get_download_file(instance, "percentiles")
return ret

def get_download_file(self, obj, file_type):
request = self.context["request"]
return request.build_absolute_uri(
reverse("sedfittingresult-download", kwargs={"pk": obj.pk, "file_type": file_type})
)


class CutoutSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
23 changes: 22 additions & 1 deletion app/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from django.contrib.auth.decorators import login_required, permission_required
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
Expand Down Expand Up @@ -49,6 +49,17 @@
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

Expand Down Expand Up @@ -188,6 +199,16 @@ class SEDFittingResultViewSet(viewsets.ReadOnlyModelViewSet):
filter_backends = (DjangoFilterBackend,)
filterset_class = SEDFittingResultFilter

allowed_file_types = {'chains', 'model', 'percentiles'}

@action(methods=['get'], detail=True, url_path=r"download/(?P<file_type>[^/.]+)")
def download(self, request, pk=None, file_type=None):
if file_type not in self.allowed_file_types:
return Response({'error': "unknown file type"}, status=400)
sed_result = self.get_object()
file_field = getattr(sed_result, f"{file_type}_file")
return stream_download_file(file_field.name)


class TaskRegisterViewSet(viewsets.ReadOnlyModelViewSet):
queryset = TaskRegister.objects.all()
Expand Down
3 changes: 0 additions & 3 deletions app/host/static/robots.txt
Original file line number Diff line number Diff line change
@@ -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/
Expand Down
10 changes: 0 additions & 10 deletions app/host/templates/host/sed_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ <h4>Spectral Energy Distribution</h4>
}
</script>
{% endif %}
<!--
<button style="margin-bottom:5px;" type="button" class="report" onclick="window.open('{% url 'download_modelfit' transient.name 'local' %}','_self')">
<span>Download Best-Fit Model</span>
</button>
-->
</div>
</div>

Expand Down Expand Up @@ -120,11 +115,6 @@ <h4>Spectral Energy Distribution</h4>
}
</script>
{% endif %}
<!--
<button style="margin-bottom:5px;" type="button" class="report" onclick="window.open('{% url 'download_modelfit' transient.name 'global' %}','_self')">
<span>Download Best-Fit Model</span>
</button>
-->
</div>
</div>
</div>
Expand Down
20 changes: 0 additions & 20 deletions app/host/templates/host/sed_inference_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ <h4>Host SED inference</h4>
<h4>Local parameter details</h4>
<h6><a href="https://blast.readthedocs.io/en/latest/usage/sed_params.html">Documentation</a></h6>
{% if local_sed_results %}
<!--
<div>
<button style="margin-bottom:5px;" type="button" class="report" onclick="window.open('{% url 'download_chains' transient.name 'local' %}','_self')">
<span>Download Chains</span>
</button>
<button style="margin-bottom:5px;" type="button" class="report" onclick="window.open('{% url 'download_percentiles' transient.name 'local' %}','_self')">
<span>Download Percentiles</span>
</button>
</div>
-->

<br>

Expand Down Expand Up @@ -108,16 +98,6 @@ <h5>Binned star formation history</h5>
<h4>Global parameter details</h4>
<h6><a href="https://blast.readthedocs.io/en/latest/usage/sed_params.html">Documentation</a></h6>
{% if global_sed_results %}
<!--
<div>
<button style="margin-bottom:5px;" type="button" class="report" onclick="window.open('{% url 'download_chains' transient.name 'global' %}','_self')">
<span>Download Chains</span>
</button>
<button style="margin-bottom:5px;" type="button" class="report" onclick="window.open('{% url 'download_percentiles' transient.name 'global' %}','_self')">
<span>Download Percentiles</span>
</button>
</div>
-->

<br>

Expand Down
12 changes: 6 additions & 6 deletions app/host/templates/host/transient_actions.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
<a class="dropdown-item" title="Export without files (legacy)" href="/api/transient/get/{{transient.name}}?format=json"><i class="bi bi-filetype-json"></i> Export without files (legacy .json)</a>
{% if local_sed_results %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" title="Local parameter confidence intervals" href="{% url 'download_modelfit' transient.name 'local' %}"><i class="bi bi-graph-up"></i> Local SED best-fit model (.npz)</a>
<a class="dropdown-item" title="Local parameter confidence intervals" href="{% url 'download_percentiles' transient.name 'local' %}"><i class="bi bi-percent"></i> Local parameter confidence intervals (.npz)</a>
<a class="dropdown-item" title="Local parameter estimation chains" href="{% url 'download_chains' transient.name 'local' %}"><i class="bi bi-link"></i> Local parameter estimation chains (.npz)</a>
<a class="dropdown-item" title="Local SED best-fit model" href="{% url 'sedfittingresult-download' transient.id 'model' %}"><i class="bi bi-graph-up"></i> Local SED best-fit model (.npz)</a>
<a class="dropdown-item" title="Local parameter confidence intervals" href="{% url 'sedfittingresult-download' transient.id 'percentiles' %}"><i class="bi bi-percent"></i> Local parameter confidence intervals (.npz)</a>
<a class="dropdown-item" title="Local parameter estimation chains" href="{% url 'sedfittingresult-download' transient.id 'chains' %}"><i class="bi bi-link"></i> Local parameter estimation chains (.npz)</a>
{% endif %}
{% if global_sed_results %}
<div class="dropdown-divider"></div>
<a class="dropdown-item" title="Global parameter confidence intervals" href="{% url 'download_modelfit' transient.name 'global' %}"><i class="bi bi-graph-up"></i> Global SED best-fit model (.npz)</a>
<a class="dropdown-item" title="Global parameter confidence intervals" href="{% url 'download_percentiles' transient.name 'global' %}"><i class="bi bi-percent"></i> Global parameter confidence intervals (.npz)</a>
<a class="dropdown-item" title="Global parameter estimation chains" href="{% url 'download_chains' transient.name 'global' %}"><i class="bi bi-link"></i> Global parameter estimation chains (.npz)</a>
<a class="dropdown-item" title=Global SED best-fit model" href="{% url 'sedfittingresult-download' transient.id 'model' %}"><i class="bi bi-graph-up"></i> Global SED best-fit model (.npz)</a>
<a class="dropdown-item" title="Global parameter confidence intervals" href="{% url 'sedfittingresult-download' transient.id 'percentiles' %}"><i class="bi bi-percent"></i> Global parameter confidence intervals (.npz)</a>
<a class="dropdown-item" title="Global parameter estimation chains" href="{% url 'sedfittingresult-download' transient.id 'chains' %}"><i class="bi bi-link"></i> Global parameter estimation chains (.npz)</a>
{% endif %}
</div>
</li>
Expand Down
17 changes: 1 addition & 16 deletions app/host/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<slug:transient_name>/""", views.results, name="results"),
path(
f"""{base_path}download_chains/<slug:slug>/<str:aperture_type>/""",
views.download_chains,
name="download_chains",
),
path(
f"""{base_path}download_modelfit/<slug:slug>/<str:aperture_type>/""",
views.download_modelfit,
name="download_modelfit",
),
path(
f"""{base_path}download_percentiles/<slug:slug>/<str:aperture_type>/""",
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),
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 0 additions & 35 deletions app/host/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down
7 changes: 7 additions & 0 deletions nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ server {
alias /static/robots.txt;
}

location /api {
proxy_pass http://app/api;
proxy_set_header Host $host:4000 ;

# See the reasoning above for customizing the header
# Correct download url showing up in api serialized output
}
}
7 changes: 7 additions & 0 deletions nginx/default_slim.conf
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ server {
alias /static/robots.txt;
}

location /api {
proxy_pass http://app/api;
proxy_set_header Host $host:4000 ;

# See the reasoning above for customizing the header
# Correct download url showing up in api serialized output
}
}