Django overriden instance save metod won't return actual data after super().save()

I have a simple Shipment model. I need to access updated Shipment instance fields data after save(). But whenever I try to count Product instances related to Shipment instance I get not actual data (one save behind).

Flow example:

  1. Creating new Shipment using form with adding one Product instance with ChoiceField;
  2. Overriding Shipment save() saving instance first;
  3. After save() I'm trying to count products just added with self.products.all().count() getting 0 on first save();
  4. After repeating creation of new Shipment instance and again adding one Product instance self.products.all().count() will return 1. I'm aware of m2m problem when new m2m instance added after save() performed but in my case FK suppose not to have such a problem.

(simplified)

class Product(models.Model):
    shipments = models.ForeignKey(Shipment, related_name="products")


class Shipment(models.Model):
    order = models.ForeignKey(Order, related_name="shipments")

    def save(self, *args, **kwargs):
        super(Shipment, self).save(*args, **kwargs)
        count = self.products.all().count() # <-- Returns 0 even after instance saved with including FK model
    

The final goal is to acces Product shipped quantity and depend on result change the Order (FK related model) status.

I'm aware of m2m problem when new m2m instance added after save() performed but in my case FK suppose not to have such a problem.

It is essentially the same problem. The admin, or (Model)Form will first save the Shipment and then save the Products, it can not do that in the opposite order, since it needs the primary key (or some other field the ForeignKey is referring to). So it can not create the Product first, since it refers to a Shipment that needs to be created first. Strictly speaking, update logic could go in the opposite order, but it would probably only complicate matters more if the update flow is different from the create flow.

That being said, typically it is better not store aggregates in the model: determine aggregates when needed: storing aggregates in the model makes updating and keeping data in sync harder. You can use .annotate(…) [Django-doc] to generate counts, sums, etc. per object when needed. So I don't think you should determine the number of products in the Shipment.

Indeed, one can dynamically add, remove or link a Product to a (different) Shipment. So without the .save() of the Shipment being called, the number of Products can change. Storing the number of Products explicitly in a field in the Shipment thus makes the software less reliable. I would implement some logic in a function, and let views that change Products or Shipment call the logic to fix the status, or perhaps better: determine the status dynamically in a @property or annotation.

Indeed, for example:

class Shipment(models.Model):
    # …

    @property
    def status(self):
        if self.products.count() > 5:
            return 'too heavy'
        else:
            return 'ready to ship'
Back to Top