Почему Django не создает ограничение внешнего ключа в MySQL?
Я не новичок в Python и Django, но это первый раз, когда я создаю совершенно новый большой проект с нуля, а также первый раз, когда я фактически создаю модели для всей базы данных, и я немного запутался здесь.
Действительно ли Django не создает ограничения ForeignKey в базе данных для проверки существования ID? Это просто логическая вещь питона, которая работает только при запущенном сервере? Или это проблема, которая возникает на MySQL?
Чтобы было понятно, о чем я говорю, первое, что я заметил, поскольку, будучи разработчиком Laravel на стороне PHP, я привык всегда проверять диаграмму базы данных, которую генерирует PhpStorm/PyCharm, подключаясь к базе данных, и на перенесенных таблицах Laravel мы можем видеть стрелки, указывающие на соответствующие связи таблиц с внешними ключами, но на созданной базе данных Django нет ни одной стрелки на диаграмме базы данных, сгенерированной JetBrains IDE. Поэтому я занялся тестированием.
Например, у меня есть следующие модели:
class Series(models.Model):
class Meta:
app_label = 'core'
db_table = 'km_series'
verbose_name_plural = 'series' # added this to avoid plural being "seriess"
name = models.CharField(max_length=200)
description = models.TextField(default=None, blank=True, null=True)
cover_img = models.CharField(max_length=100, default=None, blank=True, null=True)
on_going = models.BooleanField(default=False)
date_added = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return "{} - ID #{}".format(self.name, self.id)
class Chapter(models.Model):
class Meta:
app_label = 'core'
db_table = 'km_chapter'
series = models.ForeignKey(Series, to_field='id', on_delete=models.CASCADE)
number = models.IntegerField(validators=[MinValueValidator(0)])
name = models.CharField(max_length=150, default=None, blank=True, null=True)
date_added = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
def __str__(self):
return "#{} - {}".format(self.number, self.name)
У меня уже создано более 15 моделей с использованием models.ForeignKey
наряду с другими полями. Я только что попробовал создать новую строку в MySQL, используя python manage.py shell
.
$ python manage.py shell
>>> from core.models import *
>>> Series
<class 'core.models.base_models.Series'>
>>> one = Series.objects.create(name='Test')
>>> one
<Series: Test - ID #1>
>>> one.id
1
>>> chapter = Chapter.objects.create(number=1)
MySQLdb.OperationalError: (1048, "Column 'series_id' cannot be null")
>>> chapter = Chapter.objects.create(number=1, series_id=2)
>>> chapter
<Chapter: #1 - None>
>>> chapter_1 = Chapter.objects.create(number=1, series=one)
>>> chapter_1
<Chapter: #1 - None>
>>> chapter = Chapter.objects.create(number=1, series_id=25)
В базе данных есть только один идентификатор, где идентификатор равен "1"
Итак, как я могу добавить любой ID при ручном присвоении, а не передавать весь инстанцированный объект в качестве значения foreign_key?
Почему Django позволяет мне устанавливать ID на несуществующие ID в базе данных? Разве это не должно приводить к ошибке? Я что-то упускаю в своих моделях? Почему нет ограничений и валидаций для таких вещей?
После того, как я немного покопался в том, почему MySQL может иметь проблемы с FK-контрактами, и после использования команды python manage.py dbshell
для проверки создания таблицы, как мне рекомендовали сделать в комментарии, используя SHOW CREATE TABLE core_chapter;
, я обнаружил проблему.
По какой-то причине, которую я не знаю, моя база данных была создана с использованием MyISAM, который является движком хранения MySQL, не поддерживающим FK ограничения. Мне пришлось изменить значение по умолчанию на InnoDB, так как он работает с этим типом валидации для отношений между таблицами.
В моем файле my.cnf
, который содержит конфиги для MySQL, я установил InnoDB по умолчанию, добавив свойство default-storage-engine
[mysqld]
default-storage-engine = InnoDB
В настройках Django я также добавил DATABASE "OPTIONS", как указано в Django documentation for databases, поэтому мое значение DATABASES изменилось на:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'database_name',
'USER': 'database_user',
'PASSWORD': '****',
'HOST': 'localhost',
'PORT': '3306',
'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB'},
}
}
После изменения всего этого, сброса всех таблиц базы данных и повторного вызова команды python manage.py migrate
ограничения были созданы, как и ожидалось, и теперь моя диаграмма базы данных, сгенерированная PyCharm, безупречно отображает все стрелки, показывающие взаимосвязи.