2

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:

  1. 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.
  2. After this, related receipt.update_total(amount) should be called with product.price * quantity value as amount to sync the amount of the receipt.
  3. Product sold_quantity field should be increased by ReceiptItem istance quantity value.
  4. 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?

Thank you!

1 Answer 1

3

I would recommend create classmethod on ReceiptItem. In large code bases, you want model instance creation logic to be not spread all around your code base.

Think what happens when in future you realize that create logic also needs to take care of discounting based on product! If everyone is doing ReceiptItem(product=x, ...) then update is nightmare!

I have been using models with create classmethods and it makes maintaining and updating code later much less painful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.