1👍
There are a few considerations to make related to performance and getting the exact behavior you want. A few questions you might ask are:
- How often do you save a purchase order and how often do you retrieve it?
- Does it affect the total amount if the price of an item related to an existing purchase order changes?
- Are people allowed to edit their purchase orders by adding/deleting items?
If this is your full implementation for purchase orders, then I guess you’ll create and save the PurchaseOrder
object just once, and then retrieve it several times. I would think that you don’t want the total to change when the price of a related product changes, but this is just a guess. If you do want it to change, you’ll have to implement logic similar to what I’m about to suggest to do if you allow people to edit their orders, but you’ll have to implement it on the Product
model itself.
Now there are two distinct cases:
You don’t allow people to change their orders.
This is a simple case, as you need to retrieve the dollar_amount
just once and then save it. I would suggest using a private field for the actual amount and having a property to access the private attribute and set it if it’s not set yet. Normally you would override the save
method to add the data, but in this case you depend on a many-to-many relation to exist, so the model has to be saved at least once before you can get the total price:
from decimal import Decimal
class PurchaseOrder(models.Model):
# Always use a `DecimalField` when you're dealing with currency!!! `FloatField` and the underlying `float` datatype has it's rounding issues, and currency calculations have to be exact.
_dollar_amount = models.DecimalField(verbose_name='Total price')
@property
def dollar_amount(self):
if self._dollar_amount is None:
if self.pk is None:
# Has never been saved, so we can't do anything to calculate the price here.
return Decimal(0.0)
self._dollar_amount = sum(p.price_for_each_item for p in self.product.all())
self.save(update_fields=['_dollar_amount']) # Or self.save() in Django < v1.5
return self._dollar_amount
You allow people to change their orders by adding/deleting items.
This can get a bit more complicated. You need to update the dollar_amount
whenever a new relation between your purchase order and a product is created, or whenever one is deleted. You might also want to save the product’s price at the moment it is added, otherwise you might deduct the wrong amount if the price get’s changed before the product is removed from the order. As there doesn’t seem a way to just override the add()
and delete()
methods etc. on a related manager, you’ll have to specify a custom through
model:
from django.db.models import F
class PurchaseOrder(models.Model):
product = models.ManyToManyField('Product', through='OrderItem')
dollar_amount = models.DecimalField(verbose_name='Total Price')
class OrderItem(models.Model):
current_price = models.DecimalField()
product = models.ForeignKey(Product)
order = models.ForeignKey(PurchaseOrder)
def save(self, *args, **kwargs):
if self.pk is None:
# new item
self.current_price = self.product.price_for_each_item
self.order.dollar_amount = F('dollar_amount') + self.current_price
self.order.save(update_fields=['dollar_amount']) # Or self.save() in Django < v1.5
super(OrderItem, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
self.order.dollar_amount = F('dollar_amount') - self.current_price
self.order.save(update_fields=['dollar_amount']) # Or self.save() in Django < v1.5
super(OrderItem, self).save(*args, **kwargs)
This will complicate adding or deleting items from/to a purchase orders, as the normal self.product.add()
and self.product.delete()
methods aren’t available. You’ll have to manually create an OrderItem
and save it, but I don’t think there’s an easier way.