Django auto_now Removal Gotcha
Posted on | May 6, 2007 | 2 Comments
The latest Django revisions have removed the shortcut handlers "auto_now" and "auto_now_add". This didn’t look like a very big deal to replace. I simply did what was suggested in a django-users thread.
The problem was that after I did that, my live code seemed to work fine, but my test cases had several repeatable errors that I just couldn’t reproduce on the live server or at the command line.
The Fix
The fix involves overriding the "save" method, like so (quoting the thread)
class MyModel(models.Model):
self.created = models.DatetimeField()
self.modified = models.DatetimeField()
def save(self):
if not self.id:
self.created = datetime.datetime.now()
else:
self.modified = datetime.datetime.now()
super(MyModel, self).save()
What went wrong
After a lot of debugging and shrewd guessing, I finally had the eureka moment. The problem is that some of the test code was directly instantiating objects via calls like MyModel.objects.get_or_create(name="this", field="that"). In some places the test cases were using the even simpler MyModel.objects.create(name="this", field="that").
It finally occurred to me while walking the dog this morning that creating objects this way will skip the overridden "save()" method. I rushed to the computer to test my theory, and found that if I replaced:
MyModel.objects.get_or_create(name="this", field="that")
with this:
try:
m = MyModel.objects.get(name="this", field="that")
except MyModel.DoesNotExist:
m = MyModel(name="this", field="that")
m.save()
everything worked perfectly.
So, watch out for that gotcha now that you can’t use auto_now in models any more.
Comments
2 Responses to “Django auto_now Removal Gotcha”
Leave a Reply
May 7th, 2007 @ 5:53 pm
There’s something else going on here, I suspect. Both the create() and get_or_create() method do call your model’s save() method. In fact, get_or_create() is implemented more or less exactly as in your second example.
If you want to trace throught the code, MyModel.objects.get_or_create() calls django.db.models.manager.Manager.get_or_create() which just passes off the real work to django.db.models.query.QuerySet.get_or_create() and you can see that calling obj.save() when required.
So it would be interesting to understand what is really changing when you make the substitution you suggest above.
February 24th, 2008 @ 10:03 am
Minor typo.
models.DatetimeField() should be models.DateTimeField() (with capitalized ‘T’).