Add admin view for cases ordered by total image size with filtering options

This commit is contained in:
Ross
2026-01-28 14:45:01 +00:00
parent a512c2ba09
commit 90ceff1b02
4 changed files with 112 additions and 0 deletions
+6
View File
@@ -49,6 +49,12 @@
<li>
<a class="dropdown-item" href="{% url 'atlas:normals_list' %}"><i class="bi bi-file-earmark-check me-1" aria-hidden="true"></i>Normals</a>
</li>
{% if request.user.is_staff %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="{% url 'atlas:cases_by_size' %}"><i class="bi bi-hDD-stack me-1" aria-hidden="true"></i>Large cases</a>
</li>
{% endif %}
</ul>
</li>
<li class="nav-item dropdown">
+1
View File
@@ -634,4 +634,5 @@ urlpatterns = [
views.linked_cases_overview,
name="linked_cases_overview",
),
path("cases/by_size", views.cases_by_size, name="cases_by_size"),
]
+62
View File
@@ -30,6 +30,8 @@ from django.views.generic.detail import DetailView
import pydicom
from pydicom.uid import generate_uid
from generic.mixins import SuperuserRequiredMixin, UserConfigurablePaginationMixin
from django.contrib.admin.views.decorators import staff_member_required
from django.core.paginator import Paginator
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
from django.views.generic import ListView
@@ -2304,6 +2306,66 @@ class CaseView(LoginRequiredMixin, UserConfigurablePaginationMixin, SingleTableM
filterset_class = CaseFilter
@staff_member_required
def cases_by_size(request):
"""Admin view: list cases ordered by total images size to find large cases.
Shows case id, title, number of series, number of images and total size (bytes).
Optional GET parameter `min_size` (bytes) to filter cases larger than that.
"""
min_size = request.GET.get("min_size")
try:
min_size = int(min_size) if min_size else 0
except Exception:
min_size = 0
cases_qs = Case.objects.prefetch_related("series__images")
def human(n):
# simple human readable bytes
for unit in ("B", "KB", "MB", "GB", "TB"):
if n < 1024.0:
return f"{n:.1f}{unit}"
n /= 1024.0
return f"{n:.1f}PB"
rows = []
for c in cases_qs:
try:
total = c.get_total_series_images_size()
except Exception:
total = 0
series_count = c.series.count()
image_count = sum(s.images.filter(removed=False).count() for s in c.series.all())
if total >= min_size:
rows.append({
"case": c,
"total": total,
"series_count": series_count,
"image_count": image_count,
"human_total": human(total),
})
rows.sort(key=lambda r: r["total"], reverse=True)
rows.sort(key=lambda r: r["total"], reverse=True)
paginator = Paginator(rows, 50)
page_num = request.GET.get("page")
page = paginator.get_page(page_num)
def human(n):
# simple human readable bytes
for unit in ("B", "KB", "MB", "GB", "TB"):
if n < 1024.0:
return f"{n:.1f}{unit}"
n /= 1024.0
return f"{n:.1f}PB"
context = {"page": page, "human": human, "min_size": min_size}
return render(request, "atlas/cases_by_size.html", context)
class SeriesView(LoginRequiredMixin, UserConfigurablePaginationMixin, SingleTableMixin, FilterView):
model = Series
table_class = SeriesTable
+43
View File
@@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% block content %}
<h1>Cases by size</h1>
<form method="get" class="mb-3">
<label>Min size (bytes): <input type="number" name="min_size" value="{{ min_size }}" /></label>
<button class="btn btn-primary btn-sm">Filter</button>
</form>
<table class="table table-sm">
<thead>
<tr>
<th>Case</th>
<th>Title</th>
<th>Series</th>
<th>Images</th>
<th>Total Size</th>
</tr>
</thead>
<tbody>
{% for row in page.object_list %}
<tr>
<td><a href="{{ row.case.get_absolute_url }}">{{ row.case.pk }}</a></td>
<td>{{ row.case.title }}</td>
<td>{{ row.series_count }}</td>
<td>{{ row.image_count }}</td>
<td>{{ row.human_total }}</td>
</tr>
{% empty %}
<tr><td colspan="5">No cases</td></tr>
{% endfor %}
</tbody>
</table>
<nav>
<ul class="pagination">
{% if page.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page.previous_page_number }}&min_size={{ min_size }}">Previous</a></li>
{% endif %}
<li class="page-item disabled"><span class="page-link">Page {{ page.number }} / {{ page.paginator.num_pages }}</span></li>
{% if page.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page.next_page_number }}&min_size={{ min_size }}">Next</a></li>
{% endif %}
</ul>
</nav>
{% endblock %}