Add admin view for cases ordered by total image size with filtering options
This commit is contained in:
@@ -49,6 +49,12 @@
|
|||||||
<li>
|
<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>
|
<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>
|
</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>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
|
|||||||
@@ -634,4 +634,5 @@ urlpatterns = [
|
|||||||
views.linked_cases_overview,
|
views.linked_cases_overview,
|
||||||
name="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
|
import pydicom
|
||||||
from pydicom.uid import generate_uid
|
from pydicom.uid import generate_uid
|
||||||
from generic.mixins import SuperuserRequiredMixin, UserConfigurablePaginationMixin
|
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.edit import CreateView, UpdateView, DeleteView, FormView
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
@@ -2304,6 +2306,66 @@ class CaseView(LoginRequiredMixin, UserConfigurablePaginationMixin, SingleTableM
|
|||||||
filterset_class = CaseFilter
|
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):
|
class SeriesView(LoginRequiredMixin, UserConfigurablePaginationMixin, SingleTableMixin, FilterView):
|
||||||
model = Series
|
model = Series
|
||||||
table_class = SeriesTable
|
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