Ed.
Ed.

Reputation: 4597

Proper construction of Django Model

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

Answers (2)

Burhan Khalid
Burhan Khalid

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

Chris Pratt
Chris Pratt

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

Related Questions