Add primary_answer field to AnatomyQuestion model and create migration to backfill existing values
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
"""Add primary_answer FK to AnatomyQuestion and backfill existing values.
|
||||||
|
|
||||||
|
This migration adds a nullable FK `primary_answer` on AnatomyQuestion
|
||||||
|
pointing to Answer, then backfills it using the current logic of
|
||||||
|
selecting the first non-proposed CORRECT answer (status '2').
|
||||||
|
"""
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
def forwards(apps, schema_editor):
|
||||||
|
AnatomyQuestion = apps.get_model('anatomy', 'AnatomyQuestion')
|
||||||
|
Answer = apps.get_model('anatomy', 'Answer')
|
||||||
|
|
||||||
|
# Select first non-proposed CORRECT answer (status '2') per question
|
||||||
|
for q in AnatomyQuestion.objects.all():
|
||||||
|
ans = Answer.objects.filter(question_id=q.pk, proposed=False, status='2').first()
|
||||||
|
if ans:
|
||||||
|
q.primary_answer_id = ans.pk
|
||||||
|
q.save(update_fields=['primary_answer'])
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(apps, schema_editor):
|
||||||
|
AnatomyQuestion = apps.get_model('anatomy', 'AnatomyQuestion')
|
||||||
|
for q in AnatomyQuestion.objects.all():
|
||||||
|
q.primary_answer = None
|
||||||
|
q.save(update_fields=['primary_answer'])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0025_exam_created_exam_updated'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='anatomyquestion',
|
||||||
|
name='primary_answer',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_for_questions', to='anatomy.answer'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(forwards, backwards),
|
||||||
|
]
|
||||||
+32
-24
@@ -127,6 +127,15 @@ class AnatomyQuestion(QuestionBase):
|
|||||||
related_name="anatomy_authored_questions",
|
related_name="anatomy_authored_questions",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
primary_answer = models.ForeignKey(
|
||||||
|
'Answer',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='primary_for_questions',
|
||||||
|
help_text="Optional explicit primary answer for this question",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = ()
|
permissions = ()
|
||||||
|
|
||||||
@@ -151,33 +160,32 @@ class AnatomyQuestion(QuestionBase):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
|
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
def get_primary_answer(self):
|
def get_primary_answer_obj(self):
|
||||||
if hasattr(self, "prefetched_primary_answer") and self.prefetched_primary_answer:
|
"""Return the Answer instance considered the primary answer, or None.
|
||||||
ans = self.prefetched_primary_answer[0]
|
|
||||||
else:
|
|
||||||
ans = self.answers.filter(
|
|
||||||
proposed=False, status=Answer.MarkOptions.CORRECT
|
|
||||||
).first()
|
|
||||||
# ans = self.answers.first()
|
|
||||||
|
|
||||||
|
Preference order:
|
||||||
|
- explicit `primary_answer` FK if set
|
||||||
|
- `prefetched_primary_answer` if provided by a queryset
|
||||||
|
- first non-proposed Answer with status CORRECT
|
||||||
|
"""
|
||||||
|
if getattr(self, "primary_answer", None):
|
||||||
|
return self.primary_answer
|
||||||
|
|
||||||
|
if hasattr(self, "prefetched_primary_answer") and self.prefetched_primary_answer:
|
||||||
|
return self.prefetched_primary_answer[0]
|
||||||
|
|
||||||
|
return self.answers.filter(proposed=False, status=Answer.MarkOptions.CORRECT).first()
|
||||||
|
|
||||||
|
def get_primary_answer(self):
|
||||||
|
"""Return the primary answer text (string) for templates and display.
|
||||||
|
|
||||||
|
Keeps backward compatibility by returning the answer string when
|
||||||
|
available, or "None yet..." if no primary answer is defined.
|
||||||
|
"""
|
||||||
|
ans = self.get_primary_answer_obj()
|
||||||
if ans is None:
|
if ans is None:
|
||||||
return "None yet..."
|
return "None yet..."
|
||||||
else:
|
return ans.answer
|
||||||
return ans.answer
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.answers.filter(
|
|
||||||
proposed=False, status=Answer.MarkOptions.CORRECT
|
|
||||||
).count()
|
|
||||||
> 0
|
|
||||||
):
|
|
||||||
return (
|
|
||||||
self.answers.filter(proposed=False, status=Answer.MarkOptions.CORRECT)
|
|
||||||
.first()
|
|
||||||
.answer
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return "None yet..."
|
|
||||||
|
|
||||||
def get_exams(self):
|
def get_exams(self):
|
||||||
e = self.exams.all().values_list("name", flat=True)
|
e = self.exams.all().values_list("name", flat=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user