17

So some time a couple migrations after my first one, I decided I wanted to include these fields:

created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)

into one of my models. When I makemigrations it gave me You are trying to add a non-nullable field 'created' to episode without a default; we can't do that (the database needs something to populate existing rows).

So I then changed it to

created = models.DateTimeField(auto_now_add=True, default=datetime.now)

After trying to makemigrations again, it said that at_api.Episode.modified: (fields.E160) The options auto_now, auto_now_add, and default are mutually exclusive. Only one of these options may be present.

All right, so I just went ahead and removed the auto_now_add

created = models.DateTimeField(default=datetime.now)

I could now makemigrations without any problems. And then I later removed default=datetime.now and replaced it with auto_now_add=True, and migrated again without any problems. However, I can't help feeling that this might not be the best way of doing things. I feel like something might go wrong later in the project.

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
pyramidface
  • 1,207
  • 2
  • 17
  • 39

2 Answers2

10

I think the best practice here would have been to make the fields nullable. What your created field means at the moment is: "The time when the instance was created, or the arbitrary time when I ran the migration." The standard way to represent the lack of a value is NULL, rather than an arbitrary value.

That said, if you do want to use some arbitrary value you just need to tell Django what it is. Usually makemigrations gives you the option to indicate a one-off value to use for existing rows - did that not happen?

A more laborious method would be to declare the field nullable, then create a data migration to fill in your desired value, and then make it non-nullable. What you did is basically a simplified version of that. I don't see it creating any problems moving forward other than the issue of created not really being the time the instance was created.

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
  • Yep, django did ask me about a one-off value for existing rows. I tried `datetime.now` but it didn't seem to work. But I suppose everything is overall okay then? Is it typical to set `null=True` as a "placeholder" for existing row values and then remove it later to replace with something else? – pyramidface Jul 19 '15 at 10:27
  • 1
    `datetime.now()` would have worked (note the parentheses - you want to *call* the function and use its return value). You would add and remove `null=True` in more complicated cases where you want to assign a different value to different rows. For example, let's say you had the actual creation date stored in a file somewhere. You'd write a data migration that, for each row, looked up the date in the file and then put it in the database. After that, you could remove `null=True` and migrate again. – Kevin Christopher Henry Jul 19 '15 at 17:23
  • Ahh okay, I'll keep that in mind for future projects. Thanks!! – pyramidface Jul 19 '15 at 18:02
  • It might have worked with `timezone.now` at least on later version of Django that seems to work – Michael Discenza Dec 09 '20 at 02:23
5

I've just had the exact problem. I use Django 1.10. I read Kevin answer and I've tried to put default value when Django asked me to fill it as datetime.now string. And I was surprised because, for those fields, Django automatically ask you if you want to use datetime.now as default:

$ ./manage.py makemigrations
You are trying to add the field 'date_created' with 'auto_now_add=True' to projectasset without a default; the database needs something to populate existing rows.

 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
You can accept the default 'timezone.now' by pressing 'Enter' or you can provide another value.
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
[default: timezone.now] >>>

So, I just confirm that and everything seems to be working fine!

Darkowic
  • 661
  • 8
  • 17