improve case quesitons
This commit is contained in:
+7
-3
@@ -736,13 +736,17 @@ class AnswerJSONForm(Form):
|
|||||||
options = {}
|
options = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SvelteJSONEditorWidgetOverride(SvelteJSONEditorWidget):
|
||||||
|
template_name = "atlas/svelte_jsoneditor_widget_override.html"
|
||||||
|
|
||||||
class CaseDetailForm(ModelForm):
|
class CaseDetailForm(ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CaseDetail
|
model = CaseDetail
|
||||||
fields = ["sort_order", "question_schema", "question_answers"]#, "user"]
|
fields = ["question_schema", "question_answers"]#, "user"]
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
"question_schema": SvelteJSONEditorWidget(),
|
"question_schema": SvelteJSONEditorWidgetOverride(),
|
||||||
"question_answers": SvelteJSONEditorWidget(),
|
"question_answers": SvelteJSONEditorWidgetOverride(),
|
||||||
}
|
}
|
||||||
@@ -968,6 +968,9 @@ class CaseDetail(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("sort_order",)
|
ordering = ("sort_order",)
|
||||||
|
|
||||||
|
def get_question_schema(self):
|
||||||
|
return json.dumps(self.question_schema)
|
||||||
|
|
||||||
|
|
||||||
class BaseReportAnswer(models.Model):
|
class BaseReportAnswer(models.Model):
|
||||||
question = models.ForeignKey(CaseDetail, on_delete=models.CASCADE)
|
question = models.ForeignKey(CaseDetail, on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -5,20 +5,48 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{case_detail.collection.name}} / {{case_detail.case.pk}}</h2>
|
<h2>{{case_detail.collection.name}} / {{case_detail.case.pk}}</h2>
|
||||||
|
|
||||||
|
<p>Collection: <a href="{% url 'atlas:collection_detail' case_detail.collection.pk %}">{{case_detail.collection.name}}</a>, Case: <a href="{% url 'atlas:case_detail' case_detail.case.pk %}">{{case_detail.case.title}}</a></p>
|
||||||
|
|
||||||
|
<p>This form allows you to add questions to a case. A example of how the quesiton will be displayed is shown below.</p>
|
||||||
|
<p>If answers are supplied the question will be automarked</p>
|
||||||
|
|
||||||
<p>See https://django-jsonform.readthedocs.io/en/latest/guide/inputs.html for formatting</p>
|
<p>See https://django-jsonform.readthedocs.io/en/latest/guide/inputs.html for formatting</p>
|
||||||
|
|
||||||
|
Import schema from:
|
||||||
|
<button class="btn btn-primary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasRight" aria-controls="offcanvasRight"
|
||||||
|
hx-get="{% url 'atlas:collection_question_schemas' case_detail.collection.id %}"
|
||||||
|
hx-target=".offcanvas-body"
|
||||||
|
>Collection questions</button>
|
||||||
|
<button class="btn btn-primary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasRight" aria-controls="offcanvasRight"
|
||||||
|
hx-get="{% url 'atlas:question_schemas_preset' %}"
|
||||||
|
hx-target=".offcanvas-body"
|
||||||
|
>Preset questions</button>
|
||||||
|
|
||||||
|
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight" aria-labelledby="offcanvasRightLabel"
|
||||||
|
>
|
||||||
|
<div class="offcanvas-header">
|
||||||
|
<h5 id="offcanvasRightLabel">Question schemas</h5>
|
||||||
|
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body">
|
||||||
|
...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<button type="submit" value="save" name="submit">Submit</button>
|
<button type="submit" value="save" name="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h2>Blank form</h2>
|
{% comment %} <h2>Blank form</h2>
|
||||||
<form>
|
<form>
|
||||||
{{ blank_form}}
|
{{ blank_form}}
|
||||||
</form>
|
</form> {% endcomment %}
|
||||||
|
|
||||||
|
<h2>Question display</h2>
|
||||||
|
This shows the form as it will be displayed (with the correct answers). If you wish to edit a correct answer you can change it below.
|
||||||
|
|
||||||
<h2>Correct answers</h2>
|
|
||||||
<form method="POST" class="post-form">
|
<form method="POST" class="post-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{example_form}}
|
{{example_form}}
|
||||||
@@ -32,5 +60,20 @@
|
|||||||
{% comment %} {{ example_form.media }} {% endcomment %}
|
{% comment %} {{ example_form.media }} {% endcomment %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script>
|
||||||
<script src="/static/django_jsonforms/jsoneditor_init.js"></script>
|
<script src="/static/django_jsonforms/jsoneditor_init.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
|
// Put the JS code that you want to execute here
|
||||||
|
console.log($('.use-layout').each((n, el) => {
|
||||||
|
el.addEventListener('click', (e) => {
|
||||||
|
console.log(e.target.parentElement.querySelector('.schema').dataset.schema);
|
||||||
|
window.jsoneditor_id_question_schema.set(
|
||||||
|
{
|
||||||
|
json: JSON.parse(e.target.parentElement.querySelector('.schema').dataset.schema)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% comment %} <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script> {% endcomment %}
|
||||||
|
<ol>
|
||||||
|
{% for case_detail in case_details %}
|
||||||
|
|
||||||
|
<li id="case-detail-{{case_detail.pk}}"{% if case_detail.question_schema %} class="cases"{% endif %}>
|
||||||
|
<h2>Case: {{ case_detail.case.title }}</h2>
|
||||||
|
|
||||||
|
{% if case_detail.question_schema %}
|
||||||
|
<button class="btn btn-sm btn-secondary use-layout">Use layout</button>
|
||||||
|
<details><summary>Schema:</summary>
|
||||||
|
<div class="schema" data-schema='{{ case_detail.get_question_schema }}'>{{ case_detail.question_schema }}</div>
|
||||||
|
</details>
|
||||||
|
<div class="form"></div>
|
||||||
|
{% else %}
|
||||||
|
<p>No question(s)</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$(".cases").each(function(n, el){
|
||||||
|
console.log(n, el);
|
||||||
|
form_el = $(el).find(".form").get(0);
|
||||||
|
schema = $(el).find(".schema").get(0).dataset.schema;
|
||||||
|
console.log(schema)
|
||||||
|
console.log(form_el)
|
||||||
|
form = new JSONEditor(form_el, {
|
||||||
|
schema: JSON.parse(schema),
|
||||||
|
theme: 'bootstrap4',
|
||||||
|
disable_edit_json: true,
|
||||||
|
disable_collapse: true,
|
||||||
|
disable_properties: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% comment %} <script src="{% static 'js/jquery-3.7.1.min.js' %}"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script> {% endcomment %}
|
||||||
|
<ol>
|
||||||
|
{% for schema in schemas %}
|
||||||
|
|
||||||
|
<li class="schemas">
|
||||||
|
<button class="btn btn-sm btn-secondary use-layout">Use layout</button>
|
||||||
|
<details><summary>Schema:</summary>
|
||||||
|
<div class="schema" data-schema='{{schema|safe}}'>{{schema}}</div>
|
||||||
|
</details>
|
||||||
|
<div class="form"></div>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$(".schemas").each(function(n, el){
|
||||||
|
console.log(n, el);
|
||||||
|
form_el = $(el).find(".form").get(0);
|
||||||
|
schema = $(el).find(".schema").get(0).dataset.schema;
|
||||||
|
console.log(schema)
|
||||||
|
console.log(form_el)
|
||||||
|
form = new JSONEditor(form_el, {
|
||||||
|
schema: JSON.parse(schema),
|
||||||
|
theme: 'bootstrap4',
|
||||||
|
disable_edit_json: true,
|
||||||
|
disable_collapse: true,
|
||||||
|
disable_properties: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<div id="jsoneditor_{{ widget.attrs.id }}" class="svelte-jsoneditor-wrapper"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { JSONEditor } from '{% static "django_svelte_jsoneditor/js/svelte_jsoneditor.js" %}'
|
||||||
|
|
||||||
|
const editor = new JSONEditor({
|
||||||
|
target: document.getElementById('jsoneditor_{{ widget.attrs.id }}'),
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
text: document.getElementById('{{ widget.attrs.id }}').value || undefined
|
||||||
|
},
|
||||||
|
onChange: (updatedContent, previousContent, { contentErrors, patchResult }) => {
|
||||||
|
const value = updatedContent.json ? JSON.stringify(updatedContent.json) : updatedContent.text
|
||||||
|
document.getElementById('{{ widget.attrs.id }}').value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
editor.updateProps({{ widget.props|safe }})
|
||||||
|
window.jsoneditor_{{ widget.attrs.id }} = editor
|
||||||
|
</script>
|
||||||
@@ -116,6 +116,16 @@ urlpatterns = [
|
|||||||
views.collection_case_details,
|
views.collection_case_details,
|
||||||
name="collection_case_details",
|
name="collection_case_details",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"collection/<int:exam_id>/question_schemas",
|
||||||
|
views.collection_question_schemas,
|
||||||
|
name="collection_question_schemas",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"question_schemas_preset",
|
||||||
|
views.question_schemas_preset,
|
||||||
|
name="question_schemas_preset",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"collection/<int:exam_id>/cids/<int:cid>/delete_answers",
|
"collection/<int:exam_id>/cids/<int:cid>/delete_answers",
|
||||||
views.delete_collection_cid_answers,
|
views.delete_collection_cid_answers,
|
||||||
|
|||||||
+80
-12
@@ -1568,15 +1568,22 @@ def collection_case_details(request, exam_id, case_id):
|
|||||||
case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id)
|
case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id)
|
||||||
|
|
||||||
|
|
||||||
if request.method == 'POST' and request.POST.get('submit') == 'answer':
|
if request.method == 'POST':
|
||||||
|
|
||||||
|
# Called if the user saves the correct answer form
|
||||||
|
if request.POST.get('submit') == 'answer':
|
||||||
#answers = request.POST
|
#answers = request.POST
|
||||||
case_detail.question_answers = json.loads(request.POST.get("json_answer"))
|
case_detail.question_answers = json.loads(request.POST.get("json_answer"))
|
||||||
case_detail.save()
|
case_detail.save()
|
||||||
example_form = JsonAnswerForm(request.POST, case_detail=case_detail)
|
example_form = JsonAnswerForm(request.POST, case_detail=case_detail)
|
||||||
|
form = CaseDetailForm(instance=case_detail)
|
||||||
else:
|
# Called if the user saves the main form
|
||||||
post_data = None
|
elif request.POST.get('submit') == 'save':
|
||||||
|
form = CaseDetailForm(request.POST, instance=case_detail)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
# Add any additional logic or redirection here
|
||||||
|
#example_form = JsonAnswerForm(request.POST, case_detail=case_detail)
|
||||||
if case_detail.question_schema is not None and case_detail.question_answers is not None:
|
if case_detail.question_schema is not None and case_detail.question_answers is not None:
|
||||||
question_keys = set(case_detail.question_schema["properties"].keys())
|
question_keys = set(case_detail.question_schema["properties"].keys())
|
||||||
answer_keys = set(case_detail.question_answers.keys())
|
answer_keys = set(case_detail.question_answers.keys())
|
||||||
@@ -1594,24 +1601,54 @@ def collection_case_details(request, exam_id, case_id):
|
|||||||
|
|
||||||
post_data = request.POST.copy()
|
post_data = request.POST.copy()
|
||||||
post_data["json_answer"]= json.dumps(answers)
|
post_data["json_answer"]= json.dumps(answers)
|
||||||
|
case_detail.question_answers = answers
|
||||||
|
case_detail.save()
|
||||||
example_form = JsonAnswerForm(post_data, case_detail=case_detail)
|
example_form = JsonAnswerForm(post_data, case_detail=case_detail)
|
||||||
|
|
||||||
if request.method == 'POST' and request.POST.get('submit') == 'save':
|
form = CaseDetailForm(instance=case_detail)
|
||||||
form = CaseDetailForm(request.POST, instance=case_detail)
|
# This shouldn't happen
|
||||||
if form.is_valid():
|
|
||||||
form.save()
|
|
||||||
# Add any additional logic or redirection here
|
|
||||||
else:
|
else:
|
||||||
|
assert False
|
||||||
|
form = CaseDetailForm(request.POST, instance=case_detail)
|
||||||
|
|
||||||
|
else:
|
||||||
|
post_data = request.POST.copy()
|
||||||
|
post_data["json_answer"]= json.dumps(case_detail.question_answers)
|
||||||
|
example_form = JsonAnswerForm(post_data, case_detail=case_detail)
|
||||||
|
|
||||||
form = CaseDetailForm(instance=case_detail)
|
form = CaseDetailForm(instance=case_detail)
|
||||||
|
pass
|
||||||
|
#post_data = None
|
||||||
|
|
||||||
blank_form = JsonAnswerForm(case_detail=case_detail)
|
#if case_detail.question_schema is not None and case_detail.question_answers is not None:
|
||||||
|
# question_keys = set(case_detail.question_schema["properties"].keys())
|
||||||
|
# answer_keys = set(case_detail.question_answers.keys())
|
||||||
|
|
||||||
|
# answers = {}
|
||||||
|
# for key, value in case_detail.question_answers.items():
|
||||||
|
# if key in question_keys:
|
||||||
|
# answers[key] = value
|
||||||
|
|
||||||
|
# diff = question_keys - answer_keys
|
||||||
|
|
||||||
|
# for key in diff:
|
||||||
|
# answers[key] = ""
|
||||||
|
|
||||||
|
|
||||||
|
# post_data = request.POST.copy()
|
||||||
|
# post_data["json_answer"]= json.dumps(answers)
|
||||||
|
# case_detail.question_answers = answers
|
||||||
|
#example_form = JsonAnswerForm(post_data, case_detail=case_detail)
|
||||||
|
|
||||||
|
#form = CaseDetailForm(instance=case_detail)
|
||||||
|
|
||||||
|
#blank_form = JsonAnswerForm(case_detail=case_detail)
|
||||||
|
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request, "atlas/collection_case_detail.html", {"case_detail": case_detail,
|
request, "atlas/collection_case_detail.html", {"case_detail": case_detail,
|
||||||
"form": form, "example_form": example_form,
|
"form": form, "example_form": example_form,
|
||||||
"blank_form": blank_form}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@user_is_collection_author_or_atlas_editor
|
@user_is_collection_author_or_atlas_editor
|
||||||
@@ -2497,3 +2534,34 @@ class SeriesAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
|
|||||||
context = super(SeriesAuthorUpdate, self).get_context_data(**kwargs)
|
context = super(SeriesAuthorUpdate, self).get_context_data(**kwargs)
|
||||||
context["collection"] = context["object"]
|
context["collection"] = context["object"]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def question_schemas_preset(request):
|
||||||
|
schemas = [
|
||||||
|
json.dumps({'type': 'object', 'title': 'Questions', 'required': ['laterality', 'diagnosis'], 'properties': {'test': {'type': 'string', 'title': 'test string', 'description': 'What side is the lesion on?'}, 'diagnosis': {'enum': ['Meningioma', 'Schwannoma', 'Neurofibroma', 'Ependymoma'], 'type': 'string', 'title': 'Diagnosis', 'description': 'What is the diagnosis?'}, 'laterality': {'enum': ['left', 'right'], 'type': 'string', 'title': 'Laterality', 'widget': 'radio', 'description': 'What side is the lesion on?'}, 'laterality (copy 2)': {'enum': ['left', 'right'], 'type': 'string', 'title': 'Lateralityaoue', 'widget': 'radio', 'description': 'What side is the lesion on?'}}}),
|
||||||
|
]
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"atlas/question_schemas_preset.html",
|
||||||
|
{
|
||||||
|
"schemas": schemas,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def collection_question_schemas(request, exam_id: int):
|
||||||
|
collection = get_object_or_404(CaseCollection, pk=exam_id)
|
||||||
|
|
||||||
|
questions = collection.cases.all().prefetch_related()
|
||||||
|
|
||||||
|
case_details = CaseDetail.objects.filter(
|
||||||
|
case__in=questions, collection=collection
|
||||||
|
).prefetch_related("case").order_by("sort_order")
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"atlas/collection_question_schemas.html",
|
||||||
|
{
|
||||||
|
"collection": collection,
|
||||||
|
"case_details": case_details,
|
||||||
|
},
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user