Курс по Django: https://stepik.org/a/183363
Архив проекта: 25_sitewomen.zip
На этом занятии
воспользуемся классом ForeignKey, который необходим для
организации связей «многие к одному» для отношений между постами и категориями,
чтобы каждый пост был связан с одной конкретной категорией, а каждая категория со
множеством статей. То есть, здесь слово «многие» относятся к категориям, а
слово «один» - к постам. При этом модель category называют первичной,
а модель women – вторичной.
Класс ForeignKey принимает два
обязательных аргумента:
- to – ссылка или
строка класса первичной модели, с которой происходит связывание (в нашем случае
это класс Category – модели для
категорий);
- on_delete – определяет тип
ограничения при удалении внешней записи (в нашем примере – это удаление из первичной
таблицы Category).
В свою очередь,
опция on_delete может принимать
следующие значения:
- models.CASCADE – удаление всех
записей из вторичной модели (например, Women), связанных с
удаляемой категорией;
- models.PROTECT – запрещает
удаление записи из первичной модели, если она используется во вторичной (выдает
исключение);
- models.SET_NULL – при удалении
записи первичной модели (Category) устанавливает значение foreign key в NULL у
соответствующих записей вторичной модели (Women);
- models.SET_DEFAULT – то же самое,
что и SET_NULL, только вместо
значения NULL устанавливает
значение по умолчанию;
- models.SET() – то же
самое, только устанавливает пользовательское значение;
- models.DO_NOTHING – удаление
записи в первичной модели (Category) не вызывает никаких действий у
вторичных моделей.
Подробнее о них
можно почитать на странице документации:
https://docs.djangoproject.com/en/4.2/ref/models/fields/#arguments
Давайте,
посмотрим на конкретном примере, как используется класс ForeignKey. Перейдем в
файл women/models.py и определим еще
одну модель для категорий:
class Category(models.Model):
name = models.CharField(max_length=100, db_index=True)
slug = models.SlugField(max_length=255, unique=True, db_index=True)
def __str__(self):
return self.name
Здесь все вам
должно быть уже знакомо. Напомню, что параметр db_index указывает СУБД
индексировать данное поле, чтобы поиск по нему происходил быстрее.
Далее, пропишем
дополнительное поле cat_id во вторичной модели Women:
cat = models.ForeignKey('Category', on_delete=models.PROTECT)
(суффикс _id для формирования
поля cat_id Django добавит
автоматически).
Обратите
внимание, мы класс первичной модели Category указали как
строку, потому что модель Category в файле models.py записана после
модели Women, поэтому, при
попытке указать ссылку на этот класс возникнет ошибка. Только поэтому класс
указан как строка. Но вообще, можно записывать и так, и так. Следующий параметр
мы определили через функцию PROTECT (это ссылка на функцию, а не
константа), которая запрещает удаление категорий, используемых во вторичной
модели Women.
Создание таблиц в базе данных
Все, формально
модели у нас определены и пришло время создать соответствующие таблицы в БД.
Фактически, первую таблицу women нужно лишь модифицировать, добавив одно
поле, а вторую таблицу создать целиком. Для этого нам надо сформировать новую
миграцию командой:
python manage.py makemigrations
Но полноценно
выполниться она не сможет. Я специально решил показать этот момент, чтобы вы
лучше понимали процесс перестройки структуры таблиц в БД. Что здесь не так? Смотрите,
у нас в таблице women должно добавиться новое поле cat_id, ссылающееся на
запись первичной таблицы category. Но в таблице women уже есть записи
и при добавлении этого поля оно оказывается для них пустым. СУБД запрещает
такую операцию, так как это поле обязательно должно ссылаться на идентификатор
записи из связанной таблицы. Отсюда и возникает эта проблема.
Как ее обойти?
Давайте временно разрешим записывать в cat_id значение NULL. Это можно сделать через параметр null:
cat = models.ForeignKey('Category', on_delete=models.PROTECT, null=True)
Завершим
предыдущую миграцию (выберем 2) и снова запустим команду:
python manage.py makemigrations
Теперь все
прошло в штатном режиме и у нас сформировался еще один файл миграции под
номером 0005. Выполним миграции, внесем изменения непосредственно в БД с
помощью уже известной нам команды:
python manage.py migrate
Видим, что
ошибок никаких нет. Откроем программу SQLiteStudio и видим две
наши таблицы: women_category и women_women. Причем, в
таблице women_women появилось новое
поле cat_id со значениями NULL.
Все, таблицы
сформированы и давайте теперь добавим категории в новую таблицу. Перейдем в
терминал, выполним команду:
python manage.py
shell_plus
и создадим две
записи в таблице category (в оболочке shell_plus модели
импортируются автоматически):
Category.objects.create(name='Актрисы', slug='aktrisy')
Category.objects.create(name='Певицы', slug='pevicy')
Затем, у всех
записей таблицы women установим поле cat_id равным 1
(певицы). Сначала выбираем все записи:
w_list = Women.objects.all()
и обновляем их:
Теперь можно
выйти из консоли, убрать аргумент null=True у класса ForeignKey и снова создать
миграции:
python manage.py makemigrations
В появившемся
меню выберем пункт 2 и миграция будет создана. Применим ее:
python manage.py migrate
Все, теперь
внешний ключ cat_id у нас не может
быть пустым.
Работа параметра models.PROTECT
Давайте убедимся
в том, что мы не сможем удалить рубрики из первичной модели Category, если они
используются во вторичной модели Women. Для этого снова перейдем в оболочку:
python manage.py
shell_plus
и выберем запись
с id равным 1, так
как именно эта категория сейчас используется в постах:
c = Category.objects.get(pk=1)
И попробуем ее
удалить:
Получим ошибку
ProtectedError, как раз из-за значения models.PROTECT параметра on_delete. А вот
категорию с id=2 удалить
можно. Прежде я сохраню отдельно файл БД, чтобы потом быстро ее восстановить в
исходном виде и выполню команды:
c = Category.objects.get(pk=2)
c.delete()
В этом случае
никаких ошибок не возникло и запись из таблицы Category была успешно
удалена.
Работа параметра models.CASCADE
Давайте еще
посмотрим на порядок работы параметра models.CASCADE. Выйдем из
оболочки. Пропишем новое значение для on_delete:
cat = models.ForeignKey('Category', on_delete=models.CASCADE)
Снова перейдем в
оболочку и выполним команды:
c = Category.objects.get(pk=1)
c.delete()
Никаких ошибок
не произошло и при удалении первой категории из таблицы Category были удалены
также все записи из таблицы Women, связанные с этой первой категорией (то
есть, все).
Вот принцип
работы двух параметров PROTECT и CASCADE. Я думаю,
теперь вам не сложно будет понять работу всех остальных параметров.
Курс по Django: https://stepik.org/a/183363