Numerous improvements to case questions

This commit is contained in:
Ross
2024-06-24 08:59:15 +01:00
parent db43bcf557
commit 64cebf50a0
11 changed files with 348 additions and 74 deletions
+32 -13
View File
@@ -521,9 +521,9 @@ SeriesImageFormSet = inlineformset_factory(
class JsonAnswerForm(Form):
json_answer = JSONSchemaField(
schema = "schema/report.json",
schema = {},
#options = 'schema/options.json'
options = {}
options = 'schema/options.json'
)
class Meta:
fields = ["json_answer"]
@@ -531,16 +531,11 @@ class JsonAnswerForm(Form):
def __init__(self, *args, case_detail, **kwargs):
super(JsonAnswerForm, self).__init__(*args, **kwargs)
if case_detail.question_schema is not None:
self.fields["json_answer"] = JSONSchemaField(schema=case_detail.question_schema, options={})
self.fields["json_answer"] = JSONSchemaField(schema=case_detail.question_schema, options='schema/options.json')
class BaseReportAnswerForm(ModelForm):
json_answer = JSONSchemaField(
schema = "schema/report.json",
#options = 'schema/options.json'
options = {}
)
class Meta:
fields = ["answer", "json_answer"]
fields = ["answer"]
widgets = {
# "normal": RadioSelect(
@@ -548,7 +543,7 @@ class BaseReportAnswerForm(ModelForm):
# (False, 'No')])
# "findings": TinyMCE(attrs={"cols": 80, "rows": 20}),
# "mark_scheme": TinyMCE(attrs={"cols": 80, "rows": 30}),
"answer": Textarea(attrs={"cols": 100, "rows": 5}),
"answer": Textarea(attrs={"cols": 100, "rows": 5, "placeholder":"Report text"}),
}
# help_texts = {
@@ -558,9 +553,26 @@ class BaseReportAnswerForm(ModelForm):
def __init__(self, *args, case_detail, **kwargs):
super(BaseReportAnswerForm, self).__init__(*args, **kwargs)
self.fields["answer"].required = False
class BaseQuestionAnswerForm(ModelForm):
json_answer = JSONSchemaField(
schema = {},
options = 'schema/options.json'
)
class Meta:
fields = ["json_answer"]
labels = {"json_answer": ""}
# help_texts = {
# "answer": "Write your answer in here."
# }
def __init__(self, *args, case_detail, **kwargs):
super(BaseQuestionAnswerForm, self).__init__(*args, **kwargs)
#self.fields["json_answer"].schema = case_detail.question_schema
if case_detail.question_schema is not None:
self.fields["json_answer"] = JSONSchemaField(schema=case_detail.question_schema, options={})
self.fields["json_answer"] = JSONSchemaField(schema=case_detail.question_schema, options="schema/options.json")
self.fields["json_answer"].label = ""
class CidReportAnswerForm(BaseReportAnswerForm):
@@ -571,6 +583,14 @@ class UserReportAnswerForm(BaseReportAnswerForm):
class Meta(BaseReportAnswerForm.Meta):
model = UserReportAnswer
class CidQuestionAnswerForm(BaseQuestionAnswerForm):
class Meta(BaseQuestionAnswerForm.Meta):
model = CidReportAnswer
class UserQuestionAnswerForm(BaseQuestionAnswerForm):
class Meta(BaseQuestionAnswerForm.Meta):
model = UserReportAnswer
class CidReportAnswerMarkForm(ModelForm):
class Meta:
model = CidReportAnswer
@@ -732,8 +752,7 @@ class AnswerJSONForm(Form):
json = JSONSchemaField(
schema = 'schema/schema.json',
#options = 'schema/options.json'
options = {}
options = 'schema/options.json'
)
@@ -0,0 +1,23 @@
# Generated by Django 5.0.2 on 2024-06-10 22:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0053_casedetail_question_answers'),
]
operations = [
migrations.AddField(
model_name='casecollection',
name='feedback_once_collection_complete',
field=models.BooleanField(default=True, help_text='If true feedback is only given once the collection is complete. If false feedback is given after each case.'),
),
migrations.AlterField(
model_name='casecollection',
name='collection_type',
field=models.CharField(choices=[('REV', 'Review'), ('REP', 'Report'), ('QUE', 'Question'), ('VIV', 'Viva')], default='REP', max_length=3),
),
]
+33
View File
@@ -774,6 +774,8 @@ class CaseCollection(ExamOrCollectionGenericBase):
help_text="If true allows users self complete and review cases in a self directed way.",
)
feedback_once_collection_complete = models.BooleanField(default=True, help_text="If true feedback is only given once the collection is complete. If false feedback is given after each case.")
class COLLECTION_TYPE_CHOICES(models.TextChoices):
REVIEW = (
"REV",
@@ -783,6 +785,10 @@ class CaseCollection(ExamOrCollectionGenericBase):
"REP",
_("Report"),
)
QUESTION = (
"QUE",
_("Question"),
)
VIVA = (
"VIV",
_("Viva"),
@@ -874,6 +880,33 @@ class CaseCollection(ExamOrCollectionGenericBase):
"""Returns the cases in the collection in order of the CaseDetail sort_order"""
return self.cases.all().order_by("casedetail__sort_order")
def get_next_case(self, case):
cases = list(self.get_cases())
try:
return cases[cases.index(case) + 1]
except IndexError:
return None
def get_previous_case(self, case):
cases = list(self.get_cases())
new_index = cases.index(case) - 1
if new_index < 0:
return None
else:
return cases[new_index]
def get_index_of_case(self, case, case_count=False):
cases = list(self.get_cases())
if case_count:
return cases.index(case), len(cases)
else:
return cases.index(case)
def get_case_by_index(
self, case_index: int, case_count=False
) -> Case | Tuple[Case, int]:
+5
View File
@@ -0,0 +1,5 @@
{
"disable_edit_json": true,
"disable_collapse": true,
"disable_properties": true
}
@@ -3,14 +3,25 @@
{% block content %}
<h2>{{case_detail.collection.name}} / {{case_detail.case.pk}}</h2>
<div>
<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>
{% 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 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>The question system is built using https://github.com/json-editor/json-editor, this allows for highly flexible forms / question design at a cost of some complexity. To help with this a number of preset questions are avalible to choose from (these can then be edited if needed). It is also possible to copy questions from other cases in the collection. </p>
Import schema from:
<button class="btn btn-primary btn-sm" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasRight" aria-controls="offcanvasRight"
@@ -44,14 +55,17 @@
{{ blank_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.
<form method="POST" class="post-form">
{% csrf_token %}
{{example_form}}
<button type="submit" value="answer" name="submit">Save Correct Answers</button>
</form>
{% if case_detail.question_schema %}
<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.
<form method="POST" class="post-form">
{% csrf_token %}
{{example_form}}
<button type="submit" value="answer" name="submit">Save Correct Answers</button>
</form>
{% endif %}
{% endblock %}
@@ -9,7 +9,7 @@
{% endif %}
{% if completed %}
{% if question_completed %}
<span class="stamp-white">REVIEW</span>
{% endif %}
@@ -18,7 +18,7 @@
{% if not completed %}
{% if not question_completed %}
{% if resources %}
<h5>Resources</h4>
<ul class="no-list-style">
@@ -109,7 +109,7 @@
</p>
{% endif %}
{% if completed %}
{% if question_completed %}
{% if resources %}
<h5>Resources</h4>
<ul class="no-list-style">
@@ -127,15 +127,15 @@
{% endif %}
<form method="POST" class="post-form">{% csrf_token %}
{% if collection.collection_type == "REP" %}
{% if collection.collection_type == "REP" or collection.collection_type == "QUE" %}
{{form.json.errors}}
<div class="form-contents">
<fieldset {% if completed %}disabled="disabled"{% endif %}>
<fieldset {% if question_completed %}disabled="disabled"{% endif %}>
{{form}}
</fieldset>
</div>
<p>
{% if completed %}
{% if question_completed %}
{% if collection.self_review %}
{% if self_review %}
@@ -167,7 +167,7 @@
{% endif %}
{% endif %}
{% if collection.self_review and not completed %}
{% if collection.self_review and not collection.feedback_once_collection_complete and not question_completed %}
<button type="submit" name="complete_case" class="save btn btn-default">Finish Case</button>
{% endif %}
@@ -202,6 +202,7 @@
{% endblock %}
{% block js %}
{{ form.media }}
{% 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 %}
@@ -214,6 +215,92 @@
window.loadDicomViewer(window.images[0])
}, 500);
})
</script>
{% comment %} $('document').ready(function() {
// Get value from either a json string or url pointing to a json file
function process(value) {
var isjson=true;
var result;
try {
result = JSON.parse(value);
} catch(e) {
isjson=false;
}
if (isjson) {
return result;
} else {
return $.getJSON(value)
.then(function (response) {
return response;
});
}
}
$('.editor_holder').each(function() {
// Get the DOM Element
var element = $(this).get(0);
console.log("el", element)
var options_text = $(this).attr('options')
var schema_text = $(this).attr('schema')
var schema = process(schema_text);
var options = process(options_text);
var name = $(this).attr('name');
var hidden_identifier = 'input[name=' + name + ']';
var initial = $(hidden_identifier).val();
// Check if editor is within form
var form = $(this).closest('form')
console.log("form", form)
//Wait for any ajax requests to complete
$.when(schema, options).done(function(schemaresult, optionsresult) {
optionsresult.form_name_root = name;
// Pass initial value though to editor
if (initial) {
optionsresult.startval = JSON.parse(initial);
}
optionsresult.schema = schemaresult;
// console.log(options);
var editor = new JSONEditor(element, optionsresult);
console.log("editor", editor)
if (form) {
$(form).submit(function(e) {
console.log("submitting")
// Set the hidden field value to the editors value
$(hidden_identifier).val(JSON.stringify(editor.getValue()));
// Disable the editor so it's values wont be submitted
//editor.disable();
// Validate the editor's current value against the schema
const errors = editor.validate();
if (errors.length) {
// errors is an array of objects, each with a `path`, `property`, and `message` parameter
// `property` is the schema keyword that triggered the validation error (e.g. "minLength")
// `path` is a dot separated path into the JSON object (e.g. "root.path.to.field")
console.log(errors);
}
else {
console.log("valid");
}
console.log(editor.getValue());
//e.preventDefault();
})
} else {
console.log("No form found")
}
})
});
})
{% endcomment %}
</script>
{% endblock js %}
+5 -1
View File
@@ -15,7 +15,11 @@
<ol id="full-question-list" class="sortable">
{% for casedetail in casesdetails %}
<li data-question_pk={{casedetail.case.pk}}><a title="sort_order: {{casedetail.sort_order}}" href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=forloop.counter0 %}">Case {{forloop.counter}}</a>
: {{casedetail.case.title}} (<a href='{% url "atlas:collection_case_details" casedetail.collection.pk casedetail.case.pk %}'>edit</a>)
: {{casedetail.case.title}}
{% if collection.collection_type == "QUE" %}
(<a href='{% url "atlas:collection_case_details" casedetail.collection.pk casedetail.case.pk %}'>edit</a>)
{% endif %}
</li>
{% endfor %}
@@ -9,7 +9,7 @@
<h2>Case: {{ case_detail.case.title }}</h2>
{% if case_detail.question_schema %}
<button class="btn btn-sm btn-secondary use-layout">Use layout</button>
<button class="btn btn-sm btn-secondary use-layout">Use schema</button>
<details><summary>Schema:</summary>
<div class="schema" data-schema='{{ case_detail.get_question_schema }}'>{{ case_detail.question_schema }}</div>
</details>
@@ -6,7 +6,7 @@
{% for schema in schemas %}
<li class="schemas">
<button class="btn btn-sm btn-secondary use-layout">Use layout</button>
<button class="btn btn-sm btn-secondary use-layout">Use schema</button>
<details><summary>Schema:</summary>
<div class="schema" data-schema='{{schema|safe}}'>{{schema}}</div>
</details>
+127 -39
View File
@@ -42,6 +42,7 @@ from .forms import (
CaseDetailForm,
CaseForm,
CaseResourceFormSet,
CidQuestionAnswerForm,
CidReportAnswerForm,
CidReportAnswerMarkForm,
ConditionAutocompleteForm,
@@ -57,6 +58,7 @@ from .forms import (
SeriesFindingForm,
CaseDifferentialFormSet,
StructureForm,
UserQuestionAnswerForm,
UserReportAnswerForm,
)
from .models import (
@@ -186,6 +188,7 @@ def case_detail(request, pk):
# logging.debug(atlas.subspecialty.first().name.all())
return render(request, "atlas/case_detail.html", {"case": case})
@login_required
@user_is_author_or_atlas_series_checker_or_atlas_marker
def series_thumbnail(request, pk, finding_pk=None):
@@ -372,6 +375,7 @@ def resource_detail(request, pk):
return render(request, "atlas/resource_detail.html", {"resource": resource})
@login_required
def resource_view(request, pk):
resource = get_object_or_404(Resource, pk=pk)
@@ -452,10 +456,12 @@ def user_uploads_series(request, series_instance_uid: str):
{"dicoms": dicoms, "series_id": series_instance_uid},
)
@login_required
def new_uploads(request):
return render(request, "atlas/new_uploads.html", {})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def all_uploads(request, case_id: int | None = None):
@@ -712,7 +718,8 @@ class CaseCollectionUpdate(RevisionMixin, AuthorOrCheckerRequiredMixin, UpdateVi
context["case_formset"].full_clean()
else:
context["case_formset"] = CaseCollectionCaseFormSet(
instance=self.object, form_kwargs={"user": self.request.user, "collection": self.object}
instance=self.object,
form_kwargs={"user": self.request.user, "collection": self.object},
)
context["collection"] = context["casecollection"]
@@ -1554,7 +1561,7 @@ def collection_take_start(request, pk):
}
match collection.collection_type:
case "REP":
case x if x in ("REP", "QUE"):
return render(
request, "atlas/collection_take_start.html", template_variables
)
@@ -1563,28 +1570,37 @@ def collection_take_start(request, pk):
request, "atlas/collection_review_start.html", template_variables
)
@user_is_collection_author_or_atlas_editor
def collection_case_details(request, exam_id, case_id):
case_detail = CaseDetail.objects.get(case=case_id, collection=exam_id)
collection = case_detail.collection
if not collection.collection_type == "QUE":
raise Http404("Collection not in question mode")
if request.method == "POST":
if request.method == 'POST':
# Called if the user saves the correct answer form
if request.POST.get('submit') == 'answer':
#answers = request.POST
if request.POST.get("submit") == "answer":
# answers = request.POST
case_detail.question_answers = json.loads(request.POST.get("json_answer"))
case_detail.save()
example_form = JsonAnswerForm(request.POST, case_detail=case_detail)
form = CaseDetailForm(instance=case_detail)
# Called if the user saves the main form
elif request.POST.get('submit') == 'save':
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:
# example_form = JsonAnswerForm(request.POST, case_detail=case_detail)
post_data = request.POST.copy()
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())
@@ -1598,10 +1614,8 @@ def collection_case_details(request, exam_id, case_id):
for key in diff:
answers[key] = ""
post_data = request.POST.copy()
post_data["json_answer"]= json.dumps(answers)
case_detail.question_answers = 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)
@@ -1613,14 +1627,14 @@ def collection_case_details(request, exam_id, case_id):
else:
post_data = request.POST.copy()
post_data["json_answer"]= json.dumps(case_detail.question_answers)
post_data["json_answer"] = json.dumps(case_detail.question_answers)
example_form = JsonAnswerForm(post_data, case_detail=case_detail)
form = CaseDetailForm(instance=case_detail)
pass
#post_data = None
# post_data = None
#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())
# answer_keys = set(case_detail.question_answers.keys())
@@ -1634,23 +1648,37 @@ def collection_case_details(request, exam_id, case_id):
# 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)
# example_form = JsonAnswerForm(post_data, case_detail=case_detail)
#form = CaseDetailForm(instance=case_detail)
# form = CaseDetailForm(instance=case_detail)
#blank_form = JsonAnswerForm(case_detail=case_detail)
# blank_form = JsonAnswerForm(case_detail=case_detail)
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_detail.html", {"case_detail": case_detail,
"form": form, "example_form": example_form,
}
request,
"atlas/collection_case_detail.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,
},
)
@user_is_collection_author_or_atlas_editor
def collection_mark_overview(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
@@ -1861,14 +1889,15 @@ def collection_take_overview(
},
)
def collection_case_view_take_user_answers(request, pk: int, case_number: int):
return collection_case_view_take_answers(request, pk, case_number)
def collection_case_view_take_answers(
request, pk: int, case_number: int, cid=None, passcode=None
):
"""
"""
""" """
collection = get_object_or_404(CaseCollection, pk=pk)
collection.check_user_can_take(cid, passcode, request.user)
@@ -1891,6 +1920,7 @@ def collection_case_view_take_answers(
},
)
def collection_case_view_take_user(request, pk: int, case_number: int):
return collection_case_view_take(request, pk, case_number)
@@ -1932,9 +1962,8 @@ def collection_case_view_take(
cid_user_exam.save()
if not collection.review_only():
case_detail = CaseDetail.objects.get(case=case, collection=collection)
if collection.collection_type == "REP":
case_detail = CaseDetail.objects.get(case=case, collection=collection)
if cid is not None:
answer = case_detail.cidreportanswer_set.filter(cid=cid).first()
ReportAnswerForm = CidReportAnswerForm
@@ -1943,12 +1972,24 @@ def collection_case_view_take(
user=request.user
).first()
ReportAnswerForm = UserReportAnswerForm
elif collection.collection_type == "QUE":
if cid is not None:
answer = case_detail.cidreportanswer_set.filter(cid=cid).first()
ReportAnswerForm = CidQuestionAnswerForm
else:
answer = case_detail.userreportanswer_set.filter(
user=request.user
).first()
ReportAnswerForm = UserQuestionAnswerForm
if request.method == "POST":
if collection.collection_type == "REP":
if collection.collection_type in ("REP", "QUE"):
if not collection.publish_results:
if answer:
form = ReportAnswerForm(request.POST, instance=answer, case_detail=case_detail)
form = ReportAnswerForm(
request.POST, instance=answer, case_detail=case_detail
)
else:
form = ReportAnswerForm(request.POST, case_detail=case_detail)
@@ -1993,7 +2034,7 @@ def collection_case_view_take(
**kwargs,
)
else:
if collection.collection_type == "REP":
if collection.collection_type in ("REP", "QUE"):
form = ReportAnswerForm(instance=answer, case_detail=case_detail)
series_list = case.series.all().prefetch_related("images", "examination", "plane")
@@ -2007,9 +2048,13 @@ def collection_case_view_take(
if (
collection.publish_results
or cid_user_exam.completed
or (answer is not None and answer.completed)
or (
not collection.feedback_once_collection_complete
and answer is not None
and answer.completed
)
):
completed = True
question_completed = True
show_title = collection.show_title_post
show_history = collection.show_history_post
show_description = collection.show_description_post
@@ -2020,7 +2065,7 @@ def collection_case_view_take(
resources = case.caseresource_set.all()
else:
completed = False
question_completed = False
show_title = collection.show_title_pre
show_history = collection.show_history_pre
show_description = collection.show_description_pre
@@ -2051,7 +2096,7 @@ def collection_case_view_take(
"show_report": show_report,
"resources": resources,
"cid_user_exam": cid_user_exam,
"completed": completed,
"question_completed": question_completed,
"self_review": self_review,
},
)
@@ -2525,6 +2570,7 @@ class CaseAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
context["collection"] = context["object"]
return context
class SeriesAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
model = Series
form_class = SeriesAuthorForm
@@ -2535,9 +2581,48 @@ class SeriesAuthorUpdate(RevisionMixin, AuthorRequiredMixin, UpdateView):
context["collection"] = context["object"]
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?'}}}),
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(
@@ -2548,14 +2633,17 @@ def question_schemas_preset(request):
},
)
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")
case_details = (
CaseDetail.objects.filter(case__in=questions, collection=collection)
.prefetch_related("case")
.order_by("sort_order")
)
return render(
request,
@@ -2564,4 +2652,4 @@ def collection_question_schemas(request, exam_id: int):
"collection": collection,
"case_details": case_details,
},
)
)
+2 -1
View File
@@ -47,5 +47,6 @@ django-sortedm2m # remove once migrations squashed
loguru
pylibjpeg
pylibjpeg-libjpeg
django-jsonforms
#django-jsonforms
git+https://github.com/xkjq/django-jsonforms.git@bump-json-editor-lib
django_svelte_jsoneditor