Add admin hash search link and enhance uploads hash search functionality
This commit is contained in:
@@ -122,6 +122,11 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'atlas:user_uploads' %}" title="View unimported uploads"><i class="bi bi-upload me-1" aria-hidden="true"></i>Uploads</a>
|
<a class="nav-link" href="{% url 'atlas:user_uploads' %}" title="View unimported uploads"><i class="bi bi-upload me-1" aria-hidden="true"></i>Uploads</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if request.user.is_staff %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'atlas:uploads_hash_search' %}" title="Search uploads and imported images by pixel hash"><i class="bi bi-fingerprint me-1" aria-hidden="true"></i>Hash Search</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'atlas:question_schema_overview' %}" title="View and edit question schemas"><i class="bi bi-journal-text me-1" aria-hidden="true"></i>Question Schemas</a>
|
<a class="nav-link" href="{% url 'atlas:question_schema_overview' %}" title="View and edit question schemas"><i class="bi bi-journal-text me-1" aria-hidden="true"></i>Question Schemas</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Search uploaded DICOMs by pixel-data hash</h2>
|
<h2>Search uploaded DICOMs by pixel-data hash</h2>
|
||||||
<p class="text-muted">Admin-only tool for searching uncategorised uploads by <code>image_blake3_hash</code>.</p>
|
<p class="text-muted">Admin-only tool for searching uncategorised uploads and imported Atlas images by <code>image_blake3_hash</code>.</p>
|
||||||
|
|
||||||
<form method="get" class="mb-3">
|
<form method="get" class="mb-3">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -21,9 +21,16 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if hash_query %}
|
{% if hash_query %}
|
||||||
<p>Found <strong>{{ result_count }}</strong> upload{{ result_count|pluralize }} for <code>{{ hash_query }}</code>.</p>
|
<p>
|
||||||
|
Found
|
||||||
|
<strong>{{ uploads_result_count }}</strong> pending upload{{ uploads_result_count|pluralize }}
|
||||||
|
and
|
||||||
|
<strong>{{ imported_result_count }}</strong> imported image{{ imported_result_count|pluralize }}
|
||||||
|
for <code>{{ hash_query }}</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
{% if page_obj.object_list %}
|
<h4>Pending uploads</h4>
|
||||||
|
{% if uploads_page_obj.object_list %}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-sm table-striped align-middle">
|
<table class="table table-sm table-striped align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -36,7 +43,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for dicom in page_obj.object_list %}
|
{% for dicom in uploads_page_obj.object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>{{ dicom.image_blake3_hash }}</code></td>
|
<td><code>{{ dicom.image_blake3_hash }}</code></td>
|
||||||
<td>{{ dicom.created_date }}</td>
|
<td>{{ dicom.created_date }}</td>
|
||||||
@@ -55,24 +62,24 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
{% if uploads_page_obj.paginator.num_pages > 1 %}
|
||||||
<nav aria-label="Hash search pagination">
|
<nav aria-label="Pending uploads pagination">
|
||||||
<ul class="pagination">
|
<ul class="pagination">
|
||||||
{% if page_obj.has_previous %}
|
{% if uploads_page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?hash={{ hash_query|urlencode }}&page={{ page_obj.previous_page_number }}">Previous</a>
|
<a class="page-link" href="?hash={{ hash_query|urlencode }}&uploads_page={{ uploads_page_obj.previous_page_number }}&imported_page={{ imported_page_obj.number }}">Previous</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled"><span class="page-link">Previous</span></li>
|
<li class="page-item disabled"><span class="page-link">Previous</span></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
<span class="page-link">Page {{ uploads_page_obj.number }} of {{ uploads_page_obj.paginator.num_pages }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if uploads_page_obj.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="?hash={{ hash_query|urlencode }}&page={{ page_obj.next_page_number }}">Next</a>
|
<a class="page-link" href="?hash={{ hash_query|urlencode }}&uploads_page={{ uploads_page_obj.next_page_number }}&imported_page={{ imported_page_obj.number }}">Next</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||||
@@ -81,7 +88,81 @@
|
|||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning">No uploads matched that hash.</div>
|
<div class="alert alert-warning">No pending uploads matched that hash.</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h4 class="mt-4">Imported Atlas images</h4>
|
||||||
|
{% if imported_page_obj.object_list %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-striped align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hash</th>
|
||||||
|
<th>Image ID</th>
|
||||||
|
<th>Series</th>
|
||||||
|
<th>Series UID</th>
|
||||||
|
<th>Links</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for image in imported_page_obj.object_list %}
|
||||||
|
<tr>
|
||||||
|
<td><code>{{ image.image_blake3_hash }}</code></td>
|
||||||
|
<td>{{ image.pk }}</td>
|
||||||
|
<td>
|
||||||
|
{% if image.series %}
|
||||||
|
{{ image.series }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if image.series and image.series.series_instance_uid %}
|
||||||
|
<code>{{ image.series.series_instance_uid }}</code>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if image.series %}
|
||||||
|
<a href="{% url 'atlas:series_detail' image.series.pk %}">View series</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if imported_page_obj.paginator.num_pages > 1 %}
|
||||||
|
<nav aria-label="Imported images pagination">
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if imported_page_obj.has_previous %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="?hash={{ hash_query|urlencode }}&uploads_page={{ uploads_page_obj.number }}&imported_page={{ imported_page_obj.previous_page_number }}">Previous</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">Previous</span></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<span class="page-link">Page {{ imported_page_obj.number }} of {{ imported_page_obj.paginator.num_pages }}</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% if imported_page_obj.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="?hash={{ hash_query|urlencode }}&uploads_page={{ uploads_page_obj.number }}&imported_page={{ imported_page_obj.next_page_number }}">Next</a>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">Next</span></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-warning">No imported Atlas images matched that hash.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-info">Enter a hash value to search uploads.</div>
|
<div class="alert alert-info">Enter a hash value to search uploads.</div>
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
<h2>Uploaded dicoms</h2>
|
<h2>Uploaded dicoms</h2>
|
||||||
User: {{user}}<br/>
|
User: {{user}}<br/>
|
||||||
<a href="{% url 'atlas:new_uploads' %}">Upload more dicoms</a><br/>
|
<a href="{% url 'atlas:new_uploads' %}">Upload more dicoms</a><br/>
|
||||||
{% if request.user.is_staff %}
|
|
||||||
<a href="{% url 'atlas:uploads_hash_search' %}">Admin: search uploads by pixel hash</a><br/>
|
|
||||||
{% endif %}
|
|
||||||
{% if not case %}
|
{% if not case %}
|
||||||
<div class="alert alert-info" role="alert">
|
<div class="alert alert-info" role="alert">
|
||||||
Series imported from here will be orphaned (not associated with a case). <br/>It is usually better to <a href="{% url 'atlas:case_create' %}">create a case</a> first and then import directly into the case.
|
Series imported from here will be orphaned (not associated with a case). <br/>It is usually better to <a href="{% url 'atlas:case_create' %}">create a case</a> first and then import directly into the case.
|
||||||
|
|||||||
+17
-7
@@ -2015,24 +2015,34 @@ def all_uploads(request, case_id: int | None = None):
|
|||||||
@staff_member_required
|
@staff_member_required
|
||||||
def uploads_hash_search(request):
|
def uploads_hash_search(request):
|
||||||
query = (request.GET.get("hash") or "").strip().lower()
|
query = (request.GET.get("hash") or "").strip().lower()
|
||||||
dicoms = UncategorisedDicom.objects.none()
|
uploads_qs = UncategorisedDicom.objects.none()
|
||||||
|
imported_qs = SeriesImage.objects.none()
|
||||||
|
|
||||||
if query:
|
if query:
|
||||||
dicoms = UncategorisedDicom.objects.filter(
|
uploads_qs = UncategorisedDicom.objects.filter(
|
||||||
image_blake3_hash__icontains=query
|
image_blake3_hash__icontains=query
|
||||||
).select_related("user", "series").order_by("-created_date")
|
).select_related("user", "series").order_by("-created_date")
|
||||||
|
imported_qs = SeriesImage.objects.filter(
|
||||||
|
image_blake3_hash__icontains=query
|
||||||
|
).select_related("series").order_by("-pk")
|
||||||
|
|
||||||
paginator = Paginator(dicoms, 50)
|
uploads_paginator = Paginator(uploads_qs, 50)
|
||||||
page_number = request.GET.get("page")
|
uploads_page_number = request.GET.get("uploads_page")
|
||||||
page_obj = paginator.get_page(page_number)
|
uploads_page_obj = uploads_paginator.get_page(uploads_page_number)
|
||||||
|
|
||||||
|
imported_paginator = Paginator(imported_qs, 50)
|
||||||
|
imported_page_number = request.GET.get("imported_page")
|
||||||
|
imported_page_obj = imported_paginator.get_page(imported_page_number)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"atlas/uploads_hash_search.html",
|
"atlas/uploads_hash_search.html",
|
||||||
{
|
{
|
||||||
"hash_query": query,
|
"hash_query": query,
|
||||||
"page_obj": page_obj,
|
"uploads_page_obj": uploads_page_obj,
|
||||||
"result_count": paginator.count,
|
"uploads_result_count": uploads_paginator.count,
|
||||||
|
"imported_page_obj": imported_page_obj,
|
||||||
|
"imported_result_count": imported_paginator.count,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -10,6 +10,12 @@
|
|||||||
"."
|
"."
|
||||||
],
|
],
|
||||||
"python.testing.unittestEnabled": false,
|
"python.testing.unittestEnabled": false,
|
||||||
"python.testing.pytestEnabled": true
|
"python.testing.pytestEnabled": true,
|
||||||
|
"chat.tools.terminal.autoApprove": {
|
||||||
|
"/^source \\.venv/bin/activate\\.fish && python manage\\.py check$/": {
|
||||||
|
"approve": true,
|
||||||
|
"matchCommandLine": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user