numerous updates to case collections
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.4 on 2024-02-12 12:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('atlas', '0037_alter_seriesimage_image_blake3_hash'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='viewer_mode',
|
||||||
|
field=models.CharField(choices=[('BUILT_IN', 'Built in viewer'), ('OHIF', 'OHIF Viewer'), ('BUILT_IN_WITH_OHIF', 'Built in viewer with OHIF Viewer option')], default='BUILT_IN', max_length=100),
|
||||||
|
),
|
||||||
|
]
|
||||||
+50
-3
@@ -699,6 +699,26 @@ class CaseCollection(ExamOrCollectionGenericBase):
|
|||||||
default=COLLECTION_TYPE_CHOICES.REPORT,
|
default=COLLECTION_TYPE_CHOICES.REPORT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class VIEWER_MODE_CHOICES(models.TextChoices):
|
||||||
|
BUILT_IN = (
|
||||||
|
"BUILT_IN",
|
||||||
|
_("Built in viewer"),
|
||||||
|
)
|
||||||
|
OHIF = (
|
||||||
|
"OHIF",
|
||||||
|
_("OHIF Viewer"),
|
||||||
|
)
|
||||||
|
BUILT_IN_WITH_OHIF = (
|
||||||
|
"BUILT_IN_WITH_OHIF",
|
||||||
|
_("Built in viewer with OHIF Viewer option"),
|
||||||
|
)
|
||||||
|
|
||||||
|
viewer_mode = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
choices=VIEWER_MODE_CHOICES.choices,
|
||||||
|
default=VIEWER_MODE_CHOICES.BUILT_IN,
|
||||||
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("atlas:collection_detail", kwargs={"pk": self.pk})
|
return reverse("atlas:collection_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
@@ -708,6 +728,18 @@ class CaseCollection(ExamOrCollectionGenericBase):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def show_built_in_viewer(self) -> bool:
|
||||||
|
return self.viewer_mode in (
|
||||||
|
self.VIEWER_MODE_CHOICES.BUILT_IN,
|
||||||
|
self.VIEWER_MODE_CHOICES.BUILT_IN_WITH_OHIF,
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_ohif_viewer_link(self) -> bool:
|
||||||
|
return self.viewer_mode in (
|
||||||
|
self.VIEWER_MODE_CHOICES.OHIF,
|
||||||
|
self.VIEWER_MODE_CHOICES.BUILT_IN_WITH_OHIF,
|
||||||
|
)
|
||||||
|
|
||||||
def review_only(self) -> bool:
|
def review_only(self) -> bool:
|
||||||
"""Returns True if a users cannot submit responses
|
"""Returns True if a users cannot submit responses
|
||||||
|
|
||||||
@@ -719,6 +751,17 @@ class CaseCollection(ExamOrCollectionGenericBase):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def in_review_mode(self) -> bool:
|
||||||
|
return self.review_only() or self.publish_results
|
||||||
|
|
||||||
|
# def can_answer(self) -> bool:
|
||||||
|
# """Returns true if the user is able to answer questions in a collection
|
||||||
|
# (independent of current collection state (e.g. published / not published))"""
|
||||||
|
# if self.collection_type == self.COLLECTION_TYPE_CHOICES.REPORT:
|
||||||
|
# return True
|
||||||
|
|
||||||
|
# return False
|
||||||
|
|
||||||
def get_case_by_index(
|
def get_case_by_index(
|
||||||
self, case_index: int, case_count=False
|
self, case_index: int, case_count=False
|
||||||
) -> Case | Tuple[Case, int]:
|
) -> Case | Tuple[Case, int]:
|
||||||
@@ -892,8 +935,10 @@ class UncategorisedDicom(models.Model):
|
|||||||
def check_for_duplicates(self, image_hash):
|
def check_for_duplicates(self, image_hash):
|
||||||
duplicate = None
|
duplicate = None
|
||||||
|
|
||||||
if obj := UncategorisedDicom.objects.filter(image_blake3_hash=image_hash).first():
|
if obj := UncategorisedDicom.objects.filter(
|
||||||
#duplicate = obj
|
image_blake3_hash=image_hash
|
||||||
|
).first():
|
||||||
|
# duplicate = obj
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
print(image_hash)
|
print(image_hash)
|
||||||
@@ -912,7 +957,9 @@ class UncategorisedDicom(models.Model):
|
|||||||
|
|
||||||
# if self.check_for_duplicates():
|
# if self.check_for_duplicates():
|
||||||
# return DuplicateDicom
|
# return DuplicateDicom
|
||||||
image_blake3_hash = get_image_dicom_hash(None, dataset=ds, hash_type="blake3", direct_pixel_data=True)
|
image_blake3_hash = get_image_dicom_hash(
|
||||||
|
None, dataset=ds, hash_type="blake3", direct_pixel_data=True
|
||||||
|
)
|
||||||
|
|
||||||
self.image_blake3_hash = image_blake3_hash
|
self.image_blake3_hash = image_blake3_hash
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<span id="add-case-form">
|
||||||
|
<form hx-post="{% url 'atlas:add_case_to_collection' collection.id %}" hx-swap="outerHTML">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label for="case">Select Case:</label>
|
||||||
|
<select name="case" id="case">
|
||||||
|
{% for case in cases %}
|
||||||
|
<option value="{{ case.id }}">{{ case.title }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button type="submit">Add</button>
|
||||||
|
<button _="on click remove #add-case-form">Cancel</button>
|
||||||
|
</form>
|
||||||
|
</span
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
Use this form to create a atlas case. Existing associated image sets can be added using this form.
|
Use this form to create a atlas case. Existing associated image sets can be added using this form.
|
||||||
|
|
||||||
{% if form.collection %}
|
{% if form.collection %}
|
||||||
<div class="alert alert-info" role="alert">Creating a case in the collection: <a href="{% url 'atlas:collection_detail' pk=form.exam.pk %}">{{form.collection.name}}</a></div>
|
<div class="alert alert-info" role="alert">Creating a case in the collection: <a href="{% url 'atlas:collection_detail' pk=form.collection.pk %}">{{form.collection.name}}</a></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form action="" method="post" enctype="multipart/form-data" id="atlas-form">
|
<form action="" method="post" enctype="multipart/form-data" id="atlas-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@@ -28,29 +28,42 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="pre-whitespace multi-image-block">
|
|
||||||
<details open>
|
|
||||||
<summary>
|
|
||||||
Images
|
|
||||||
</summary>
|
|
||||||
{% for series in series_list %}
|
|
||||||
<span class="series-block">
|
|
||||||
<a href="#" onclick='window.loadDicomViewer(window.images[{{forloop.counter0}}])'>
|
|
||||||
<span>
|
|
||||||
<span class="series-block-series-number">Series {{ forloop.counter }}:</span>
|
|
||||||
{{series.get_block}}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
{% if collection.show_ohif_viewer_link %}
|
||||||
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''>
|
<div>
|
||||||
|
<a href="https://viewer.penracourses.org.uk/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">View case in OHIF</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% if collection.show_built_in_viewer %}
|
||||||
|
<div class="pre-whitespace multi-image-block">
|
||||||
|
<details open>
|
||||||
|
<summary>
|
||||||
|
Images
|
||||||
|
</summary>
|
||||||
|
{% for series in series_list %}
|
||||||
|
<span class="series-block">
|
||||||
|
<a href="#" onclick='window.loadDicomViewer(window.images[{{forloop.counter0}}])'>
|
||||||
|
<span>
|
||||||
|
<span class="series-block-series-number">Series {{ forloop.counter }}:</span>
|
||||||
|
{{series.get_block}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<div>
|
||||||
|
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if show_discussion and case.discussion %}
|
{% if show_discussion and case.discussion %}
|
||||||
<p>
|
<p>
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -21,8 +21,14 @@
|
|||||||
<p>This collection will be available to view/take <a href='{{collection.get_take_url}}'>here</a>
|
<p>This collection will be available to view/take <a href='{{collection.get_take_url}}'>here</a>
|
||||||
|
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
|
|
||||||
<p><button id='button-edit-order' title='click and drag questions to change order' data-posturl="{% url 'atlas:exam_json_edit' pk=collection.pk %}">Edit case order / Delete cases</button></p>
|
<p><button id='button-edit-order' title='click and drag questions to change order' data-posturl="{% url 'atlas:exam_json_edit' pk=collection.pk %}">Edit case order / Delete cases</button></p>
|
||||||
|
<p><button id='button-add-case'
|
||||||
|
title='click to add a case to this collection'
|
||||||
|
hx-get="{% url 'atlas:add_case_to_collection' collection.pk %}" hx-swap="innerHTML" hx-target="#case-list">
|
||||||
|
Add case
|
||||||
|
</button>
|
||||||
|
<div id='case-list'></div>
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% include 'exam_overview_js.html' %}
|
{% include 'exam_overview_js.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
|
|
||||||
<h2>Collection: {{collection.name}}
|
<h2>Collection: {{collection.name}}
|
||||||
|
|
||||||
{% if collection.publish_results or cid_user_exam.completed %}
|
{% if collection.in_review_mode or cid_user_exam.completed %}
|
||||||
<span class="stamp-white">REVIEW</span>
|
<span class="stamp-white">REVIEW</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{{answer_count}} out of {{collection_length}} cases answered. Click to go to case.
|
|
||||||
|
{% if not collection.review_only %}
|
||||||
|
{{answer_count}} out of {{collection_length}} cases answered. Click to go to case.
|
||||||
|
{% endif %}
|
||||||
<div class="sba-finish-list">
|
<div class="sba-finish-list">
|
||||||
<ul>
|
<ul>
|
||||||
{% for question, answer, self_review in question_answer_tuples %}
|
{% for question, answer, self_review in question_answer_tuples %}
|
||||||
@@ -40,9 +43,8 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
Start time: {{cid_user_exam.start_time}}
|
Start time: {{cid_user_exam.start_time}}
|
||||||
|
|
||||||
|
|
||||||
{% if collection.self_review %}
|
{% if collection.self_review %}
|
||||||
<p>Completed: <span id="completed-state">{{cid_user_exam.completed}}</span></p>
|
<p>Completed: <span id="completed-state">{{cid_user_exam.completed}}</span></p>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Collections</h2>
|
<h2>Collections</h2>
|
||||||
|
|
||||||
|
The following collections are available for you to view / take.
|
||||||
|
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for collection in collections %}
|
{% for collection in collections %}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ urlpatterns = [
|
|||||||
path("author/<int:pk>/", views.author_detail, name="author_detail"),
|
path("author/<int:pk>/", views.author_detail, name="author_detail"),
|
||||||
path("author/", views.author_list, name="author_list"),
|
path("author/", views.author_list, name="author_list"),
|
||||||
path("case/", views.CaseView.as_view(), name="case_view"),
|
path("case/", views.CaseView.as_view(), name="case_view"),
|
||||||
|
path("collection/<int:collection_id>/add_case", views.add_case_to_collection, name="add_case_to_collection"),
|
||||||
path("collection/", views.CollectionView.as_view(), name="collection_view"),
|
path("collection/", views.CollectionView.as_view(), name="collection_view"),
|
||||||
path("collection/user", views.user_collections, name="user_collections"),
|
path("collection/user", views.user_collections, name="user_collections"),
|
||||||
path("uploads", views.user_uploads, name="user_uploads"),
|
path("uploads", views.user_uploads, name="user_uploads"),
|
||||||
|
|||||||
@@ -136,6 +136,14 @@ import difflib
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def get_cases_available_to_user(user):
|
||||||
|
"""Returns a queryset of cases available to the user"""
|
||||||
|
return Case.objects.filter(author=user) | Case.objects.filter(open_access=True)
|
||||||
|
if user.is_superuser:
|
||||||
|
return Case.objects.all()
|
||||||
|
else:
|
||||||
|
return Case.objects.filter(author=user) | Case.objects.filter(open_access=True)
|
||||||
|
|
||||||
|
|
||||||
class AuthorOrCheckerRequiredMixin(object):
|
class AuthorOrCheckerRequiredMixin(object):
|
||||||
def get_object(self, *args, **kwargs):
|
def get_object(self, *args, **kwargs):
|
||||||
@@ -370,6 +378,25 @@ def user_collections(request):
|
|||||||
|
|
||||||
return render(request, "atlas/user_collections.html", {"collections": collections})
|
return render(request, "atlas/user_collections.html", {"collections": collections})
|
||||||
|
|
||||||
|
def add_case_to_collection(request, collection_id):
|
||||||
|
if not request.htmx:
|
||||||
|
return Http404
|
||||||
|
|
||||||
|
|
||||||
|
collection = get_object_or_404(CaseCollection, pk=collection_id)
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
case = get_object_or_404(Case, pk=request.POST["case"])
|
||||||
|
print(case)
|
||||||
|
collection.cases.add(case)
|
||||||
|
return HttpResponse("Case added to collection")
|
||||||
|
|
||||||
|
|
||||||
|
#if request.method == "POST":
|
||||||
|
|
||||||
|
cases = get_cases_available_to_user(request.user)
|
||||||
|
|
||||||
|
return render(request, "atlas/add_case_to_collection.html", {"cases": cases, "collection": collection})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def user_uploads_series(request, series_instance_uid: str):
|
def user_uploads_series(request, series_instance_uid: str):
|
||||||
|
|||||||
Reference in New Issue
Block a user