From 93f0bb89272245cd30d7b472b049285b9b043dab Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 20 Oct 2025 10:43:12 +0100 Subject: [PATCH] Add LLM question import functionality and enhance question model with new fields --- sbas/forms.py | 21 ++ ...ion_condition_question_finding_and_more.py | 39 +++ sbas/migrations/0019_question_title.py | 18 ++ sbas/models.py | 9 + sbas/templates/sbas/base.html | 7 + sbas/templates/sbas/import_llm_questions.html | 11 + sbas/templates/sbas/llm_prompt_view.html | 133 ++++++++++ sbas/templates/sbas/question_detail.html | 70 ++++- sbas/urls.py | 12 + sbas/views.py | 248 ++++++++++++++++++ 10 files changed, 565 insertions(+), 3 deletions(-) create mode 100644 sbas/migrations/0018_question_condition_question_finding_and_more.py create mode 100644 sbas/migrations/0019_question_title.py create mode 100644 sbas/templates/sbas/import_llm_questions.html create mode 100644 sbas/templates/sbas/llm_prompt_view.html diff --git a/sbas/forms.py b/sbas/forms.py index fd4e5752..1101a18a 100755 --- a/sbas/forms.py +++ b/sbas/forms.py @@ -3,6 +3,7 @@ from django.forms import ( ModelForm, ModelMultipleChoiceField, ModelChoiceField, + CheckboxSelectMultiple, ChoiceField, CharField, ) @@ -22,6 +23,8 @@ from django.forms.widgets import RadioSelect, TextInput, Textarea from tinymce.widgets import TinyMCE +from dal import autocomplete + class UserAnswerForm(ModelForm): class Meta: @@ -140,6 +143,11 @@ class QuestionForm(ModelForm): "e_answer", "e_feedback", "best_answer", + "finding", + "structure", + "condition", + "presentation", + "subspecialty", ] widgets = { @@ -158,6 +166,19 @@ class QuestionForm(ModelForm): "c_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}), "d_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}), "e_feedback" : TinyMCE(attrs={'cols': 80, 'rows': 4}, mce_attrs={'height': 140}), + "structure": autocomplete.ModelSelect2Multiple( + url="atlas:structure-autocomplete" + ), + "finding": autocomplete.ModelSelect2Multiple( + url="atlas:finding-autocomplete" + ), + "condition": autocomplete.ModelSelect2Multiple( + url="atlas:condition-autocomplete" + ), + "presentation": autocomplete.ModelSelect2Multiple( + url="atlas:presentation-autocomplete" + ), + "subspecialty": CheckboxSelectMultiple(), } #widgets = { diff --git a/sbas/migrations/0018_question_condition_question_finding_and_more.py b/sbas/migrations/0018_question_condition_question_finding_and_more.py new file mode 100644 index 00000000..044b9bae --- /dev/null +++ b/sbas/migrations/0018_question_condition_question_finding_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 5.1.4 on 2025-10-20 08:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('atlas', '0079_casecollection_prerequisites'), + ('sbas', '0017_exam_results_supervisor_visible'), + ] + + operations = [ + migrations.AddField( + model_name='question', + name='condition', + field=models.ManyToManyField(blank=True, related_name='sbas_questions', to='atlas.condition'), + ), + migrations.AddField( + model_name='question', + name='finding', + field=models.ManyToManyField(blank=True, related_name='sbas_questions', to='atlas.finding'), + ), + migrations.AddField( + model_name='question', + name='presentation', + field=models.ManyToManyField(blank=True, related_name='sbas_questions', to='atlas.presentation'), + ), + migrations.AddField( + model_name='question', + name='structure', + field=models.ManyToManyField(blank=True, related_name='sbas_questions', to='atlas.structure'), + ), + migrations.AddField( + model_name='question', + name='subspecialty', + field=models.ManyToManyField(blank=True, related_name='sbas_questions', to='atlas.subspecialty'), + ), + ] diff --git a/sbas/migrations/0019_question_title.py b/sbas/migrations/0019_question_title.py new file mode 100644 index 00000000..2108c61f --- /dev/null +++ b/sbas/migrations/0019_question_title.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-10-20 09:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sbas', '0018_question_condition_question_finding_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='question', + name='title', + field=models.CharField(blank=True, help_text='Short title for question', max_length=200, null=True), + ), + ] diff --git a/sbas/models.py b/sbas/models.py index d60638b2..430bb372 100644 --- a/sbas/models.py +++ b/sbas/models.py @@ -15,6 +15,8 @@ import reversion from django.contrib.contenttypes.fields import GenericRelation +from atlas.models import Finding, Structure, Condition, Subspecialty, Presentation + class Category(models.Model): category = models.CharField(max_length=200) @@ -24,6 +26,7 @@ class Category(models.Model): class Question(QuestionBase): + title = models.CharField(max_length=200, help_text="Short title for question", blank=True, null=True) stem = models.TextField( blank=False, help_text="Stem of the question", @@ -91,6 +94,12 @@ class Question(QuestionBase): Category, on_delete=models.SET_NULL, null=True, blank=True ) + finding = models.ManyToManyField(Finding, blank=True, related_name="sbas_questions") + structure = models.ManyToManyField(Structure, blank=True, related_name="sbas_questions") + condition = models.ManyToManyField(Condition, blank=True, related_name="sbas_questions") + presentation = models.ManyToManyField(Presentation, blank=True, related_name="sbas_questions") + subspecialty = models.ManyToManyField(Subspecialty, blank=True, related_name="sbas_questions") + #notes = GenericRelation(QuestionNote) def __str__(self): diff --git a/sbas/templates/sbas/base.html b/sbas/templates/sbas/base.html index 1b601c53..2148a89d 100644 --- a/sbas/templates/sbas/base.html +++ b/sbas/templates/sbas/base.html @@ -27,6 +27,13 @@
  • Question
  • + {% if request.user.is_superuser %}