add question schema model and integrate most urls/views

This commit is contained in:
Ross
2024-06-24 11:43:33 +01:00
parent 71fefe6c4f
commit cef06e4682
12 changed files with 232 additions and 25 deletions
+6
View File
@@ -7,6 +7,7 @@ from .models import (
Finding,
PathologicalProcess,
Presentation,
QuestionSchema,
Series,
Structure,
Subspecialty,
@@ -282,3 +283,8 @@ class SubspecialtyFilter(django_filters.FilterSet):
data=data, queryset=queryset, prefix=prefix, request=request
)
pass
class QuestionSchemaFilter(django_filters.FilterSet):
class Meta:
model = QuestionSchema
fields = ("name", "description")
+15 -3
View File
@@ -27,6 +27,7 @@ from atlas.models import (
CidReportAnswer,
Differential,
Finding,
QuestionSchema,
Resource,
SelfReview,
Series,
@@ -528,10 +529,11 @@ class JsonAnswerForm(Form):
class Meta:
fields = ["json_answer"]
def __init__(self, *args, case_detail, **kwargs):
def __init__(self, *args, question_schema=None, **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='schema/options.json')
if question_schema is not None:
self.fields["json_answer"] = JSONSchemaField(schema=question_schema, options='schema/options.json')
self.fields["json_answer"].label = False
class BaseReportAnswerForm(ModelForm):
class Meta:
@@ -769,3 +771,13 @@ class CaseDetailForm(ModelForm):
"question_schema": SvelteJSONEditorWidgetOverride(),
"question_answers": SvelteJSONEditorWidgetOverride(),
}
class QuestionSchemaForm(ModelForm):
class Meta:
model = QuestionSchema
fields = ["name", "description", "schema"]
widgets = {
"schema": SvelteJSONEditorWidgetOverride(),
}
+24
View File
@@ -0,0 +1,24 @@
# Generated by Django 5.0.2 on 2024-06-24 09:27
import generic.mixins
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('atlas', '0054_casecollection_feedback_once_collection_complete_and_more'),
]
operations = [
migrations.CreateModel(
name='QuestionSchema',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, null=True)),
('schema', models.JSONField()),
],
bases=(models.Model, generic.mixins.AuthorMixin),
),
]
+23
View File
@@ -74,6 +74,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
from loguru import logger
image_storage = FileSystemStorage(
# Physical file location ROOT
location="{0}atlas/".format(settings.MEDIA_ROOT),
@@ -1272,3 +1273,25 @@ class CaseResource(models.Model):
def __str__(self) -> str:
return f"{self.resource} - {self.case} (Pre: {self.pre_review})"
class QuestionSchema(models.Model, AuthorMixin):
name = models.CharField(max_length=255)
description = models.TextField(blank=True, null=True)
schema = models.JSONField()
def get_absolute_url(self):
return reverse("atlas:question_schema_detail", kwargs={"pk": self.pk})
def get_example_form(self):
from .forms import JsonAnswerForm
example_form = JsonAnswerForm(
question_schema=self.schema
)
return example_form
def get_schema_as_json(self) -> str:
return json.dumps(self.schema)
def __str__(self) -> str:
return "{}".format(self.name)
+22
View File
@@ -9,6 +9,7 @@ from .models import (
Condition,
PathologicalProcess,
Presentation,
QuestionSchema,
Series,
Structure,
Finding,
@@ -338,3 +339,24 @@ class CaseCollectionTable(tables.Table):
*args,
**kwargs,
)
class QuestionSchemaTable(tables.Table):
name = tables.Column(
linkify=("atlas:question_schema_detail", {"pk": tables.A("pk")}),
verbose_name="QuestionSchema",
)
edit = tables.LinkColumn(
"atlas:question_schema_update", text="Edit", args=[A("pk")], orderable=False
)
delete = tables.LinkColumn(
"atlas:question_schema_delete", text="Delete", args=[A("pk")], orderable=False
)
class Meta:
model = QuestionSchema
template_name = "django_tables2/bootstrap4.html"
fields = ("name", "description", "schema")
def render_schema(self, value, record):
return format_html("<details><summary>Schema</summary>{}</details>", value)
+1
View File
@@ -16,6 +16,7 @@
<a href="{% url 'atlas:series_create' %}" title="Create a new image series">Create Series</a> /
<a href="{% url 'atlas:resource_view' %}" title="Resources">Resources</a> /
<a href="{% url 'atlas:user_uploads' %}" title="View unimported uploads">Uploads</a> /
<a href="{% url 'atlas:question_schema_overview' %}" title="View and edit question schemas">Question Schemas</a> /
<a href="{% url 'atlas:help' %}" title="View the atlas help pages">Help</a>
{% endif %}
{% comment %} </br>
@@ -29,7 +29,7 @@
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-get="{% url 'atlas:question_schema_schemas' %}"
hx-target=".offcanvas-body"
>Preset questions</button>
+24
View File
@@ -0,0 +1,24 @@
{% extends 'atlas/base.html' %}
{% block content %}
<div>
<h3>Name: {{question_schema.name}}</h3>
<p>Description: {{question_schema.description}}</p>
<details><summary>JSON:</summary>{{question_schema.schema}}</details>
{{example_form}}
<a href='{% url "atlas:question_schema_update" pk=question_schema.pk %}'><button>Edit</button></a>
<a href='{% url "atlas:question_schema_delete" pk=question_schema.pk %}'><button>Delete</button></a>
</div>
{% endblock %}
{% block js %}
{{example_form.media}}
{% endblock %}
@@ -3,12 +3,12 @@
{% 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 %}
{% for question_schema in question_schemas %}
<li class="schemas">
<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><summary>{{question_schema.name}}</summary>
<div class="schema" data-schema='{{question_schema.get_schema_as_json|safe}}'>{{question_schema.schema}}</div>
</details>
<div class="form"></div>
+24
View File
@@ -0,0 +1,24 @@
{% extends "atlas/base.html" %}
<!-- {% load static from static %} -->
{% block css %}
{% endblock %}
{% block js %}
<!--<script type="text/javascript" src="/admin/jsi18n/"></script>-->
{{form.media}}
<script type="text/javascript">
</script>
<!-- {{ form.media }} -->
{% endblock %}
{% block content %}
<h2>Add/Edit Condition</h2>
<p>Use this form to create or edit a question schema.</p>
<form action="" method="post" enctype="multipart/form-data" id="condition-form">
{% csrf_token %}
{{ form.as_div }}
<input type="submit" class="submit-button" value="Submit" name="submit">
</form>
{% endblock %}
+11 -6
View File
@@ -121,11 +121,11 @@ urlpatterns = [
views.collection_question_schemas,
name="collection_question_schemas",
),
path(
"question_schemas_preset",
views.question_schemas_preset,
name="question_schemas_preset",
),
#path(
# "question_schemas_preset",
# views.question_schemas_preset,
# name="question_schemas_preset",
#),
path(
"collection/<int:exam_id>/cids/<int:cid>/delete_answers",
views.delete_collection_cid_answers,
@@ -218,7 +218,12 @@ urlpatterns = [
name="series_dicom_json",
),
path("uncategorised_dicoms/", views.uncategorised_dicoms, name="uncategorised_dicoms_view"),
path("question_schema/", views.QuestionSchemaView.as_view(), name="question_schema_overview"),
path("question_schema/<int:pk>", views.question_schema_detail, name="question_schema_detail"),
path("question_schema/<int:pk>/update", views.QuestionSchemaUpdate.as_view(), name="question_schema_update"),
path("question_schema/<int:pk>/delete", views.QuestionSchemaDelete.as_view(), name="question_schema_delete"),
path("question_schema/create", views.QuestionSchemaCreate.as_view(), name="question_schema_create"),
path("question_schema/schemas", views.question_schema_schemas, name="question_schema_schemas"),
path("condition/", views.ConditionView.as_view(), name="condition_view"),
path("categories/", views.categories_list, name="categories_list"),
path("condition/<int:pk>", views.condition_detail, name="condition_detail"),
+77 -11
View File
@@ -49,6 +49,7 @@ from .forms import (
ConditionForm,
FindingForm,
JsonAnswerForm,
QuestionSchemaForm,
ResourceForm,
SelfReviewForm,
SeriesAuthorForm,
@@ -70,6 +71,7 @@ from .models import (
Differential,
PathologicalProcess,
Presentation,
QuestionSchema,
Resource,
SelfReview,
Series,
@@ -89,6 +91,7 @@ from .tables import (
FindingTable,
PathologicalProcessTable,
PresentationTable,
QuestionSchemaTable,
SeriesTable,
StructureTable,
SubspecialtyTable,
@@ -100,6 +103,7 @@ from .filters import (
FindingFilter,
PathologicalProcessFilter,
PresentationFilter,
QuestionSchemaFilter,
SeriesFilter,
StructureFilter,
SubspecialtyFilter,
@@ -224,6 +228,33 @@ def series_detail(request, pk, finding_pk=None):
)
@login_required
# @user_is_atlas_editor
def question_schema_detail(request, pk: int):
question_schema = get_object_or_404(QuestionSchema, pk=pk)
example_form = question_schema.get_example_form()
# logging.debug(atlas.subspecialty.first().name.all())
return render(
request,
"atlas/question_schema_detail.html",
{"question_schema": question_schema, "example_form": example_form},
)
@login_required
# @user_is_atlas_editor
def question_schema_schemas(request):
question_schemas = QuestionSchema.objects.all()
return render(
request,
"atlas/question_schemas_preset.html",
{
"question_schemas": question_schemas,
},
)
@login_required
# @user_is_atlas_editor
def condition_detail(request, pk):
@@ -823,6 +854,18 @@ class ConditionCreate(RevisionMixin, LoginRequiredMixin, CreateView):
form_class = ConditionForm
class QuestionSchemaCreate(LoginRequiredMixin, CreateView):
model = QuestionSchema
form_class = QuestionSchemaForm
class QuestionSchemaUpdate(LoginRequiredMixin, UpdateView):
model = QuestionSchema
form_class = QuestionSchemaForm
class QuestionSchemaDelete(AuthorOrCheckerRequiredMixin, DeleteView):
model = QuestionSchema
template_name = "confirm_delete.html"
class FindingUpdate(RevisionMixin, LoginRequiredMixin, UpdateView):
model = Finding
form_class = FindingForm
@@ -1141,6 +1184,14 @@ class ConditionView(LoginRequiredMixin, SingleTableMixin, FilterView):
filterset_class = ConditionFilter
class QuestionSchemaView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = QuestionSchema
table_class = QuestionSchemaTable
template_name = "atlas/view.html"
filterset_class = QuestionSchemaFilter
class SubspecialtyView(LoginRequiredMixin, SingleTableMixin, FilterView):
model = Subspecialty
table_class = SubspecialtyTable
@@ -1484,8 +1535,17 @@ class CollectionView(LoginRequiredMixin, SingleTableMixin, FilterView):
def collection_viva(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
cases = collection.cases.all().order_by("casedetail__sort_order").prefetch_related("series", "series__images", "series__contrast", "series__plane", "series__examination")
cases = (
collection.cases.all()
.order_by("casedetail__sort_order")
.prefetch_related(
"series",
"series__images",
"series__contrast",
"series__plane",
"series__examination",
)
)
return render(
request,
@@ -1588,7 +1648,9 @@ def collection_case_details(request, exam_id, case_id):
# 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)
example_form = JsonAnswerForm(
request.POST, question_schema=case_detail.question_schema
)
form = CaseDetailForm(instance=case_detail)
# Called if the user saves the main form
elif request.POST.get("submit") == "save":
@@ -1596,7 +1658,7 @@ def collection_case_details(request, exam_id, case_id):
if form.is_valid():
form.save()
# Add any additional logic or redirection here
# example_form = JsonAnswerForm(request.POST, case_detail=case_detail)
# example_form = JsonAnswerForm(request.POST, question_schema=case_detail.question_schema)
post_data = request.POST.copy()
if (
case_detail.question_schema is not None
@@ -1618,7 +1680,9 @@ def collection_case_details(request, exam_id, case_id):
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, question_schema=case_detail.question_schema
)
form = CaseDetailForm(instance=case_detail)
# This shouldn't happen
@@ -1629,7 +1693,9 @@ 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)
example_form = JsonAnswerForm(post_data, case_detail=case_detail)
example_form = JsonAnswerForm(
post_data, question_schema=case_detail.question_schema
)
form = CaseDetailForm(instance=case_detail)
pass
@@ -1652,17 +1718,18 @@ def collection_case_details(request, exam_id, case_id):
# 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, question_schema=case_detail.question_schema)
# form = CaseDetailForm(instance=case_detail)
# blank_form = JsonAnswerForm(case_detail=case_detail)
case_number, case_count = collection.get_index_of_case(case_detail.case, case_count=True)
# blank_form = JsonAnswerForm(question_schema=case_detail.question_schema)
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",
@@ -1983,7 +2050,6 @@ def collection_case_view_take(
).first()
ReportAnswerForm = UserQuestionAnswerForm
if request.method == "POST":
if collection.collection_type in ("REP", "QUE"):
if not collection.publish_results: