Django: overriding save() just on new objects

Today I was tasked to create a new feature: create a form that would allow the user create a record on the database. Easy peasy for Django. One requirement, though, was to have a field that would be calculated before saving just on a new record, not when updating it.

Approach 1 - Django Signals

Searching online I saw people suggesting using Django Signals to do that. I don't like this approach since, mainly, it was meant to enable doing things on other places when acting on an object. Not on the object you're dealing with.

Approach 2 - Overriding save()

Django Model save() method can be overridden easily but, to check whether it is a new record or not, can be tricky:

You could check if your record already has a primary key associated with it. If it does, then you would be updating instead of creating:

def save(self, *args, **kwargs):
    if self.pk:
        # Do you custom stuff here and add to you instance
        # ex. self.name = str.upper(name) 
    super(Person, self).save(*args, **kwargs)

One thing though: you cannot do that if your model inherits from and abstract class that manually associates a custom PK like an UUID:

import uuid

class BaseModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_date = models.DateTimeField(auto_now_add=True)
    updated_date = models.DateTimeField(auto_now=True)
    class Meta:
        abstract = True

class Person(BaseModel):
    name = models.CharField(max_length=200)

    class Meta:
        ...

By default, self.pk is populated after saving to the database in the case of a new object with the DB auto-generated ID. That's why it works like in the example and not when using a custom PK (when inheriting from a base class, the PK is generated on instantiation because of the default=uuid.uuid64 argument).

So, the approach I'm using for now is a bit risky: using and internal field. Django Model's __init__() method sets a _state.adding field to True when the object is new (not saved yet):

def save(self, *args, **kwargs):
    if self._state.adding:
        # Do you custom stuff here and add to you instance
        # ex. self.name = str.upper(name) 
    super(Person, self).save(*args, **kwargs)

And that did the trick.

Approach 3 - Overriding save() but validate with a new query

For the UUID base class (my scenario) I opted for the previous solution to avoid issuing a new query to check if the object already exists in the database before modifying it. I could have used the solution bellow:

def save(self, *args, **kwargs):
    instance = Person.objects.filter(pk=self.pk) # self.pk already
                                                 # populated even if the 
                                                 # object is new
    if not instance:
        # Do you custom stuff here and add to you instance
        # ex. self.name = str.upper(name) 
    super(Person, self).save(*args, **kwargs)

And that's a wrap! If you have another solution I would like to see your approach.

Cheers!