Django's DateTimeField provides two convenient parameters for automatic timestamp management: auto_now and auto_now_add. While these seem straightforward at first, they come with subtle limitations that can cause frustration when you need more flexibility.
Understanding auto_now and auto_now_add
Let's start with what these parameters do:
- auto_now_add: Automatically sets the field to the current datetime when the object is first created. The field is then never updated on subsequent saves.
- auto_now: Automatically sets the field to the current datetime every time the object is saved.
A typical use case looks like this:
class TimeTest(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
This pattern is common for tracking when records are created and last modified. The created field is set once on creation, while updated changes every time you save the object.
The Hidden Constraint
Here's where things get tricky: fields with auto_now_add=True or auto_now=True are automatically set to editable=False. This means:
- They won't appear in model forms
- They can't be manually set or overridden
- You can't pass values for these fields when creating objects
This is by design—Django assumes that if you want automatic timestamps, you never want to manually control them.
When This Becomes a Problem
The issue arises when you need to:
- Import historical data: You have old records with specific creation timestamps that you need to preserve
- Testing: You want to create objects with specific timestamps for test scenarios
- Data migrations: You're migrating from another system and need to maintain original timestamps
- Manual overrides: In rare cases, you need to manually adjust timestamps (e.g., correcting errors)
If you try to make a field with auto_now_add editable, you'll run into migration errors:
class TimeTest(models.Model):
created = models.DateTimeField(auto_now_add=True, editable=True)
updated = models.DateTimeField(auto_now=True)
Running python manage.py makemigrations will fail with a KeyError or similar error. Django explicitly prevents this combination because it's considered contradictory—why would you want both automatic and manual control?
Solution 1: Use default Instead
The most straightforward solution is to use default instead of auto_now_add:
from django.utils import timezone
class TimeTest(models.Model):
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(auto_now=True)
With default, the field is still editable, so you can override it when needed:
# Normal creation uses current time
obj = TimeTest.objects.create()
# But you can override it
historical_obj = TimeTest.objects.create(
created=datetime(2020, 1, 1, tzinfo=timezone.utc)
)
Note: Use timezone.now without parentheses—you're passing the function itself, not calling it. Django will call it when needed.
Solution 2: Override save() for auto_now Behavior
For the updated field, if you need it to be editable, you can implement the auto-update logic yourself:
from django.utils import timezone
class TimeTest(models.Model):
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(default=timezone.now)
def save(self, *args, **kwargs):
# Update 'updated' field unless explicitly told not to
if not kwargs.pop('skip_updated', False):
self.updated = timezone.now()
super().save(*args, **kwargs)
This gives you full control: the field updates automatically by default, but you can override it when needed:
# Normal save updates 'updated'
obj.save()
# Can skip the update
obj.save(skip_updated=True)
# Can set it manually
obj.updated = specific_datetime
obj.save(skip_updated=True)
Solution 3: Custom Field Subclass
For a more reusable solution, create a custom field class:
from django.db import models
from django.utils import timezone
class AutoDateTimeField(models.DateTimeField):
def pre_save(self, model_instance, add):
if add or not getattr(model_instance, '_skip_auto_now', False):
value = timezone.now()
setattr(model_instance, self.attname, value)
return value
else:
return super().pre_save(model_instance, add)
class TimeTest(models.Model):
created = models.DateTimeField(default=timezone.now)
updated = AutoDateTimeField(default=timezone.now)
Best Practices
Based on my experience, here are some recommendations:
- For created timestamps: Use
default=timezone.nowinstead ofauto_now_add. This gives you flexibility without much downside. - For updated timestamps:
auto_now=Trueis usually fine unless you specifically need manual control. - Be explicit in tests: Always pass explicit timestamps in test data rather than relying on automatic values.
- Document exceptions: If you need to override timestamps, document why in comments.
Conclusion
While auto_now and auto_now_add seem convenient, their inflexibility can cause problems down the line. Understanding these limitations and using alternatives like default or custom save() methods gives you better control while maintaining the convenience of automatic timestamps.
The key is knowing when you need that flexibility. For simple cases, auto_now_add works fine. But if you're building a system that might need to import historical data or requires fine-grained control, it's better to use the more flexible approaches from the start.