Adding fields to database schemas to audit the creation and modification is a common best practice, useful for any number of things, most commonly debugging and cache invalidation. The good news is, Django has field-types built for just this purpose!
As always, let’s start with the docs!
These fields are built into Django for expressly this purpose - auto_now
fields are updated to the current timestamp every time an object is saved and are therefore perfect for tracking when an object was last modified, while an auto_now_add
field is saved as the current timestamp when a row is first added to the database, and is therefore perfect for tracking when it was created.
It’s worth noting that that both fields are set on initial creation, whether they go through .save()
or bulk_create
- they may be a few milliseconds different, but will be effectively the same, but an auto_now_add
field won’t change again after it’s set.
Let’s dive in and talk through some quirks of these fields.
Django’s DateField
definition includes this on init:
if auto_now or auto_now_add:
kwargs['editable'] = False
kwargs['blank'] = True
super(DateField, self).__init__(verbose_name, name, **kwargs)
This has some implications for both your Django admin set-up, as well as different-than-usual options, should you need to update these yourself.
Because both of these fields are read only, by default they won’t show up in your django admin view. If you try to explicitly include them via the fields option, you’ll see an error that looks like this:
'created_at' cannot be specified for <ModelClass> model form as it is a non-editable field
If you want them to appear anyway, you can add them to readonly_fields
on a ModelAdmin
class, and they will be displayed on the form in a non-editable way. They can also be included in list_display
.
Because an auto_now_add
field is only set on initial creation, not on can be changed manually in the same way you’d update any other field - by setting the value and calling .save()
.
However, if you were to do this with an auto_now
field, because calling .save()
would itself change the value, the value you manually set would not be reflected. But fear not - there are a couple ways you can update it!
auto_now_add
field relies on Django’s .save()
mechanism, any value can be changed using a SQL update statement.update()
- for the same reason calling .update()
does not update the value even if you might want it to, we can take advantage of this fact and pass a value to the statement, and it will be persisted. You can always do this for a single row with a statement like <Model>.objects.filter(id=<object_id>).update(modified_at=<desired timestamp>)
Customers often want to know when something was created or updated - this can be useful information for sorting, display, etc. However, these fields may not be the right choice for displaying this information to users. In general, I’d recommend using them only for internal auditing purposes.
For reasons like these, I recommend having separate customer-facing fields that only update on customer actions, and only using these built-in Django fields for internal auditing purposes.
If you know you want these auditing fields added to all of your models, and you don’t want to have to remember to add them each time you create a new model, you can create a base class that looks something like this:
class YourBaseClass(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
And then instead of your model classes being instances of models.Model
, they can use YourBaseClass
instead.