compare model data before save

I am not using any forms here, just data from APIs.

I have a model that has three fields:


class Example(models.Model):
    a = models.CharField ...
    b = models.ManyToManyField ...
    c = models.ForeignKeyField ...

I want to achieve the following:

  • when a value for a is being saved, I want to execute b.clear() and c = None on save().
  • when I set Example.b.set(...) = foo and Example.c = bar I want to set a=None on save().

so a model can either have a value in a or in b and c but never in both.

I tried overriding the models save() method, but I cannot distinguish (or can I?) between values I just loaded and the once I save. I also tried messing with pre_save signals, but I did not manage to come to a smart solution.

First, there is no mechanism available to you to guarantee this if you do not have complete control over the code being written that will update this model.

And, if you have this control, then the answer is relatively easy. Don’t allow for direct updates to a, b, and c. Create model methods for those operations and require that all updates use those methods rather than updating the model fields directly.

The issue in any situation with trying to use signals (ick) or overriding save, is that there are situations where those mechanisms aren’t used. (e.g., bulk_update, or, updating the other side of the ManyToManyField).

Since you would already need to manage those cases from a “coding standard” perspective, it’s not a far stretch to expand those requirements to using your model update methods.

I understand your input. Also I feel very uncertain when using signals.
I built a custom save function like this:

class Example(models.Model):
    a = models.CharField ...
    b = models.ManyToManyField ...
    c = models.ForeignKeyField ...

    def safe_save(self, objects: dict):
        if "a" in objects:
            self.b.clear()
            self.c = None
            self.a= objects["a"]
        elif "c" in objects:
            self.a = None
            self.products.set(objects["products"])
            self.asset = None
        return self.save()