Reputation: 4597
I'm creating a tracking application for exercise and I'm wondering about the most efficient way to create the models. Most exercises I do have repetitions, sets, and weights. But then there are runs which have distances and times. At first, I was going to create two different models to capture each, but then I thought it might be better to combine then. Now, I'm not sure.
Ok, below is my first pass:
LEVELS = (
(1, '1 - Hardly'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, '5 - Average'),
(6, '6'),
(7, '7'),
(8, '8'),
(9, '9'),
(10, '10 - Very'),
class Jog(models.Model):
distance = models.DecimalField("Distance (Miles)", max_digits=4, decimal_places=2)
time = models.DecimalField("Time (Minutes)", max_digits=4, decimal_places=2)
intensity = models.IntegerField("Intensity", choices = LEVELS, default = 5)
date = models.DateTimeField("Date", blank=True, default=datetime.now)
notes = models.TextField("Notes", blank=True)
def __str__(self):
return "%s Miles in %s Minutes (Intensity of %s)" % (self.distance, self.time, self.intensity)
class Meta:
verbose_name = "Jog"
class Exercise_Type(models.Model):
name = models.CharField("Exercise Name", max_length=200, unique = True)
slug = models.SlugField(max_length=100, blank=True)
notes = models.TextField("Notes", blank=True)
def __str__(self):
return self.name
class Workout(models.Model):
exercise_type = models.ForeignKey(Exercise_Type, verbose_name="Exercise Type")
reps = models.IntegerField("Repetitions")
sets = models.DecimalField("Sets", max_digits=2, decimal_places=1)
weight = models.IntegerField("Weight", blank=True, null=True)
intensity = models.IntegerField("Intensity", choices = LEVELS, default = 5)
date = models.DateTimeField("Date", blank=True, default=datetime.now)
notes = models.TextField("Notes", blank=True)
This seemed silly though because a jog is a type of workout and is only getting split out because it has different measurement characteristics. So then I thought, what if I do something like this. Define the necessary fields in the workout type and then enable/suppress them by asking the user the Exercise Type:
class Exercise_Type(models.Model):
name = models.CharField("Exercise Name", max_length=200, unique = True)
slug = models.SlugField(max_length=100, blank=True)
notes = models.TextField("Notes", blank=True)
distance = models.BooleanField("Use Distance Field?", default = False)
time = models.BooleanField("Use Time Field?", default = False)
reps = models.BooleanField("Use Reps Field", default = False)
sets = models.BooleanField("Use Sets Field?", default = False)
weight = models.BooleanField("Use Weight Field?", default = False)
def __str__(self):
return self.name
class Workout(models.Model):
exercise_type = models.ForeignKey(Exercise_Type, verbose_name="Exercise Type")
distance = models.DecimalField("Distance (Miles)", max_digits=4, decimal_places=2, blank = True, null=True)
time = models.DecimalField("Time (Minutes)", max_digits=4, decimal_places=2, blank = True, null=True)
reps = models.IntegerField("Repetitions", blank = True, null=True)
sets = models.DecimalField("Sets", max_digits=2, decimal_places=1, blank = True, null=True)
weight = models.IntegerField("Weight", blank=True, null=True)
intensity = models.IntegerField("Intensity", choices = LEVELS, default = 5)
date = models.DateTimeField("Date", blank=True, default=datetime.now)
notes = models.TextField("Notes", blank=True)
This seems like a waste of resources because every exercise will technically have every field regardless of whether it needs it or not.
Then I thought, what about sub-classing? That's when I gave up and decided to appeal to those more knowledgeable than myself.
What's the best way to organize this model?
Upvotes: 1
Views: 322
Reputation: 174758
Since each exercise has one or more properties that you want to measure, you should extract those properties out, so you end up with three main models.
class Metric(models.Model):
name = models.CharField(max_length=20)
description = models.TextField(null=True, blank=True)
measured_in = models.CharField(max_length=20, null=True, blank=True)
# other fields
class Measurement(models.Model):
metric = models.ForeignKey(Metric)
value = models.CharField(max_length=20, null=True, blank=True)
workout_date = models.DateField(auto_now=True)
class Workout(models.Model):
# common attributes for each workout
name = models.CharField(max_length=200)
notes = models.TextField()
metrics = models.ManyToManyField(Measurement)
You add various metrics (things you measure) to Metric
. For each workout, you identify which metric you want to track, by adding creating a new Measurement
. Finally, you associate it to each workout when you create it.
Here is a sample:
reps = Metric.objects.create(name='Reps')
my_reps = Measurement.objects.create(metric=reps,value=10)
w = Workout(name="Weights")
w.metrics.add(my_reps)
w.save()
Upvotes: 1
Reputation: 239470
This is exactly what inheritance is made for. Create a generic Workout
type, and then subclass it with the types of workouts. With the unique/specific attributes on the subclasses.
class Workout(models.Model):
date = models.DateTimeField("Date", blank=True, default=datetime.now)
notes = models.TextField("Notes", blank=True)
class Jog(Workout):
distance = models.DecimalField("Distance (Miles)", max_digits=4, decimal_places=2, blank = True, null=True)
time = models.DecimalField("Time (Minutes)", max_digits=4, decimal_places=2, blank = True, null=True)
class Weightlifting(Workout):
reps = models.IntegerField("Repetitions", blank = True, null=True)
sets = models.DecimalField("Sets", max_digits=2, decimal_places=1, blank = True, null=True)
weight = models.IntegerField("Weight", blank=True, null=True)
And so on. If you don't need to use the generic Workout
type anywhere, you can make it an abstract model.
Upvotes: 1