osiris/tracker/models.py

176 lines
5.2 KiB
Python

from django.db import models
from django.utils import timezone
class TimeOfDay(models.IntegerChoices):
"""Integer-backed so that ordering by this field is chronological."""
MORNING = 1, "Morning"
NOON = 2, "Noon"
EVENING = 3, "Evening"
NIGHT = 4, "Night"
class Medication(models.Model):
"""A drug Osiris is on, e.g. "Vetmedin"."""
name = models.CharField(max_length=100, unique=True)
is_active = models.BooleanField(
default=True, help_text="Uncheck when the treatment ends, instead of deleting."
)
notes = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["name"]
def __str__(self):
return self.name
class ScheduledDose(models.Model):
"""One recurring dose of a medication, e.g. "Vetmedin, 1/4, every morning".
A medication taken morning and evening has two of these.
"""
medication = models.ForeignKey(
Medication, on_delete=models.CASCADE, related_name="scheduled_doses"
)
time_of_day = models.IntegerField(choices=TimeOfDay.choices)
amount = models.CharField(
max_length=20, default="1/4", help_text='Free text, e.g. "1/4", "1", "5 mg".'
)
reminder_time = models.TimeField(
null=True,
blank=True,
help_text="Send a push reminder at this time if the dose isn't ticked off. "
"Leave empty for no reminder.",
)
is_active = models.BooleanField(default=True)
class Meta:
ordering = ["medication__name", "time_of_day"]
constraints = [
models.UniqueConstraint(
fields=["medication", "time_of_day"],
name="unique_medication_time_of_day",
)
]
def __str__(self):
return f"{self.medication.name}{self.amount} ({self.get_time_of_day_display().lower()})"
class DoseLog(models.Model):
"""Whether a given scheduled dose was actually given on a given day.
A missing row means "not recorded"; a row with taken=False means "explicitly
marked as not given", so unchecking a box is distinguishable from never
having touched it.
"""
scheduled_dose = models.ForeignKey(
ScheduledDose, on_delete=models.CASCADE, related_name="logs"
)
date = models.DateField(default=timezone.localdate)
taken = models.BooleanField(default=True)
taken_at = models.DateTimeField(null=True, blank=True)
notes = models.TextField(blank=True)
class Meta:
ordering = ["-date", "scheduled_dose__time_of_day"]
constraints = [
models.UniqueConstraint(
fields=["scheduled_dose", "date"], name="unique_dose_per_day"
)
]
def __str__(self):
state = "given" if self.taken else "missed"
return f"{self.date}{self.scheduled_dose}{state}"
def save(self, *args, **kwargs):
if self.taken and self.taken_at is None:
self.taken_at = timezone.now()
if not self.taken:
self.taken_at = None
return super().save(*args, **kwargs)
class ReminderSent(models.Model):
"""One row per dose per day once its reminder has gone out.
This is what stops a reminder being re-sent every time the cron job ticks.
"""
scheduled_dose = models.ForeignKey(
ScheduledDose, on_delete=models.CASCADE, related_name="reminders"
)
date = models.DateField()
sent_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-sent_at"]
constraints = [
models.UniqueConstraint(
fields=["scheduled_dose", "date"], name="unique_reminder_per_day"
)
]
def __str__(self):
return f"{self.date}{self.scheduled_dose}"
class DailyNote(models.Model):
"""Free-form journal entry for one day: appetite, behaviour, how Osiris is doing."""
date = models.DateField(default=timezone.localdate, unique=True)
body = models.TextField(blank=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-date"]
def __str__(self):
return f"{self.date}{self.body[:40]}"
class GlobalNote(models.Model):
"""The single shared note: standing instructions and things to do.
There is exactly one row, shared by everyone; the constraint pins its pk
so a second one cannot be created even by accident.
"""
body = models.TextField(blank=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
constraints = [
models.CheckConstraint(condition=models.Q(id=1), name="single_global_note")
]
def __str__(self):
return "Global note"
@classmethod
def load(cls) -> "GlobalNote":
note, _ = cls.objects.get_or_create(pk=1)
return note
class PulseReading(models.Model):
"""One heart-rate measurement per day, in beats per minute."""
date = models.DateField(default=timezone.localdate, unique=True)
bpm = models.PositiveSmallIntegerField(help_text="Beats per minute.")
notes = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-date"]
def __str__(self):
return f"{self.date}{self.bpm} bpm"