Add download functionality for case DICOMs and metadata as ZIP
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user