'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