Add download functionality for case DICOMs and metadata as ZIP

This commit is contained in:
Ross
2026-03-16 11:03:37 +00:00
parent 65208ae7ad
commit e391f4f943
3 changed files with 79 additions and 0 deletions
@@ -223,6 +223,11 @@
<li>
<a class="dropdown-item" target="_blank" href="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">Open in new tab</a>
</li>
{% if can_edit %}
<li>
<a class="dropdown-item" href="{% url 'atlas:case_download' case.pk %}">Download DICOMs + metadata (ZIP)</a>
</li>
{% endif %}
</ul>
</div>
<div class="normal-block">
+5
View File
@@ -525,6 +525,11 @@ urlpatterns = [
views.SeriesImagesZipView.as_view(),
name="series_download",
),
path(
"case/<int:pk>/download",
views.case_download,
name="case_download",
),
path("api_tokens/", views.api_tokens_page, name="api_tokens"),
path(
"series/<int:pk>/order_dicom_instance",
+69
View File
@@ -48,6 +48,10 @@ from django.urls.exceptions import NoReverseMatch
from django.http import Http404, JsonResponse
from django.http import HttpResponseRedirect, HttpResponse
from django.contrib.contenttypes.models import ContentType
import zipfile
import os
from django.core.files.base import ContentFile
from django.core.exceptions import PermissionDenied
from generic.models import CidUser, CidUserExam, CimarCase, Site
from atlas.helpers import get_cases_available_to_user
@@ -3373,6 +3377,71 @@ class SeriesImagesZipView(SeriesImagesZipViewBase, SuperuserRequiredMixin):
series_object = Series
@login_required
def case_download(request, pk):
"""Allow a case author (or superuser) to download all linked series DICOMs
and a metadata export as a zip file.
"""
case = get_object_or_404(Case, pk=pk)
# Only allow superusers or case authors
if not (request.user.is_superuser or case.author.filter(id=request.user.id).exists()):
raise PermissionDenied()
temp_file = ContentFile(b"", name=f"case_{case.pk}.zip")
with zipfile.ZipFile(temp_file, mode="w", compression=zipfile.ZIP_DEFLATED) as zip_file:
metadata = {
"case": {
"id": case.pk,
"title": case.title,
"authors": [str(a) for a in case.author.all()],
},
"series": [],
}
# Iterate ordered series if available, otherwise fall back to related series
try:
ordered_series = case.get_ordered_series()
except Exception:
ordered_series = list(case.series.all())
for series in ordered_series:
series_entry = {"pk": series.pk, "description": str(series)}
# Prefer series-provided dicom JSON if available
try:
series_entry["dicom_json"] = series.get_series_dicom_json()
except Exception:
series_entry["dicom_json"] = None
images = series.images.filter(removed=False).order_by("position")
series_entry["images"] = []
for img in images:
try:
file_obj = img.image.file
fname = os.path.split(file_obj.name)[-1]
arcname = f"series_{series.pk}/{fname}"
zip_file.writestr(arcname, file_obj.read())
series_entry["images"].append({"filename": fname, "position": img.position})
except Exception:
# skip missing files
continue
metadata["series"].append(series_entry)
# Write metadata.json
zip_file.writestr("metadata.json", json.dumps(metadata, default=str, indent=2))
file_size = temp_file.tell()
temp_file.seek(0)
response = HttpResponse(temp_file, content_type="application/zip")
response["Content-Disposition"] = (
"attachment; filename=%s" % f"case_{case.pk}.zip"
)
response["Content-Length"] = file_size
return response
# At the moment only the series owner / author can delete findings but anyone can create
def create_series_findings(request):
# posts = Post.objects.all()