I have a Django model that looks something like this:

class MyModel(models.Model):
   modify_date = models.DateTimeField(auto_now=True)
   ...

Retroactively now I wanted to add a field called add_date which uses the auto_now_add=True trick. The migration used in this project is South which is great but doesn't work very well with the auto_now_add=True because the field doesn't have a straight forward default. So, first I changed the field to this:

class MyModel(models.Model):
   modify_date = models.DateTimeField(auto_now=True)
   add_date = models.DateTimeField(auto_now_add=True, null=True)
   ...

Notice the null=True which is important. Then I used startmigration to generate the code for the forward and backward to which I added a this stuff:

class Migration:

   def forwards(self, orm):

       db.add_column('myapp_mymodel', 'add_date', orm['myapp.mymodel:add_date'])
       for each in MyModel.objects.all():
           # since MyModel is referenced elsewhere I can work out the oldest date
           oldest_date = get_oldest_related_date(each, 
                              default=each.modify_date)
           each.add_date = oldest_date
           each.save()

That way all old records will have the date (not entirely accurate but good enough) and all new records will automatically get a date. Is there a better way? I bet, but I don't know how to do it.

Carl - 17 December 2009 [«« Reply to this]
I think the better way is to avoid using auto_now_add in the first place, and instead use the more explicit "default=datetime.datetime.now" (or utcnow). Then South can handle it without assistance.
Nirvisual - 18 December 2009 [«« Reply to this]
Carl: Using "default=datetime.datetime.now" doesn't work with South migrations. It causes South to evaluate the expression, hard-coding today's date in the migration. Then every time you run the migration you get that date, regardless of the actual date.
Carl - 18 December 2009 [«« Reply to this]
Hmm, apparently South 0.6 doesn't actually handle dynamic defaults well at all; I was basing this comment off the last time I did it, with 0.5, when it worked fine. So never mind ;-)

Though apparently the next version of South will handle it well again (by ignoring defaults entirely): http://south.aeracode.org/ticket/273
Gord - 28 April 2010 [«« Reply to this]
We use something like the following when migration data containing fields that have auto_now or auto_now_add:

#Hack must override the pre_save to get touched to update correctly.
def no_touch_pre_save(self, model_instance, add):
return super(models.fields.DateField, self).pre_save(model_instance, add)
#End override touched.

def no_auto_add(fn):
"""
Hack django to not update touched - used for migrations.
"""
def _no_auto_add(*args, **kwargs):
#Force users to be explicit in this method about touched fields.
old_pre_save = models.fields.DateField.pre_save
models.fields.DateField.pre_save = no_touch_pre_save

#Call fn
ret = fn(*args, **kwargs)

#Revert to old skool pre_save in case.
models.fields.DateField.pre_save = old_pre_save
return ret
return _no_auto_add
Fabian Topfstedt - 29 November 2010 [«« Reply to this]
In your case I would monkeypatch the fields of MyModel while migrating:

from django.db.models.fields import DateTimeField
from my_app.models import MyModel

def forwards(self, orm):
for f in [f for f in MyModel._meta.fields if isinstance(f, DateTimeField)]:
f.auto_now_add = False
f.auto_now = False
# Your data migration code here...


Your email will never ever be published