[Answer]-Making an instance of a class equal to a definition Django

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.

👤knbk

Leave a comment