first version
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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".'
|
||||
)
|
||||
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 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"
|
||||
Reference in New Issue
Block a user