Home > OS >  How to assign default value of the model based on the value of ForeignKey
How to assign default value of the model based on the value of ForeignKey

Time:01-07

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?

CodePudding user response:

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']

CodePudding user response:

Two ways come to mind: overriding the model's save method or by using a pre_save signal. I would try the first one and if it doesn't work then the second one. The reason is that signals are notoriously difficult to debug so if you have alternatives you should always leave them as a last resort.

Ok so, I think this should work:

def save(self, *args, **kwargs):
        self.preferred_units = self.owner.units
        super(CustomExercise, self).save(*args, **kwargs

Otherwise:

@receiver(pre_save, sender=CustomExercise)
def assign_unit(sender, instance, **kwargs):
    instance.preferred_units = instance.owner.units

The convention is to store your signals in signals.py in your app. Make sure to "activate" them from apps.py or they won't work. Here the docs.

  • Related