diff --git a/analysis/tasks/analysis_grid_export_tasks.py b/analysis/tasks/analysis_grid_export_tasks.py index b3d24b731..ee473c037 100644 --- a/analysis/tasks/analysis_grid_export_tasks.py +++ b/analysis/tasks/analysis_grid_export_tasks.py @@ -29,11 +29,13 @@ def _write_cached_generated_file(cgf: CachedGeneratedFile, filename, file_iterat for line in file_iterator(): f.write(line) # Already has newline cgf.filename = media_root_filename + cgf.task_status = "SUCCESS" cgf.generate_end = timezone.now() logging.info("Wrote %s", media_root_filename) except Exception as e: logging.error("Failed to write %s: %s", media_root_filename, e) cgf.exception = str(e) + cgf.task_status = "FAILURE" cgf.save() diff --git a/analysis/views/views.py b/analysis/views/views.py index bf9bacef6..1b34929ab 100644 --- a/analysis/views/views.py +++ b/analysis/views/views.py @@ -609,14 +609,14 @@ def node_graph(request, analysis_id, node_id, graph_type_id, cmap): get_node_subclass_or_404(request.user, node_id) # Permission check node_graph_type = NodeGraphType.objects.get(pk=graph_type_id) cached_graph = graphcache.async_graph(node_graph_type.graph_class, cmap, node_id) - return HttpResponseRedirect(reverse("cached_generated_file_check", kwargs={"cgf_id": cached_graph.id})) + return redirect(cached_graph) def column_summary_boxplot(request, analysis_id, node_id, label, variant_column): get_node_subclass_or_404(request.user, node_id) # Permission check graph_class_name = full_class_name(ColumnBoxplotGraph) cached_graph = graphcache.async_graph(graph_class_name, node_id, label, variant_column) - return HttpResponseRedirect(reverse("cached_generated_file_check", kwargs={"cgf_id": cached_graph.id})) + return redirect(cached_graph) def cohort_zygosity_filters(request, analysis_id, node_id, cohort_id): diff --git a/analysis/views/views_grid.py b/analysis/views/views_grid.py index e1b1f9bd8..1f5f4cb53 100644 --- a/analysis/views/views_grid.py +++ b/analysis/views/views_grid.py @@ -6,6 +6,7 @@ from django.core.cache import cache from django.http import JsonResponse from django.http.response import StreamingHttpResponse, HttpResponseRedirect +from django.shortcuts import redirect from django.urls import reverse from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page @@ -118,7 +119,7 @@ def cohort_grid_export(request, cohort_id, export_type): params_hash = get_grid_downloadable_file_params_hash(cohort_id, export_type) task = export_cohort_to_downloadable_file.si(cohort_id, export_type) cgf = CachedGeneratedFile.get_or_create_and_launch("export_cohort_to_downloadable_file", params_hash, task) - return JsonResponse({"celery_task": cgf.task_id}) + return redirect(cgf) def sample_grid_export(request, sample_id, export_type): diff --git a/library/django_utils/__init__.py b/library/django_utils/__init__.py index 346b4309b..703ef76d6 100644 --- a/library/django_utils/__init__.py +++ b/library/django_utils/__init__.py @@ -1,5 +1,6 @@ import datetime import operator +import os from functools import reduce from functools import wraps, partial @@ -39,6 +40,13 @@ def get_url_from_view_path(view_path): return f'{protocol}://{current_site.domain}{view_path}' +def get_url_from_media_root_filename(filename): + media_root_with_slash = os.path.join(settings.MEDIA_ROOT, "") + if not filename.startswith(media_root_with_slash): + raise ValueError(f"'{filename}' must start with MEDIA_ROOT: {media_root_with_slash}") + return os.path.join(settings.MEDIA_URL, filename[len(media_root_with_slash):]) + + def add_save_message(request, valid, name, created=False): action = "created" if created else "saved" diff --git a/pedigree/views.py b/pedigree/views.py index 13b2b812a..6858c463d 100644 --- a/pedigree/views.py +++ b/pedigree/views.py @@ -4,7 +4,7 @@ from django.forms.formsets import formset_factory from django.forms.models import ModelChoiceField from django.http.response import HttpResponseRedirect -from django.shortcuts import get_object_or_404, render +from django.shortcuts import get_object_or_404, render, redirect from django.urls.base import reverse from library.utils import full_class_name @@ -37,7 +37,7 @@ def pedigree_chart(request, ped_file_id): ped_file = PedFile.get_for_user(request.user, ped_file_id) # Make sure we can access it graph_class_name = full_class_name(PedigreeChart) cached_graph = graphcache.async_graph(graph_class_name, ped_file.pk) - return HttpResponseRedirect(reverse("cached_generated_file_check", kwargs={"cgf_id": cached_graph.id})) + return redirect(cached_graph) def ped_files(request): diff --git a/seqauto/views.py b/seqauto/views.py index 33682f3e2..d5a90494c 100644 --- a/seqauto/views.py +++ b/seqauto/views.py @@ -8,7 +8,7 @@ from django.core.exceptions import PermissionDenied from django.db.models.aggregates import Count from django.http.response import HttpResponseRedirect, JsonResponse, HttpResponse -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import render, get_object_or_404, redirect from django.urls.base import reverse from django.utils.decorators import method_decorator from django.utils.safestring import mark_safe @@ -478,7 +478,7 @@ def sequencing_run_qc_graph(request, sequencing_run_id, qc_compare_type): _ = QCCompareType(qc_compare_type) # Check valid graph_class_name = full_class_name(SequencingRunQCGraph) cached_graph = graphcache.async_graph(graph_class_name, sequencing_run_id, qc_compare_type) - return HttpResponseRedirect(reverse("cached_generated_file_check", kwargs={"cgf_id": cached_graph.id})) + return redirect(cached_graph) def sequencing_run_qc_json_graph(request, sequencing_run_id, qc_compare_type): @@ -565,13 +565,13 @@ def get_field(f): def index_metrics_qc_graph(request, illumina_qc_id): graph_class_name = full_class_name(IndexMetricsQCGraph) cached_graph = graphcache.async_graph(graph_class_name, illumina_qc_id) - return HttpResponseRedirect(reverse("cached_generated_file_check", kwargs={"cgf_id": cached_graph.id})) + return redirect(cached_graph) def qc_exec_summary_graph(request, qc_exec_summary_id, qc_compare_type): graph_class_name = full_class_name(QCExecSummaryGraph) cached_graph = graphcache.async_graph(graph_class_name, qc_exec_summary_id, qc_compare_type) - return HttpResponseRedirect(reverse("cached_generated_file_check", kwargs={"cgf_id": cached_graph.id})) + return redirect(cached_graph) def qc_exec_summary_json_graph(request, qc_exec_summary_id, qc_compare_type): diff --git a/snpdb/graphs/graphcache.py b/snpdb/graphs/graphcache.py index e9317a785..7fb8505b3 100644 --- a/snpdb/graphs/graphcache.py +++ b/snpdb/graphs/graphcache.py @@ -1,11 +1,8 @@ import abc -import logging import os from celery import signature -from celery.result import AsyncResult from django.conf import settings -from django.utils import timezone from library.graphs import graph_base from library.utils import import_class diff --git a/snpdb/models/models.py b/snpdb/models/models.py index 2685c9e41..fa52c088a 100644 --- a/snpdb/models/models.py +++ b/snpdb/models/models.py @@ -10,6 +10,7 @@ import re from dataclasses import dataclass from datetime import datetime +from fileinput import filename from functools import cached_property, total_ordering from html import escape from re import RegexFlag @@ -33,6 +34,7 @@ from more_itertools import first from classification.enums.classification_enums import ShareLevel +from library.django_utils import get_url_from_media_root_filename from library.django_utils.django_object_managers import ObjectManagerCachingRequest from library.enums.log_level import LogLevel from library.preview_request import PreviewModelMixin @@ -69,6 +71,14 @@ def __str__(self): description = f"task: {self.task_id} sent: {self.generate_start}" return f"{self.generator}({self.params_hash}): {description}" + def get_absolute_url(self): + return reverse("cached_generated_file_check", kwargs={"cgf_id": self.pk}) + + def get_media_url(self): + if self.filename is None: + raise ValueError(f"{self}.filename is None") + return get_url_from_media_root_filename(self.filename) + @staticmethod def get_or_create_and_launch(generator, params_hash, task: signature) -> 'CachedGeneratedFile': cgf, created = CachedGeneratedFile.objects.get_or_create(generator=generator, diff --git a/snpdb/models/models_somalier.py b/snpdb/models/models_somalier.py index f31095fb2..b3008d224 100644 --- a/snpdb/models/models_somalier.py +++ b/snpdb/models/models_somalier.py @@ -14,6 +14,7 @@ from django_extensions.db.models import TimeStampedModel from model_utils.managers import InheritanceManager +from library.django_utils import get_url_from_media_root_filename from library.utils import execute_cmd from patients.models_enums import Sex from pedigree.ped.export_ped import write_unrelated_ped, write_trio_ped @@ -72,11 +73,7 @@ def sample_name_to_id(sample_name: str): @staticmethod def media_url(file_path): # Need to use a slash, so that later joins don't have absolute path - media_root_with_slash = os.path.join(settings.MEDIA_ROOT, "") - if not file_path.startswith(media_root_with_slash): - raise ValueError(f"'{file_path}' must start with MEDIA_ROOT: {media_root_with_slash}") - - return os.path.join(settings.MEDIA_URL, file_path[len(media_root_with_slash):]) + return get_url_from_media_root_filename(file_path) class SomalierVCFExtract(AbstractSomalierModel): diff --git a/snpdb/templates/snpdb/data/view_vcf.html b/snpdb/templates/snpdb/data/view_vcf.html index 7b67cefe5..a946f187b 100644 --- a/snpdb/templates/snpdb/data/view_vcf.html +++ b/snpdb/templates/snpdb/data/view_vcf.html @@ -10,6 +10,7 @@ {% block title %}{{ vcf.name }}{% endblock %} {% block head %} +