Adding Fields to Models With Pre-existing Data
When you add a field to a model that already has stored data, Django must populate that field with some value for the pre-existing data. For example, consider the following model:
class Course(models.Model):
name = models.CharField("Name", max_length=128)
enrolled_students = models.IntegerField("Students")
Which contains in the underlying database (let's say, SQLite) the following rows:
Now let's say you want to add the time
field:
class Course(models.Model):
name = models.CharField("Name", max_length=128)
enrolled_students = models.IntegerField("Students")
TIMES_OF_DAY = (
(1, "Morning"),
(2, "Afternoon"),
(3, "Evening")
)
time = models.PositiveSmallIntegerField("Time", choices=TIMES_OF_DAY)
If you run python manage.py makemigrations myapp
to create a new migration, Django will complain saying:
It is impossible to add a non-nullable field 'time' to course without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Select an option:
This means that the pre-existing courses ("Advanced Python", "Java", "PHP" and "Django") will now have a new time
column that cannot be empty (because the default value of the null
argument in the definition of a field is False
.) So, Django offers two solutions: either you enter a one-time value to populate the time
field for all pre-existing courses, or you exit and modify models.py
so that time
has a default value or accepts null values, and run the makemigrations
command again.
If you choose the first option, Django will launch an interactive console in which you will have to enter any value to populate the time
field of the pre-existing courses:
Select an option: 1
Please enter the default value as valid Python.
The datetime and django.utils.timezone modules are available, so it is possible to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
>>> 1
After this, continue with the migration by running python manage.py migrate myapp
. Now in the database you will see that all pre-existing courses have time=1
("Morning"):
The second option was to exit and modify the time
definition to accept null values:
class Course(models.Model):
name = models.CharField("Name", max_length=128)
enrolled_students = models.IntegerField("Students")
TIMES_OF_DAY = (
(1, "Morning"),
(2, "Afternoon"),
(3, "Evening")
)
time = models.PositiveSmallIntegerField("Time", choices=TIMES_OF_DAY, null=True)
This lets you to create and apply the migration without problem, since pre-existing courses are simply filled with NULL
(or the equivalent in the configured database engine):
This implies that all those courses, when retrieved as Python objects, will have time=None
:
>>> from myapp.models import Course
>>> course_swift = Course.objects.get(name="Swift")
>>> course_swift.time is None
True
The third option was also to modify models.py
, but by giving the default
parameter:
class Course(models.Model):
name = models.CharField("Name", max_length=128)
enrolled_students = models.IntegerField("Students")
TIMES_OF_DAY = (
(1, "Morning"),
(2, "Afternoon"),
(3, "Evening")
)
time = models.PositiveSmallIntegerField("Time", choices=TIMES_OF_DAY, default=3)
Like null=True
, this lets you make and run without problems. The new column will be populated with the specified default value:
Besides migrations, Django will also use the default
value when creating Course
instances if an explicit value is not given to time
. For example:
>>> from myapp.models import Course
>>> new_course = Course.objects.create(name="Java", enrolled_students=10)
>>> new_course.time
3
This also applies to forms generated from the Course
model using a ModelForm
. If the user does not submit a value for the time
field, the default will be used.