Django: django.db.utils.IntegrityError: (1215, «Невозможно добавить ограничение внешнего ключа»)

Новая миграция, добавляющая одну простую таблицу, дает мне ошибку «Невозможно добавить ограничение внешнего ключа» во время миграции.

Вот существующая модель под названием EventLog:

class EventLog(models.Model):
    """
    The event log.
    """
    user = models.ForeignKey(User, blank=True, null=True)
    timestamp = models.DateTimeField(auto_now=True)
    text = models.TextField(blank=True, null=True)
    ip = models.CharField(max_length=15)
    metadata = JSONField(default={},blank=True)
    product = models.TextField(default=None,blank=True, null=True)
    type = models.ForeignKey(EventType)

    def __unicode__(self):
        return "[%-15s]-[%s] %s (%s)" % (self.type, self.timestamp, self.text, self.user)

    def highlite(self):
        if self.type.highlite:
            return self.type.highlitecss
        return False

Вот новая модель, которую я пытаюсь создать:

class EventLogDetail(models.Model):
    # NOTE: I've already tried switching 'EventLog' out for just EventLog.
    eventlog = models.ForeignKey('EventLog', related_name='details')
    order = models.IntegerField(default=0)
    line = models.CharField(max_length=500)

    class Meta:
        ordering = ['eventlog', 'order']

Кажется достаточно простым, не так ли? Итак, я делаю миграцию:

./manage.py makemigrations:

Migrations for 'accounts':
  accounts/migrations/0016_eventlogdetail.py
    - Create model EventLogDetail

Все идет нормально. Затем я мигрирую, например:

./manage.py migrate:

Operations to perform:
  Apply all migrations: accounts, admin, attention, auth, contenttypes, freedns, hosting, info, mail, sessions, sites, taggit, vserver
Running migrations:
  Applying accounts.0016_eventlogdetail...Traceback (most recent call last):
  File "./manage.py", line 10, in 
    execute_from_command_line(sys.argv)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 363, in execute_from_command_line
    utility.execute()
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 355, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/core/management/base.py", line 283, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/core/management/base.py", line 330, in execute
    output = self.handle(*args, **options)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/core/management/commands/migrate.py", line 204, in handle
    fake_initial=fake_initial,
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 115, in migrate
    state = self._migrate_all_forwards(state, plan, full_plan, fake=fake, fake_initial=fake_initial)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 145, in _migrate_all_forwards
    state = self.apply_migration(state, migration, fake=fake, fake_initial=fake_initial)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 244, in apply_migration
    state = migration.apply(state, schema_editor)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 93, in __exit__
    self.execute(sql)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/backends/base/schema.py", line 120, in execute
    cursor.execute(sql, params)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/backends/utils.py", line 65, in execute
    return self.cursor.execute(sql, params)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/django/db/backends/mysql/base.py", line 101, in execute
    return self.cursor.execute(query, args)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/MySQLdb/cursors.py", line 205, in execute
    self.errorhandler(self, exc, value)
  File "/home/the_user/code/the_project/local/lib/python2.7/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
django.db.utils.IntegrityError: (1215, 'Cannot add foreign key constraint')

Вот и сама миграция во всей красе Pythonic:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-30 12:51
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

    dependencies = [
        ('accounts', '0015_product_public'),
    ]

    operations = [
        migrations.CreateModel(
            name='EventLogDetail',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('order', models.IntegerField(default=0)),
                ('line', models.CharField(max_length=500)),
                ('eventlog', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='accounts.EventLog')),
            ],
            options={
                'ordering': ['eventlog', 'order'],
            },
        ),
    ]

Я пытался переименовать новую модель и все в ней (событие атрибута related_name), на случай, если я использовал имена переменных, уже принятые какой-то машиной под капотом, но с теми же результатами.

При поиске в Интернете я нашел только один пример этой проблемы конкретно с Django (Django MySQL error при создании таблиц), но это не помогло. В auth нет никаких миграций, и я не понимаю, почему они должны быть, поскольку мы не возились с этой частью и не обновляли какие-либо пакеты в последнее время.

Любая помощь глубоко оценена.


person Teekin    schedule 30.08.2017    source источник
comment
Вы пытались удалить цитату из журнала событий?   -  person Raj Subit    schedule 30.08.2017
comment
Да. Не имеет значения. Спасибо хоть. :)   -  person Teekin    schedule 30.08.2017


Ответы (4)


Моя проблема заключалась в том, что база данных в моем проекте Django создается с нуля, а таблицы импортируются из дампа mysql. Таблицы в дампе mysql имели CHARSET utf8mb4, в то время как новые таблицы, которые я создаю с помощью миграции, создаются с CHARSET latin1. Таким образом, новый внешний ключ создается в таблице с latin1 и ссылается на таблицу с utf8mb4, которая выдает ошибку

Django: django.db.utils.IntegrityError: (1215, «Невозможно добавить ограничение внешнего ключа»)

Новые таблицы создаются с CHARSET latin1, потому что CHARSET по умолчанию базы данных, которую я создал, был latin1. Чтобы проверить CHARSET по умолчанию, введите следующую команду в консоли mysql.

mysql> SELECT default_character_set_name FROM information_schema.SCHEMATA S WHERE schema_name = "DBNAME";

Обходной путь для этой проблемы — преобразование CHARSET базы данных по умолчанию в utf8mb4. Это необходимо не только для исправления ошибки целостности, но и у Django будет много других проблем, если CHARSET не utf8. Чтобы изменить CHARSET базы данных, используйте эту команду.

mysql> ALTER DATABASE DBNAME CHARACTER SET utf8 COLLATE utf8_general_ci;
person Sandeep Balagopal    schedule 29.09.2020

Ладно, разобрался.

Я попытался вручную создать внешний ключ, который затем потерпел неудачу с тем же загадочным сообщением об ошибке. В поисках решений, полностью ориентированных на MySQL, я нашел ответ @Andrew здесь: MySQL не может добавить Ограничение внешнего ключа, в котором подробно описаны требования к внешнему ключу.

Одно из этих требований заключается в том, что обе таблицы должны использовать один и тот же тип механизма, которым может быть InnoDB или MyISAM. Оказывается, в моей базе данных старые таблицы — это MyISAM, а более новые — InnoDB, и действительно, это было корнем моей проблемы.

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

#!/bin/bash

DBNAME=excellent_database
PASSWORD=very-very-bad-password-on-many-sides-on-many-sides

# Some of the datetime data in the old MyISAM tables were giving
# InnoDB a rough time so here they are updated to something InnoDB
# feels more comfortable with. Subqueries didn't work and I
# couldn't be bothered to figure out why.
IDS=$(mysql "$DBNAME" -u root -p"$PASSWORD" -e "SELECT id FROM appname_modelname WHERE timestamp_created = '0000-00-00 00:00:00';")
for ROW_ID in $IDS; do
    mysql "$DBNAME" -u root -p"$PASSWORD" -e "UPDATE appname_modelname SET timestamp_created = '0001-01-01 00:00:00' WHERE id = $ROW_ID";
    echo $ROW_ID
done

mysql "$DBNAME" -u root -p"$PASSWORD" -e "SHOW TABLE STATUS WHERE ENGINE = 'MyISAM';" | awk 'NR>1 {print "ALTER TABLE "$1" ENGINE = InnoDB;"}' | mysql -u root -p"$PASSWORD" "$DBNAME"

Надеюсь, это поможет кому-то еще!

person Teekin    schedule 30.08.2017

Изменять

eventlog = models.ForeignKey('EventLog', related_name='details')

to

eventlog = models.ForeignKey(EventLog, related_name='details')

;)

person campovski    schedule 30.08.2017
comment
Это не имеет значения. Спасибо хоть. :) - person Teekin; 30.08.2017
comment
Пожалуйста, опубликуйте django_migrations таблицу. - person campovski; 30.08.2017
comment
Хороший вопрос, @campovski, но я нашел решение и разместил его выше. :) Спасибо за внимание. - person Teekin; 30.08.2017

Попробуйте удалить цитату:

eventlog = models.ForeignKey(EventLog, related_name='details')

или если вы хотите использовать цитату, используйте app_name также

eventlog = models.ForeignKey('accounts.EventLog', related_name='details')
person Raj Subit    schedule 30.08.2017
comment
Пробовал это. Тот же результат. Спасибо за ответ. :) - person Teekin; 30.08.2017