enable priors in atlas
This commit is contained in:
@@ -781,3 +781,24 @@ class QuestionSchemaForm(ModelForm):
|
||||
widgets = {
|
||||
"schema": SvelteJSONEditorWidgetOverride(),
|
||||
}
|
||||
|
||||
class PriorCaseForm(Form):
|
||||
|
||||
relation = CharField(max_length=255, required=True)
|
||||
|
||||
#def __init__(self, *args, **kwargs):
|
||||
# self.case_detail = kwargs.pop(
|
||||
# "case_detail"
|
||||
# ) # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
|
||||
|
||||
# super(PriorCaseForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# prior_cases = self.case_detail.case.get_all_prior_cases()
|
||||
|
||||
# if not prior_cases:
|
||||
# prior_cases = Case.objects.none()
|
||||
|
||||
# self.fields["case"] = ChoiceField(
|
||||
# required=False,
|
||||
# # widget=Select(verbose_name="Series", is_stacked=False),
|
||||
# )
|
||||
@@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.0.2 on 2024-07-15 11:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0055_questionschema'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CasePrior',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('relation_text', models.CharField(blank=True, help_text='Text to describe the relationship between the cases', max_length=255)),
|
||||
('case_detail', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='atlas.casedetail')),
|
||||
('prior_case', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='prior_case', to='atlas.case')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('case_detail', 'prior_case')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.0.2 on 2024-07-15 12:22
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0056_caseprior'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='caseprior',
|
||||
name='prior_case',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='prior_case', to='atlas.case'),
|
||||
),
|
||||
]
|
||||
@@ -461,6 +461,18 @@ class Case(models.Model, AuthorMixin, QuestionMixin):
|
||||
def get_viva_details_json(self):
|
||||
return json.dumps(self.get_viva_details())
|
||||
|
||||
def get_all_prior_cases(self):
|
||||
prior_cases = []
|
||||
|
||||
while self.previous_case is not None:
|
||||
prior_cases.append(self.previous_case)
|
||||
self = self.previous_case
|
||||
|
||||
return prior_cases
|
||||
|
||||
def get_series(self):
|
||||
return self.series.all().prefetch_related("images", "examination", "plane")
|
||||
|
||||
|
||||
def extract_image_dicom_json_from_ds(ds, url, image_index):
|
||||
to_keep = [
|
||||
@@ -1007,6 +1019,9 @@ class CaseDetail(models.Model):
|
||||
class Meta:
|
||||
ordering = ("sort_order",)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.case} -> {self.collection}"
|
||||
|
||||
def get_question_schema(self):
|
||||
return json.dumps(self.question_schema)
|
||||
|
||||
@@ -1014,6 +1029,17 @@ class CaseDetail(models.Model):
|
||||
"""Returns the correct question answers as a json string"""
|
||||
return json.dumps(self.question_answers)
|
||||
|
||||
class CasePrior(models.Model):
|
||||
case_detail = models.ForeignKey(CaseDetail, on_delete=models.CASCADE)
|
||||
prior_case = models.ForeignKey(Case, on_delete=models.CASCADE, related_name="prior_case")
|
||||
|
||||
relation_text = models.CharField(max_length=255, blank=True, help_text="Text to describe the relationship between the cases")
|
||||
|
||||
class Meta:
|
||||
unique_together = ("case_detail", "prior_case")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.case_detail.case} -> {self.prior_case}"
|
||||
|
||||
class BaseReportAnswer(models.Model):
|
||||
question = models.ForeignKey(CaseDetail, on_delete=models.CASCADE)
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
{% extends 'atlas/exams.html' %}
|
||||
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div>
|
||||
|
||||
{% if previous %}
|
||||
<a href="{% url 'atlas:collection_case_details' collection.id previous.id %}">Previous question</a>
|
||||
{% endif %}
|
||||
Viewing question as part of collection: <a href="{% url 'atlas:collection_detail' collection.id %}">{{collection.name}}</a> [{{case_number|add:1}}/{{collection_length}}]
|
||||
{% if next %}
|
||||
<a href="{% url 'atlas:collection_case_details' collection.id next.id %}">Next question</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Case: <a href="{% url 'atlas:case_detail' case_detail.case.pk %}">{{case_detail.case.title}}</a></h2>
|
||||
|
||||
<p>This page allows you to add series from prior cases to a case in a collection. In order to be available here the cases must be linked on the system.</p>
|
||||
<p>It is necessary to give a description of how the cases are related that will be displayed to the user when viewing the case.</p>
|
||||
|
||||
<h3>Available priors</h3>
|
||||
|
||||
<ul>
|
||||
{% for case, added, relation in available_priors %}
|
||||
|
||||
<li class='
|
||||
{% if added %}
|
||||
added
|
||||
{% else %}
|
||||
not-added
|
||||
{% endif %}
|
||||
' data-case={{case.id}}>
|
||||
{{case.title}} / {{added}} / {{relation}}
|
||||
<br/>{{case.get_series_blocks|safe}}
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
{% if not added %}
|
||||
<form hx-post="{% url 'atlas:collection_case_priors' collection.id case_detail.case.pk %}"
|
||||
hx-target="#htmx-response-{{case.id}}"
|
||||
_="on htmx:afterRequest log 'test' then remove me"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="prior_case_id" value="{{case.id}}" />
|
||||
<input type="text" name="relation" value="{{relation}}" placeholder="relation to case" />
|
||||
<button class="btn primary">Add to case</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form hx-post="{% url 'atlas:collection_case_priors' collection.id case_detail.case.pk %}"
|
||||
hx-target="#htmx-response-{{case.id}}"
|
||||
_="on htmx:afterRequest log 'test' then remove me"
|
||||
>
|
||||
Case available: {{relation}}
|
||||
<input type="hidden" name="remove" value="{{case.id}}" />
|
||||
<button class="btn primary">Remove from case</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<span id="htmx-response-{{case.id}}"></span>
|
||||
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
{% comment %} <form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<button type="submit" value="save" name="submit">Submit</button>
|
||||
</form> {% endcomment %}
|
||||
|
||||
{% comment %} <h2>Blank form</h2>
|
||||
<form>
|
||||
{{ blank_form}}
|
||||
</form> {% endcomment %}
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<style>
|
||||
.added {
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.not-added {
|
||||
border: 1px solid white;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -63,13 +63,21 @@
|
||||
<summary>
|
||||
Images
|
||||
</summary>
|
||||
{% for series in series_list %}
|
||||
<span class="series-block">
|
||||
{% for series, prior, relation in series_to_load %}
|
||||
<span class="series-block
|
||||
{% if prior %}
|
||||
prior
|
||||
{% endif %}
|
||||
">
|
||||
<a href="#" onclick='window.loadDicomViewer(window.images[{{forloop.counter0}}])'>
|
||||
<span>
|
||||
<span class="series-block-series-number">Series {{ forloop.counter }}:</span>
|
||||
{{series.get_block}}
|
||||
</span>
|
||||
{% if prior %}
|
||||
<br/><span class="relation">Prior: {{relation}}</span>
|
||||
{% endif %}
|
||||
|
||||
</a>
|
||||
</span>
|
||||
{% endfor %}
|
||||
@@ -263,6 +271,10 @@
|
||||
background-color: darkblue;
|
||||
}
|
||||
|
||||
.series-block.prior {
|
||||
border: darkgray solid 1px;
|
||||
}
|
||||
|
||||
fieldset:disabled textarea {
|
||||
background-color: black;
|
||||
}
|
||||
@@ -291,6 +303,11 @@
|
||||
.answer-block>div {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.relation {
|
||||
opacity: 0.5;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock %}
|
||||
@@ -299,9 +316,13 @@
|
||||
{% comment %} <script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script> {% endcomment %}
|
||||
<script type="text/javascript">
|
||||
window.images = {
|
||||
{% for series in series_list %}
|
||||
{% comment %} {% for series in series_list %}
|
||||
{{ forloop.counter0 }}: ["{{ series.get_image_url_array_not_json }}"],
|
||||
{% endfor %} {% endcomment %}
|
||||
{% for series, prior, relation in series_to_load %}
|
||||
{{ forloop.counter0 }}: ["{{ series.get_image_url_array_not_json }}"],
|
||||
{% endfor %}
|
||||
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
@@ -116,6 +116,11 @@ urlpatterns = [
|
||||
views.collection_case_details,
|
||||
name="collection_case_details",
|
||||
),
|
||||
path(
|
||||
"collection/<int:exam_id>/case/<int:case_id>/priors",
|
||||
views.collection_case_priors,
|
||||
name="collection_case_priors",
|
||||
),
|
||||
path(
|
||||
"collection/<int:exam_id>/question_schemas",
|
||||
views.collection_question_schemas,
|
||||
|
||||
+89
-2
@@ -49,6 +49,7 @@ from .forms import (
|
||||
ConditionForm,
|
||||
FindingForm,
|
||||
JsonAnswerForm,
|
||||
PriorCaseForm,
|
||||
QuestionSchemaForm,
|
||||
ResourceForm,
|
||||
SelfReviewForm,
|
||||
@@ -66,6 +67,7 @@ from .models import (
|
||||
Case,
|
||||
CaseCollection,
|
||||
CaseDetail,
|
||||
CasePrior,
|
||||
CidReportAnswer,
|
||||
Condition,
|
||||
Differential,
|
||||
@@ -1631,6 +1633,78 @@ def collection_take_start(request, pk):
|
||||
request, "atlas/collection_review_start.html", template_variables
|
||||
)
|
||||
|
||||
@user_is_collection_author_or_atlas_editor
|
||||
def collection_case_priors(request, exam_id, case_id):
|
||||
case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id)
|
||||
|
||||
|
||||
if request.htmx:
|
||||
if "remove" in request.POST:
|
||||
p = CasePrior.objects.get(case_detail=case_detail, prior_case=request.POST["remove"])
|
||||
p.delete()
|
||||
return HttpResponse(f"Case removed")
|
||||
elif "prior_case_id" in request.POST:
|
||||
prior_case = Case.objects.get(pk=request.POST["prior_case_id"])
|
||||
p, created = CasePrior.objects.get_or_create(case_detail=case_detail, prior_case=prior_case)
|
||||
p.relation_text=request.POST["relation"]
|
||||
|
||||
if not p.relation_text:
|
||||
return HttpResponse("You need to enter text to describe the relationship between the cases")
|
||||
|
||||
p.save()
|
||||
|
||||
return HttpResponse(f"Case added")
|
||||
else:
|
||||
return HttpResponse("False")
|
||||
raise Http404()
|
||||
|
||||
collection = case_detail.collection
|
||||
|
||||
added_priors = CasePrior.objects.filter(case_detail=case_detail)
|
||||
|
||||
added_relation_map = {}
|
||||
for prior in added_priors:
|
||||
added_relation_map[prior.prior_case] = prior.relation_text
|
||||
|
||||
case = case_detail.case
|
||||
|
||||
available_cases = case.get_all_prior_cases()
|
||||
|
||||
available_priors = []
|
||||
for case in available_cases:
|
||||
if case in added_relation_map:
|
||||
available_priors.append((case, True, added_relation_map[case]))
|
||||
else:
|
||||
available_priors.append((case, False, ""))
|
||||
|
||||
|
||||
form = PriorCaseForm()
|
||||
|
||||
case_number, case_count = collection.get_index_of_case(
|
||||
case_detail.case, case_count=True
|
||||
)
|
||||
|
||||
previous = collection.get_previous_case(case_detail.case)
|
||||
next = collection.get_next_case(case_detail.case)
|
||||
|
||||
|
||||
return render(
|
||||
request,
|
||||
"atlas/collection_case_priors.html",
|
||||
{
|
||||
"case_detail": case_detail,
|
||||
"form": form,
|
||||
#"example_form": example_form,
|
||||
"collection": collection,
|
||||
"case": case_detail.case,
|
||||
"previous": previous,
|
||||
"next": next,
|
||||
"collection_length": case_count,
|
||||
"case_number": case_number,
|
||||
"added_priors": added_priors,
|
||||
"available_priors": available_priors,
|
||||
},
|
||||
)
|
||||
|
||||
@user_is_collection_author_or_atlas_editor
|
||||
def collection_case_details(request, exam_id, case_id):
|
||||
@@ -2039,8 +2113,8 @@ def collection_case_view_take(
|
||||
cid_user_exam.start_time = timezone.now()
|
||||
cid_user_exam.save()
|
||||
|
||||
if not collection.review_only():
|
||||
case_detail = CaseDetail.objects.get(case=case, collection=collection)
|
||||
if not collection.review_only():
|
||||
if collection.collection_type == "REP":
|
||||
if cid is not None:
|
||||
answer = case_detail.cidreportanswer_set.filter(cid=cid).first()
|
||||
@@ -2116,7 +2190,19 @@ def collection_case_view_take(
|
||||
|
||||
series_list = case.series.all().prefetch_related("images", "examination", "plane")
|
||||
|
||||
print("case_no")
|
||||
prior_cases = case_detail.caseprior_set.all()
|
||||
|
||||
series_to_load = []
|
||||
|
||||
for series in series_list:
|
||||
series_to_load.append((series, False, ""))
|
||||
|
||||
for prior in prior_cases:
|
||||
for series in prior.prior_case.get_series():
|
||||
series_to_load.append((series, True, prior.relation_text))
|
||||
|
||||
|
||||
|
||||
previous = case_number > 0
|
||||
next = case_number < (case_count - 1)
|
||||
|
||||
@@ -2160,6 +2246,7 @@ def collection_case_view_take(
|
||||
"collection": collection,
|
||||
"case": case,
|
||||
"series_list": series_list,
|
||||
"series_to_load": series_to_load,
|
||||
"case_number": case_number,
|
||||
"previous": previous,
|
||||
"next": next,
|
||||
|
||||
Reference in New Issue
Block a user