Add NormalCase model, views, filters, and templates for managing normal cases

This commit is contained in:
Ross
2025-11-13 21:44:55 +00:00
parent 726c7ea401
commit 345977e2ef
8 changed files with 231 additions and 0 deletions
+77
View File
@@ -611,6 +611,83 @@ class Case(models.Model, AuthorMixin, QuestionMixin):
return json.dumps(results)
class NormalCase(models.Model):
"""Marks a Case as a 'normal' example used in the Atlas normals section.
We store a nearest-year age (age_years) and optional primary
examination/modality to make filtering simple and fast. A OneToOne
relation keeps normal-specific fields separate from the main Case
object and is non-destructive.
"""
case = models.OneToOneField(
Case,
on_delete=models.CASCADE,
related_name="normal_case",
help_text="The Case that is marked as a normal example.",
)
age_years = models.PositiveSmallIntegerField(
null=True,
blank=True,
validators=[MinValueValidator(0), MaxValueValidator(150)],
help_text="Age of the patient at scan in years (rounded to nearest year).",
)
examination = models.ForeignKey(
Examination,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="normal_cases",
help_text="Primary examination for this normal case (optional).",
)
modality = models.ForeignKey(
Modality,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="normal_cases",
help_text="Primary modality for this normal case (optional).",
)
added_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="added_normals",
)
added_date = models.DateTimeField(default=timezone.now)
notes = models.TextField(null=True, blank=True)
class Meta:
ordering = ["-added_date"]
def __str__(self):
return f"Normal: {self.case} ({self.age_years or 'unknown'}y)"
def get_absolute_url(self):
return self.case.get_absolute_url()
def save(self, *args, **kwargs):
# If examination/modality not specified, try to infer from the first series
try:
if (self.examination is None or self.modality is None) and self.case.series.exists():
first_series = self.case.series.first()
if self.examination is None and getattr(first_series, 'examination', None) is not None:
self.examination = first_series.examination
if self.modality is None and getattr(first_series, 'modality', None) is not None:
self.modality = first_series.modality
except Exception:
# Be defensive: if anything goes wrong whilst inferring, continue and save as-is
pass
super().save(*args, **kwargs)
def extract_image_dicom_json_from_ds(ds, url, image_index):
to_keep = [
"Columns",