I'm reading Two Scoops of Django 1.11. At 7th chapter it introduces "fat models". It says that best practice is to put a big part of logic into the model and keep views as thin as possible, but then it doesn't go deep into the argument. Assuming this way of thinking as django best practice and supposing models like these:
class Product(models.Model): name = models.CharField(max_length=50) price = models.DecimalField(decimal_places=2, max_digits=4) categories = models.ForeignKey(Category, on_delete=models.CASCADE) sold_quantity = models.PositiveIntegerField(default=0) stock_quantity = models.PositiveIntegerField(default=0) class Receipt(models.Model): is_paid = models.BooleanField(default=False) total = models.DecimalField(decimal_places=2, max_digits=4, default=0) def update_total(self, amount): self.total += amount class ReceiptItem(models.Model): product = models.ForeignKey(Product, null=True, on_delete=models.SET_NULL) receipt = models.ForeignKey(Receipt, on_delete=models.CASCADE) quantity = models.PositiveIntegerField()
When a ReceiptItem is created:
- ReceiptItem receipt field should point to the last Receipt whose is_paid value is False. If this Receipt doesn't exists, then create it and point to it.
- After this, related receipt.update_total(amount) should be called with product.price * quantity value as amount to sync the amount of the receipt.
- Product sold_quantity field should be increased by ReceiptItem istance quantity value.
- Product stock_quantity should be decreased by ReceiptItem istance quantity value.
Where should I put this logic? I think it should go into ReceiptItem model, but in this case, how can I achieve in "non tricky ways" point 1?
A hypothetical extension of ReceiptItem could, maybe, have these methods?
def create(self): (self.receipt, _) = Receipt.objects.get_or_create(is_paid=False) self.receipt.update_total(self.quantitiy * self.price) self.product.sold_quantity += self.quantity self.product.stock_quantity -= self.quantity def save(self, *args, **kwargs): self.receipt.save() self.product.save() super(ReceiptItem, self).save(*args, **kwargs)
Is it a bad idea to save related objects overriding save method? Is it correct practice to define a "create" method (or maybe classmethod) to operate on related models? Or should I try to split this logic in respective models in some way?