add question schema model and integrate most urls/views
This commit is contained in:
@@ -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
@@ -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:
|
||||
@@ -768,4 +770,14 @@ class CaseDetailForm(ModelForm):
|
||||
widgets = {
|
||||
"question_schema": SvelteJSONEditorWidgetOverride(),
|
||||
"question_answers": SvelteJSONEditorWidgetOverride(),
|
||||
}
|
||||
|
||||
|
||||
class QuestionSchemaForm(ModelForm):
|
||||
class Meta:
|
||||
model = QuestionSchema
|
||||
fields = ["name", "description", "schema"]
|
||||
|
||||
widgets = {
|
||||
"schema": SvelteJSONEditorWidgetOverride(),
|
||||
}
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
+23
-1
@@ -9,6 +9,7 @@ from .models import (
|
||||
Condition,
|
||||
PathologicalProcess,
|
||||
Presentation,
|
||||
QuestionSchema,
|
||||
Series,
|
||||
Structure,
|
||||
Finding,
|
||||
@@ -337,4 +338,25 @@ 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)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user