138 lines
4.2 KiB
Python
138 lines
4.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 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"
|