'Default value for foreign key in Django migrations.AddField
Using migrations, I need to add a new field (a foreign key) to a model. I know it can be done with:
migrations.AddField(
model_name='MyModel',
name='state',
field=models.ForeignKey(null=True, related_name='mymodel_state', to='msqa_common.MyModelState'),
),
However, I don't want my field to be nullable. Instead, I want to use a default value for it, corresponding to the id of MyModelState whose name is "available" (id value might change in different machines). This "available" value of table MyModelState is inserted into the database in a previous migration script, so it does exist.
I guess I should do something like:
migrations.AddField(
model_name='MyModel',
name='state',
field=models.ForeignKey(null=False, default=available_state_id, related_name='mymodel_state', to='msqa_common.MyModelState'),
),
My question: How can I get the available_state_id
within my migration script?
Solution 1:[1]
You can't do it directly. The recommended way of doing this is to create a migration to add it with null=True, then add a data migration that uses either Python or SQL to update all the existing ones to point to available_state_id
, then a third migration that changes it to null=False.
Solution 2:[2]
I just had the same issue and stumbled upon this answer, so here is how I did it:
operations = [
# We are forced to create the field as non-nullable before
# assigning each Car to a Brand
migrations.AddField(
model_name="car",
name="brand",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="model.Brand",
),
),
# assign_car_to_brand loops over all my Car objects and sets their
# "brand" field
migrations.RunPython(add_category_to_tags, do_nothing),
# Make the field non-nullable to force all future Car to have a Brand
migrations.AlterField(
model_name="car",
name="brand",
field=models.ForeignKey(
null=False,
on_delete=django.db.models.deletion.PROTECT,
to="model.Brand",
),
preserve_default=False
),
]
Solution 3:[3]
Here is a relatively complete example:
Step One
python manage.py makemigrations
, set the temporary default value to None
Step Two
Change the genrated migration code to below style
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
def set_default_author_to_blog(apps, schema_editor):
User = apps.get_model("auth", "User")
Blog = apps.get_model("blog", "Blog")
Blog.objects.update(author=User.objects.first())
def revert_set_default_autor_to_blog(apps, schema_editor):
Blog = apps.get_model("blog", "Blog")
Blog.objects.update(author=None)
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('blog', '0001_auto_20220425_1017'),
]
operations = [
migrations.AddField(
model_name='blog',
name='author',
field=models.ForeignKey(null=True, db_constraint=False, on_delete=django.db.models.deletion.PROTECT,
to='auth.user', verbose_name='Author')
),
migrations.RunPython(set_default_author_to_blog, reverse_code=revert_set_default_autor_to_blog),
migrations.AlterField(
model_name='blog',
name='author',
field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.PROTECT,
to='auth.user', verbose_name='Author')
),
]
Step Three
python manage.py migrate
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Daniel Roseman |
Solution 2 | |
Solution 3 | BaiJiFeiLong |