Yet another blog about general thoughts and programming. Mostly Python and Django.
Alexandre Jacques
Alexandre is an old fellow with some baggage related to tech and development
Sometimes you need to make Django behave a little different . This is one of these situations.
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.
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.
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.
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!