Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly make migrations when adding a new unique field

I added a new field to one of my models:

class Agency(models.Model):
    email = models.EmailField(unique=True, verbose_name=_("e-mail"))

As this field cannot be blank, django-admin makemigrations requested me to provide one-off default, which I did. Here is the generated migration:

# Generated by Django 1.9.4 on 2016-03-20 10:38
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('accounts', '0008_auto_20160226_1226'),
    ]

    operations = [
        migrations.AddField(
            model_name='agency',
            name='email',
            field=models.EmailField(default='[email protected]', max_length=254, unique=True, verbose_name='e-mail'),
            preserve_default=False,
        ),
    ]

As expected, django-admin migrate throwed an error:

psycopg2.IntegrityError: could not create unique index "accounts_agency_email_key"
DETAIL:  Key (email)=([email protected]) is duplicate.

I thought I could edit the migration to set unique values before making the field unique. So I tried:

# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-03-20 10:38
from __future__ import unicode_literals

from django.db import migrations, models
from django.utils.text import slugify


def set_email(apps, schema_editor):
    Agency = apps.get_model('accounts', 'Agency')
    for agency in Agency.objects.all():
        agency.email = '{}@example.fr'.format(slugify(agency.name))
        agency.save()


class Migration(migrations.Migration):

    dependencies = [
        ('accounts', '0008_auto_20160226_1226'),
    ]

    operations = [
        migrations.AddField(
            model_name='agency',
            name='email',
            field=models.EmailField(default='', max_length=254, blank=True, verbose_name='e-mail'),
            preserve_default=False,
        ),
        migrations.RunPython(set_email),
        migrations.AlterField(
            model_name='agency',
            name='email',
            field=models.EmailField(max_length=254, unique=True, verbose_name='e-mail'),
            preserve_default=False,
        ),
    ]

Unfortunately I get this error when running django-admin migrate:

django.db.utils.OperationalError: cannot ALTER TABLE "accounts_agency" because it has pending trigger events

My guess is that operations are not executed synchronously.

I think I could fix the issue by splitting the migration into two migrations, but I'd like to know if I can do it in only one migration. What is the common way to create migrations when adding a new unique field in a model?

PS: I also tried to use an F expression as default (default=models.F('name') + '@example.fr') but it failed:

django.db.utils.IntegrityError: could not create unique index "accounts_agency_email_key"
DETAIL:  Key (email)=(F(name) + Vallu(@example.fr)) is duplicated.
like image 854
Antoine Pinsard Avatar asked Mar 20 '16 12:03

Antoine Pinsard


1 Answers

Maybe it's too late but maybe it could work for someone else

You can do this in one migration via using migrations.RunSQL method

For your example code after you added the new field to your model and run the python manage.py makemigrations command (here if you have existing rows in your table command wants to choice default value you can choice "Provide a one-off default now" option and give some string value it is not important because actually we did not use it) then go to migration file and change operations part with this (Note i use postgresql you can change SQL for your database)

operations = [
        migrations.RunSQL(
        'ALTER TABLE "agency" ADD COLUMN "email" varchar(254) NULL;ALTER TABLE "agency" ALTER COLUMN "email" DROP DEFAULT;COMMIT;',
        ),
        migrations.RunSQL(
        "UPDATE agency SET email= Concat(country_code, '@example.fr');COMMIT;",
        ),
        migrations.RunSQL(
        'ALTER TABLE "agency" ALTER COLUMN "email" SET NOT NULL;ALTER TABLE "agency" ADD CONSTRAINT "agency_email_b551ad2a_uniq" UNIQUE ("email");ALTER TABLE "agency" ALTER COLUMN "email" DROP DEFAULT;CREATE INDEX "agency_email_b551ad2a_like" ON "agency" ("email" varchar_pattern_ops);COMMIT;'
        )
    ]

then run "python manage.py migrate" command that is it.

like image 168
Selim Yılmaz Avatar answered Sep 18 '22 13:09

Selim Yılmaz