Foreign key django не работает

Создание связей между моделями через класс ForeignKey

Продолжаем совершенствовать наш учебный сайт и знакомиться с возможностями фреймворка Django. На этом занятии мы с вами добавим еще одну таблицу для категорий и свяжем ее с таблицей постов, так, чтобы каждая статья была соотнесена с той или иной категорией.

Я, думаю, нет необходимости разъяснять, для чего нужна отдельная таблица для категорий? Например, если мы в будущем захотим переименовать какой-либо раздел, то для этого достаточно будет поменять название только в одном месте таблицы и это не затронет тысячи записей из таблицы постов women. Есть много других преимуществ такого подхода. Вообще, разделение данных на несколько таблиц и установление между ними связей, называется нормализацией данных. Этому принципу всегда нужно следовать во избежание больших сложностей при использовании БД.

Итак, в нашем проекте, очевидно, нужно определить еще одну таблицу (модель) для категорий и связать ее с таблицей постов:

Для этого мы добавим еще одно поле cat_id в таблицу women, которое будет определено как внешний ключ и хранить идентификатор категории. А в таблице category определим два поля: идентификатор id и название раздела – name. Давайте выполним эту операцию с использованием ORM Django.

Читайте также:  Кошелек биткоинов не работает

Фреймворк Djnago имеет три специальных класса для организации связей:

  • ForeignKey – для связей Many to One (поля отношений);
  • ManyToManyField – для связей Many to Many (многие ко многим);
  • OneToOneField – для связей One to One (один к одному).

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

А на данном занятии воспользуемся классом ForeignKey, который необходим для организации связей «многие к одному» или «одного ко многим» для отношений между постами и категориями. Почему именно этот тип связей? Дело в том, что каждой отдельной категории может соответствовать множество статей, или, наоборот, множеству статей – строго одна категория. Это и есть отношение Many to One.

Класс ForeignKey принимает два обязательных аргумента:

  • ссылка или строка класса модели, с которой происходит связывание (в нашем случае это класс Category – модели для категорий);
  • опция on_delete, накладывающая ограничения при удалении внешней записи (в нашем примере – это удаление из таблицы Category).

В свою очередь, опция on_delete может принимать следующие значения:

  • models.CASCADE – при удалении записи из первичной модели (у нас это таблица Category) происходит удаление всех записей из вторичной модели (Women), связанных с удаляемой категорией;
  • models.PROTECT – запрещает удаление записи из первичной модели, если она используется во вторичной (выдает исключение);
  • models.SET_NULL – при удалении записи первичной модели устанавливает значение foreign key в NULL у соответствующих записей вторичной модели;
  • models.SET_DEFAULT – то же самое, что и SET_NULL, только вместо значения NULL устанавливает значение по умолчанию, которое должно быть определено через класс ForeignKey;
  • models.SET() – то же самое, только устанавливает пользовательское значение;
  • models.DO_NOTHING – удаление записи в первичной модели не вызывает никаких действий у вторичных моделей.

Давайте, посмотрим на конкретном примере, как используется класс ForeignKey. Перейдем в файл women/models.py и определим еще одну модель для категорий:

Здесь все вам должно быть уже знакомо, кроме вот этого параметра db_index. Он указывает СУБД индексировать данное поле, чтобы поиск по нему происходил быстрее. В результате, в таблице category будет два индексируемых поля: id и name.

Далее, пропишем дополнительное поле cat_id во вторичной модели Women:

(суффикс _id Django добавит автоматически). Смотрите, мы класс первичной модели Category указали как строку, потому что модель Category в файле models.py записана после модели Women, поэтому, при попытке указать ссылку на этот класс, возникнет ошибка. Только поэтому класс указан как строка. Но вообще, можно записывать и так, и так. Следующий параметр мы определили через функцию PROTECT (это функция, а не константа), которая запрещает удаление категорий, используемых во вторичной модели.

Все, формально модели у нас определены и пришло время создать соответствующие таблицы в БД. Фактически, первую таблицу women нужно лишь модифицировать, добавив одно поле, а вторую таблицу создать целиком. Для этого нам надо сформировать новую миграцию командой:

python manage.py makemigrations

Но полноценно выполниться она не может. Я специально решил показать этот момент, чтобы вы лучше понимали процесс перестройки структуры таблиц в БД. Что здесь не так? Смотрите, у нас в таблице women должно добавиться новое поле cat_id, ссылающееся на запись первичной таблицы category. Но в таблице women уже есть записи и при добавлении этого поля оно оказывается для них пустым. СУБД запрещает такую операцию, так как это поле обязательно должно ссылаться на идентификатор записи из связанной таблицы. Отсюда и возникает эта проблема.

Как ее обойти? Давайте временно разрешим записывать в cat_id значение NULL. Это можно сделать через параметр null:

Завершим предыдущую миграцию (выберем 2) и снова запустим команду:

python manage.py makemigrations

Теперь все прошло в штатном режиме и у нас сформировался второй файл миграции под номером 0002. Выполним миграции, внесем изменения непосредственно в БД с помощью уже известной нам команды:

python manage.py migrate

Видим, что ошибок никаких нет. Откроем программу SQLiteStudio и видим две наши таблицы: women_category и women_women. Причем, в таблице women_women появилось новое поле cat_id со значениями NULL.

Вообще, такие операции по внесению изменений в структуры ранее созданных таблиц БД, крайний шаг. Рекомендуется в самом начале проектирования сайта хорошо продумывать всю организацию данных и связей, а потом только работать с уже имеющимися таблицами, не внося существенных изменений. То, что я сделал – это лишь учебный пример, как в принципе это можно сделать. Но лучше этого избегать.

Все, таблицы сформированы и давайте теперь добавим категории в новую таблицу. Перейдем в терминал, выполним команду:

python manage.py shell

импортируем наши модели в консоль фреймворка:

и создадим две записи в таблице category:

Затем, у всех записей таблицы women установим поле cat_id равным 1 (певицы). Сначала выбираем все записи:

Источник

Джанго «не удается добавить или обновить дочернюю строку: ограничение внешнего ключа не выполняется»

у меня есть модель Coupon модель Photo С ForeignKey для этого:

Я настроил inlines в admin, поэтому теперь я могу добавлять фотографии В купон от администратора.

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

что это такое и как я могу решить эту проблему?

(если это имеет значение, это база данных MySQL.)

EDIT: я пробовал в базе данных Sqlite3, которая имеет немного другой набор данных, и это сработало, поэтому, возможно, в моей текущей БД есть свободные данные? Как я могу найти его и удалить?

6 ответов:

некоторые из моих столов были в InnoDB, а некоторые были в MyISAM. Я изменил все на MyISAM и проблема была решена.

(согласно официальному документу) В предыдущих версиях Django светильники с прямыми ссылками (т. е. отношения к строкам, которые еще не были вставлены в базу данных) не смогли бы загружаться при использовании механизма хранения InnoDB. Это было связано с тем, что InnoDB отклоняется от стандарта SQL, проверяя ограничения внешнего ключа немедленно вместо того, чтобы откладывать проверку до тех пор, пока транзакция не будет зафиксирована. Эта проблема была решена в Django 1.4.

чтобы избежать этого, что вы также можете сделать, это установить ваш STORAGE_ENGINE в вашем settings.py

обратите внимание, что это действительно только для MySQL

я столкнулся с этой же проблемой: решение mmrs151 работает, но NB, что для Django (т. е. перед поддержкой нескольких баз данных), настройка выглядит следующим образом:

стоит отметить, что Ram Rachum, похоже, работал, а не решил проблему: MyISAM вообще не поддерживает транзакции.

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

2). Проверьте поток операций базы данных и сделайте его parent —> child

другой вариант, это отбросить ограничителем в таблицу MySQL:

Источник

Избегайте использования GenericForeignKey

В Django, GenericForeignKey является сущностью, которая позволяет модели быть связанной с любыми другими моделями, в отличие от ForeignKey , который позволяет связаться только с одной моделью.

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

Прежде чем я продолжу, я считаю что существует некоторые случаи, о которых я расскажу далее, где это не является проблемой. В частности, на ум приходит следующее:

  • общий аудит, при котором все изменения записей в БД отмечаются в отдельной таблице, для данного случая некоторые недостатки не так важны и могут быть даже преимуществами (можно ссылаться на удалённые записи);
  • общие приложения для тегов;
  • другие общие приложения для которых нет реальных альтернатив, потому что вы действительно не знаете какие модели, и даже сколько именно различных моделей, вам потребуется связывать.

Тем не менее, я считаю, что существуют множество ситуаций, которые не попадают в описанные выше условия, но люди продолжают использовать GenericForeignKey :

  • в случае, когда каждый объект модели требуется связать с одной и только одной записью в известном наборе другой модели;
  • в случае разработки общего приложения, в котором модель должна быть связана с одной другой моделью, пока не известной.

Большая часть статьи сфокусирована на первых случаях, но я также коротко рассмотрю вторую часть. Сначала, для упрощения взаимодействия, я представлю тестовое приложение.

Наше приложение управляет «задачами». Задачами могут «владеть» как отдельные лица, так и группы, но не одновременно. Вы можете использовать здесь GenericForeignKey , примерно так:

В этом случае, для владельца задачи есть два варианта, это для простоты, большая часть дальнейшего материала не ограничена этими вариантами.

Пожалуйста, обратите внимание на то, что вышеприведённый пример является тем, что я крайне не рекомендую делать! И вот почему:

Почему это плохо

Дизайн базы данных

При использовании GenericForeignKey схема базы данных неидеальна. Я слышал высказывания вида: «данные зреют как вино, а код приложения как рыба». Ваша БД, вероятно, переживёт приложение в своём текущем воплощении, таким образом, было бы неплохо, чтобы для понимания структуры данных не требовалось изучать исходный код приложения.

(Если это звучит не очень убедительно и вы всё ещё желаете дочитать этот раздел, вещи, объясняемые здесь, важны для понимания остальной части статьи).

В общем, правильные названия таблиц и столбцов (которые создаёт Django), внешних ключей (которые также создаёт Django) делают базу данных самодокументируемой. GenericForeignKey ломают этот механизм.

Для вышеприведённого примера ваша БД будет выглядеть так (используем SQLite синтаксис, так как я его использую в демонстрационном приложении):

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

Посмотрим на содержимое последней таблицы для моего демонстрационного приложения:

Рассмотрим, как в недалёком будущем некто, смотря в БД, пытается разобраться в логике работы:

  • Поле gfks_task.owner_type_id ссылается на запись в django_content_type (это очевидно из ограничения целостности).
  • Объединяя вместе app_label и model из этой записи через подчёркивание, мы можем получить имя таблицы, т.е. если gfks_task.owner_type_id == 8 , то требуется заглянуть в таблицу gfks_person ; (На самом деле это не так. Чтобы сделать всё правильно, мы на самом деле должны посмотреть в модель, т.е. надо импортировать gfks.models.Person и посмотреть в её атрибут ._meta.db_table . Это довольно противный маленький глюк, на который вы будете налетать, если этот атрибут был явно установлен для модели. Это означает, что у нас есть нехорошая зависимость от необходимости импортировать наше приложение, чтобы понять схему базы данных).
  • Теперь у нас есть имя таблицы, в которой мы должна поискать запись по имеющемуся первичному ключу в owner_id .

Есть несколько очевидных вещей, которые стоит прокомментировать:

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

Но самая большая проблема заключается в том, что схема базы данных больше не описывает ваши данные.

Ссылочная целостность

Более важной проблемой является ссылочная целостность, а именно, её отсутствие.

Возможно, это самая большая и наиболее важная проблема. Непротиворечивость и целостность данных в БД имеют самое важное значение, но используя GenericForeignKey , вы многое теряете по сравнению с использованием внешних ключей.

Из-за того, что owner_id является просто целым числом, значит может вообще не указывать на реальные данные. Такое может произойти при ручной правке поля или если поле ссылается на удалённую запись или ещё чего произошло. В общем то, от чего вас защищает сама БД, если вы используете обычные внешние ключи.

Производительность

Большой проблемой GenericForeignKey является производительность.

Для того, чтобы получить объект мы должны сделать множество запросов:

  1. Получить основной объект (в нашем случае, Task ).
  2. Получить объект ContentType , на который указывает Task.owner_type (эта таблица обычно кэшируется Django).
  3. Зная имя таблицы и идентификатор объекта из первого пункта, мы можем получить связанный объект.

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

Для начала, вы не можете использовать select_related , так как это потребует знания с какой таблицей следует объединяться. Использование prefetch_related также имеет некоторые ограничения. Например, вы можете делать:

Django пытается быть умной в данном случае и уменьшит количество запросов к БД насколько это возможно. Однако, если вам потребуется сделать это:

тогда вы получите исключение, потому что только Group имеет атрибут creator , у Person такого атрибута нет.

Код Django

В дополнение к вышеприведённому, из моего опыта, использование GenericForeignKey в общем случае ухудшает ваш Django код. Можно соблазниться возможностью иметь единый атрибут Task.owner , который ведёт себя полиморфно, но иллюзии быстро пройдут.

Во-первых, фильтрация через Django ORM работает плохо. ORM не может объединить запрос с нужной таблицей, перекладывая на вас тяготы работы с фильтрацией на уровне БД.

Например, если вам требуется получить задачи, назначенные только на группы и затем отфильтровать их, вы не можете просто сделать:

Вместо этого, вам потребуется сделать:

Существует множество других эффективных вариантов, но от вас потребуется желание запустить руки в непростую сферу ручного создания SQL запросов.

Во-вторых, полиморфный объект редко работает так хорошо, как об этом рассказывают. По моему опыту, вам часто придётся использовать в коде ветвление:

… либо в коде на Python, или в ваших шаблонах, что уже нельзя назвать приятным. Особенно это проявляется в случаях, когда требуется связываться с моделями, которые находятся вне вашего контроля, поэтому достаточно непросто организовать им единый интерфейс.

Следствием дизайна GenericForeignKey является неудобство их использования, что также отражается на уровне поддержки, который им обеспечивается со стороны других компонентов Django:

Удаление

По умолчанию, если вы удаляете Group или Person (целевой объект), не имеет значения через интерфейс администратора или из кода, связанный объект не будет удалён или изменён. Интерфейс администратора не отслеживает связи через GenericForeignKey , которые могут вести на удаляемый объект. Вас просто оставят со сломанными данными.

Однако, вы можете добавить обощённые связи к моделям Group и Person , которые починят ORM и интерфейс администратора в части удаления. Но следует отметить, что это не делается по умолчанию и выполняется попытка выполнить действие на уровне приложения, что на самом деле могло бы выполняться на уровне БД при использовании внешних ключей.

Интерфейс администратора

Для GenericForeignKey поля, интерфейс администратора покажет вам только то, что вы могли бы ожидать для полей owner_id и owner_type_id — поле для целого числа и выпадашку для выбора типа контента, что не очень то и полезно. Естественно, вы можете свободно менять значение целого числа, имея возможность испортить данные. Существует несколько сторонних попыток получить более лучший интерфейс, см. http://stackoverflow.com/questions/13907211/genericforeignkey-and-admin-in-django

Как упоминалось ранее, объекты, связанные через GenericForeignKey (по умолчанию) не включаются в логику «найти и показать объекты для удаления» страницы удаления интерфейса администратора Django.

Так же есть ряд других фишек, GenericForeignKey плохо работают с фильтрами списков на интерфейсе администратора, вам потребуется писать дополнительный код для поддержки фильтрации, и они работают не очень хорошо с модельными формами. Вам потребуется патчить достаточное количество кода интерфейса администратора.

Альтернативы

Надеюсь, я был достаточно убедителен в необходимости поиска другого решения, давайте рассмотрим некоторые доступные решения.

Альтернатива 1 — NULL поля в исходной таблице

Модель выглядит так:

Возможно, это самое простое решение. Вам потребуется выполнять проверки на None при работе с полями owner_group и owner_person , которые вы можете обернуть как показано далее, если вам требуется некоторый полиморфизм в поведении:

Аналогично вы также можете проверять, что одно и только одно из двух полей было заполнено при сохранении.

Такой подход имеет недостатки на уровне схемы, пока вы не добавите дополнительное ограничение, так как существует возможность того, что Owner указывает на Person и Group одновременно, что не соответствует задаче. Но это несравнимо с проблемами GenericForeignKey .

Альтернатива 2 — промежуточная таблица с NULL полями

В данном примере мы перемещаем пустые внешние ключи в отдельную таблицу, где они становятся O2O полями и создаём непустой внешний ключ в основной таблице. Например:

Такой подход имеет ряд достоинств, мы сделали Owner абстракцией. При необходимости полиморфного использования Task.owner , у вас есть место для размещения логики, которая будет знать как следует различать Person и Group , не добавляя эту логику в указанные модели, что особенно полезно в случае когда вы не владеете этими моделями или когда вам надо хранить логику отдельно. У нас также появляется единственное место, которое документирует все сущности, которые могут стать «владельцами».

Далее, если вам потребуются другие сущности, которые будут использовать то же определение Owner , это будет несложно реализовать, просто добавляем ещё один внешний ключ в Owner , что гораздо выгоднее по сравнению с первым подходом.

Данный поход всё ещё имеет недостатки в виде пустых внешних ключей, но наличие модели Owner , которая работает с ними, делает код чище.

Также существует ряд других недостатков, по сравнению с предыдущим решением:

  • У нас появляется дополнительная таблица, увеличивая количество объединений, когда нам требуется получить все данные одним запросом.
  • Нам требуется проверять, что запись в Owner существует для каждой группы/персоны, с которыми требуется выполнить связь. Это требует создания такой записи при создании группы/персоны, или позже. Также, поиск правильного значения для поля Task.owner требует больше действий по сравнению с предыдущим решением, на уровне кода и таких сущностей как интерфейс администратора.

Альтернатива 3 — промежуточная таблица с O2O полями на целевые модели

Берём предыдущее решение и выносим O2O поля в отдельную таблицу, т.е.в целевые модели. Такой подход позволяет избавиться от пустых полей.

Отметим различия с предыдущим подходом:

  • У нас больше нет внешних ключей с NULL значениями.
  • Однако, нам по прежнему требуется создавать записи в Owner при создании Group или Person . В дополнение, эти записи могут быть никогда не использованы, т.е. группа может никогда не использоваться в как владелец.
  • Данный подход требует внесения изменений в модели Person и Group .
  • Для некоторых случаев использования приходится делать больше запросов (например, при запуске задачи и необходимости узнать тип её владельца, потребуется выполнить больше запросов по сравнению с предыдущим подходом).

Альтернатива 4 — наследование нескольких таблиц

Если вы знаете о наследовании таблиц, вы можете увидеть, что предыдущий подход мог быть создан с использованием меньшего объёма кода. Вместо явного назначения O2O на Owner мы могли унаследовать Person и Group от модели Owner .

Такое решение приведёт к созданию схемы БД подобной предыдущему варианту — Django добавить O2O связи самостоятельно. Несмотря на различие в именах полей, следует отметить то, что поле owner также будет использовано в качестве первичного ключа (что можно было сделать вручную в предыдущем подходе, при необходимости).

На уровне кода, всё очень похоже на предыдущий подход, хотя и проще, так как вам не требуется вручную создавать записи в Owner . В дополнение, теперь вы получаете полиморфизм бесплатно, так как Person унаследован от Owner , он также наследует и его поведение.

Лично я не рекомендую использовать наследование таблиц. Одной причиной этого является моё беспокойство насчёт сложности механизма наследования, используемого в Django, другой причиной являются проблемы с производительностью — наличие O2O связей явно указывает на необходимость объединений, что влияет на производительность (имхо, автор бредит, прим. переводчика). И, наконец, Django не поддерживает множественное наследование, в то время, как предыдущий подход можно использовать столько раз, сколько нужно. Тем не менее, для полноты картины, я добавил этот подход в статью:

Следует отметить, что это прямое наследование моделей, вы не можете использовать abstract = True в модели `Owner.

Заменяемые модели

Наконец, иногда требуется реализовать связь с единственной, но пока неизвестной моделью (например, в общем стороннем приложении), для чего GenericForeignKey кажется предпочтительным выбором.

В этом случае можно использовать два подхода:

Источник

Оцените статью