Search code examples
pythondjangodjango-modelsdesign-patterns

How to assign default value of the model based on the value of ForeignKey


I have the Account model were I store information about preferred units. However I also want to allow user to change the units for particular exercise which by default should be Account.units.

Here are my models:

class Account(models.Model):
    """Model to store user's data and preferences."""
    UNIT_CHOICES = [
        ('metric', 'Metric'),
        ('imperial', 'Imperial')
    ]

    uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
    created = models.DateTimeField(auto_now_add=True)

    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=False)
    units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=UNIT_CHOICES[0], null=False, blank=False)
    weight_metric = models.FloatField(null=True, blank=True)
    height_metric = models.FloatField(null=True, blank=True)
    weight_imperial = models.FloatField(null=True, blank=True)
    height_imperial = models.FloatField(null=True, blank=True)

    def __str__(self):
        return self.owner.email
class CustomExercise(models.Model):
    UNIT_CHOICES = [
        ('metric', 'Metric'),
        ('imperial', 'Imperial')
    ]

    uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
    created = models.DateTimeField(auto_now_add=True)

    owner = models.ForeignKey(Account, on_delete=models.CASCADE, null=False, blank=False)
    preferred_units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=owner.units, null=False, blank=False) # <- throws an error that "ForeignKey doesn't have units attribute."
    name = models.CharField(max_length=255, null=False, blank=False)

    measure_time = models.BooleanField(default=False)
    measure_distance = models.BooleanField(default=False)
    measure_weight = models.BooleanField(default=False)
    measure_reps = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.owner}:{self.name}'

As posted in code sample I tried to get that default value from ForeignKey, which not unexpectedly did not work out.

So my question is: what is the correct solution to implement this kind of feature?


Solution

  • I would not recommend storing duplicate values accross multiple models. You can easily access that value through a property method:

    class CustomExercise(models.Model):
       ... # remove preferred_units field from model
       @property
       def preferred_units(self):
          return self.owner.unit
    

    Although you can not use it in queryset directly, still you can annotate the 'owner__unit' field in queryset or filter by it:

    q = CustomExcercise.objects.annotate(preferred_units=F('owner__unit')).filter(preferred_units = 'kg')
    
    q.values()
    

    Displaying the value in Adminsite:

    class CustomExerciseAdmin(admin.ModelAdmin):
        fields = (..., 'preferred_units')
        readonly_fields = ['preferred_units']