Django: overriding save() just on new objects
1st: 24 April 2023 • Last Updated: 24 April 2023 • 4 min read
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!