Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - Update model field based on another field

I am new to Django and Python and I want to do something I used to do very often in Java EE.

Consider the following model (only relevant classes):

class Item(models.Model):
    name = models.CharField(max_length=40)
    default_price = models.DecimalField(max_digits=6, decimal_places=2, default=50)

    def __unicode__(self):
        return self.name


class SaleDetail(models.Model):
    item = models.ForeignKey(Item)
    deposit = models.ForeignKey(Deposit)
    quantity = models.PositiveIntegerField()
    unit_price = models.DecimalField(max_digits=6, decimal_places=2)
    sale = models.ForeignKey(Sale)

    def item_updated(self, value):
        if self.unit_price == None:
            self.unit_price = value

What I want to do is that each time an Item is added to SaleDetail, update SaleDetail.unit_price with Item.default_price if SaleDetail is new or no unit_price is set.

What I used to do in Java POJOs was to include this logic in the setter method. I've tried using python properties to encapsulate the item property, but Django updates the field directly under the hood so this would break some automatic functionallity.

I've also tried to subclassing ForeignKey to accept a callback function but I couldn't find a way to call a method on the container class.

I want to do this so I can provide a default for the UI but I don't want to include this logic in the view logic, since conceptually I think this logic should be on the model (Server side)

Other use in this case would be to update totals for each sale detail and sale. I would like to calculate this BEFORE the user decides to save the sale, so save signals would not work.

Thanks!

like image 303
Aníbal Rivero Avatar asked Oct 29 '14 14:10

Aníbal Rivero


2 Answers

The "unit price" is a literally a function of two different fields. As such I would tend to write it like this:

class Item(models.Model):
    name = models.CharField(max_length=40)
    default_price = models.DecimalField(max_digits=6, decimal_places=2, default=50)

    def __unicode__(self):
        return self.name

class SaleDetail(models.Model):
    item = models.ForeignKey(Item)
    deposit = models.ForeignKey(Deposit)
    quantity = models.PositiveIntegerField()
    entered_unit_price = models.DecimalField(max_digits=6, decimal_places=2, default=None)
    sale = models.ForeignKey(Sale)

    @property
    def unit_price(self, value):
        if self.entered_unit_price is None:
            return self.item.default_price
        else:
            return self.entered_unit_price

    @unit_price.setter
    def unit_price(self, value):
        self.entered_unit_price = value

Then use it like so:

print(sd.unit_price)
sd.unit_price = 500 
sd.save()
like image 110
MadMan2064 Avatar answered Oct 04 '22 15:10

MadMan2064


See if this works for you. We override the save method and check if there is a pk. If the pk is None, we know that the SaleDetail is new. Then, we see if there is a unit_price. If there is not a unit_price, we set it to the Item.default_price.

class Item(models.Model):
    name = models.CharField(max_length=40)
    default_price = models.DecimalField(max_digits=6, decimal_places=2, default=50)

    def __unicode__(self):
        return self.name


class SaleDetail(models.Model):
    item = models.ForeignKey(Item)
    deposit = models.ForeignKey(Deposit)
    quantity = models.PositiveIntegerField()
    unit_price = models.DecimalField(max_digits=6, decimal_places=2)
    sale = models.ForeignKey(Sale)

    def save(self, *args, **kwargs):
        # Make sure this is the first save (pk should be None) and there is no unit_price set
        if self.pk is None and not self.unit_price:
            self.unit_price = self.item.default_price
        elif not self.unit_price:
            self.unit_price = self.item.default_price

        # Call the original save method
        super(SaleDetail, self).save(*args, **kwargs)
like image 24
Michael B Avatar answered Oct 04 '22 15:10

Michael B