Как правильно выполнять миграции при добавлении нового уникального поля

Я добавил новое поле в одну из своих моделей:

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

Поскольку это поле не может быть пустым, django-admin makemigrations попросил меня предоставить разовое значение по умолчанию, что я и сделал. Вот сгенерированная миграция:

# 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,
        ),
    ]

Как и ожидалось, django-admin migrate выдал ошибку:

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

Я подумал, что могу отредактировать миграцию, чтобы установить уникальные значения, прежде чем сделать поле уникальным. Итак, я попытался:

# -*- 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,
        ),
    ]

К сожалению, я получаю эту ошибку при запуске django-admin migrate:

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

Я предполагаю, что operations не выполняются синхронно.

Я думаю, что мог бы решить проблему, разделив миграцию на две миграции, но я хотел бы знать, смогу ли я сделать это только за одну миграцию. Каков общий способ создания миграции при добавлении нового уникального поля в модель?

PS: я также пытался использовать выражение F по умолчанию (default=models.F('name') + '@example.fr'), но это не удалось:

django.db.utils.IntegrityError: could not create unique index "accounts_agency_email_key"
DETAIL:  Key (email)=(F(name) + Vallu(@example.fr)) is duplicated.

person Antoine Pinsard    schedule 20.03.2016    source источник
comment
Вы читали раздел документов, посвященный именно этой проблеме? docs.djangoproject.com/en /1.9/howto/письмо-миграции/   -  person koniiiik    schedule 20.03.2016
comment
@koniiiik, я ищу не на той странице docs.djangoproject.com/en/1.9/ топики/миграции Итак, похоже, нет способа заставить его работать только с одной миграцией.   -  person Antoine Pinsard    schedule 20.03.2016
comment
Есть ли причина, по которой вы не хотите использовать две миграции?   -  person koniiiik    schedule 20.03.2016
comment
Не особенно, просто из любопытства.   -  person Antoine Pinsard    schedule 20.03.2016
comment
Я был удивлен, что выполнение всех операций в одной миграции не сработало. Однако наличие 2 (или 3) миграций означает, что по ошибке одна из них может быть отменена без других, что поставит приложение в неожиданное состояние.   -  person Antoine Pinsard    schedule 20.03.2016
comment
Я написал pull request 9212 в Django Howto, вдохновленный этим вопросом, чтобы упростить его. и особенно для предотвращения неожиданного состояния. На данный момент жду.   -  person hynekcer    schedule 08.10.2017


Ответы (1)


Может быть, уже слишком поздно, но, может быть, это сработает для кого-то другого

Вы можете сделать это за одну миграцию с помощью метода migrations.RunSQL.

Для вашего примера кода после того, как вы добавили новое поле в свою модель и запустили команду python manage.py makemigrations (здесь, если у вас есть существующие строки в вашей таблице, команда хочет выбрать значение по умолчанию, вы можете выбрать "Предоставить одноразовое значение по умолчанию сейчас" и указать какое-то строковое значение, это не важно, потому что на самом деле мы его не использовали), затем перейдите к файлу миграции и измените часть операций с этим (обратите внимание, что я использую postgresql, вы можете изменить SQL для вашей базы данных)

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;'
        )
    ]

затем запустите команду «python manage.py migrate», вот и все.

person Selim Yılmaz    schedule 27.01.2018
comment
Это было потрясающе. Большое спасибо - person Martin; 10.03.2018