Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions adit/core/templates/core/admin_section.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ <h5>Job Overview</h5>
{% endfor %}
</tbody>
</table>
<h5>API Usage</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>User</th>
<th>Time of last request</th>
<th>Total Response Size</th>
<th>Total number of requests</th>
</tr>
</thead>
<tbody>
{% for session in api_stats %}
<tr>
<td>{{ session.owner }}</td>
<td>{{ session.time_last_accessed }}</td>
<td>{{ session.total_transfer_size | filesizeformat }}</td>
<td>{{ session.total_number_requests }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h5>Admin Tools</h5>
<ul class="list-group">
<li class="list-group-item">
Expand Down
7 changes: 3 additions & 4 deletions adit/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from procrastinate.contrib.django import app

from adit.core.utils.model_utils import reset_tasks
from adit.dicom_web.apps import collect_latest_api_usage

from .models import DicomJob, DicomTask
from .site import job_stats_collectors
Expand All @@ -46,13 +47,11 @@ def health(request: HttpRequest) -> JsonResponse:
def admin_section(request: HttpRequest) -> HttpResponse:
status_list = DicomJob.Status.choices
job_stats = [collector() for collector in job_stats_collectors]
api_stats = collect_latest_api_usage()
return render(
request,
"core/admin_section.html",
{
"status_list": status_list,
"job_stats": job_stats,
},
{"status_list": status_list, "job_stats": job_stats, "api_stats": api_stats},
)


Expand Down
32 changes: 31 additions & 1 deletion adit/dicom_web/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
from django.contrib import admin

from .models import DicomWebSettings
from .models import APIUsage, DicomWebSettings

admin.site.register(DicomWebSettings, admin.ModelAdmin)


class APIUsageAdmin(admin.ModelAdmin):
list_display = (
"get_owner",
"id",
"time_last_accessed",
"total_number_requests",
"get_size",
)
list_filter = ("total_number_requests", "owner")
search_fields = ("owner__username",)

def get_owner(self, obj):
return obj.owner.username

get_owner.admin_order_field = "owner__username"
get_owner.short_description = "Owner"

def get_size(self, obj):
size = obj.total_transfer_size
for unit in ["B", "KB", "MB", "GB", "TB"]:
if size < 1024:
return f"{size:.2f} {unit}"
size /= 1024

get_size.short_description = "Total Transfer Size"
Comment on lines +25 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

get_size doesn't return a value for sizes >= 1024 TB.

If total_transfer_size exceeds 1024 TB, the loop completes without returning, causing None to be displayed. Also consider using Django's filesizeformat for consistency with the template.

+from django.template.defaultfilters import filesizeformat
+
+class APIUsageAdmin(admin.ModelAdmin):
+    # ...
+
     def get_size(self, obj):
-        size = obj.total_transfer_size
-        for unit in ["B", "KB", "MB", "GB", "TB"]:
-            if size < 1024:
-                return f"{size:.2f} {unit}"
-            size /= 1024
+        return filesizeformat(obj.total_transfer_size)
 
     get_size.short_description = "Total Transfer Size"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In adit/dicom_web/admin.py around lines 25 to 32, get_size currently loops
through units and returns nothing when size is >= 1024 TB; update it to always
return a formatted string for extremely large sizes by adding a final fallback
return after the loop (e.g., format remaining size with "PB" or clamp to the
largest unit) so the function never returns None, and preferably replace the
manual loop with Django's filesizeformat (from django.template.defaultfilters
import filesizeformat) to ensure consistent formatting with templates.



admin.site.register(APIUsage, APIUsageAdmin)
6 changes: 6 additions & 0 deletions adit/dicom_web/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ def init_db(**kwargs):

if not DicomWebSettings.objects.exists():
DicomWebSettings.objects.create()


def collect_latest_api_usage():
from .models import APIUsage

return APIUsage.objects.order_by("-time_last_accessed")[:3]
58 changes: 58 additions & 0 deletions adit/dicom_web/migrations/0004_apiusage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 5.2.8 on 2025-12-08 20:58

import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("dicom_web", "0003_add_dicom_web_permissions"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="APIUsage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"time_last_accessed",
models.DateTimeField(default=django.utils.timezone.now),
),
("total_transfer_size", models.BigIntegerField(default=0)),
("total_number_requests", models.IntegerField(default=0)),
(
"owner",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="api_usage",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"indexes": [
models.Index(
fields=["-time_last_accessed"],
name="dicom_web_a_time_la_866621_idx",
)
],
"constraints": [
models.UniqueConstraint(
fields=("owner",), name="unique_owner_api_usage"
)
],
},
),
]
24 changes: 24 additions & 0 deletions adit/dicom_web/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from django.conf import settings
from django.db import models
from django.utils import timezone

from adit.core.models import DicomAppSettings


Expand All @@ -9,3 +13,23 @@ class Meta:
("can_retrieve", "Can retrieve"),
("can_store", "Can store"),
]


class APIUsage(models.Model):
time_last_accessed = models.DateTimeField(default=timezone.now)
total_transfer_size = models.BigIntegerField(default=0)
total_number_requests = models.IntegerField(default=0)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="api_usage",
)

class Meta:
indexes = [
models.Index(fields=["-time_last_accessed"]),
]
constraints = [models.UniqueConstraint(fields=["owner"], name="unique_owner_api_usage")]

def __str__(self) -> str:
return f"{self.__class__.__name__} [{self.pk}]"
Loading