Add admin view for cases ordered by total image size with filtering options
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user