Evgenii Legotckoi
24 апреля 2019 г. 13:42

Django - Урок 045. Перемещение моделей из одного приложения в другое

Я хотел бы поделиться одним из возможных способов переноса модели данных из одного приложения в другое.

Сразу отмечу, что этот вариант переноса модели данных не на 100% рабочий и может потребоваться дополнительная ручная правка таблиц для корректной установки Content Type. Так как любые подобные модификации чреваты потерей данных для отношений GenericForeignKey.

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


Исходные данные

Предположим, у вас есть старая статья article с моделью Article. Вам нужно создать приложение для блога и перенести модель статьи в это приложение. Использует базу данных PostgreSQL.

Перемещение модели

  1. Сделайте резервную копию базы (backup), можете сделать дамп базы и потренироваться на этом дампе на сервере разработки.

  2. Переместите код модели Article из article.models.py в blog.models.py . То есть код от старой модели надо убрать.

  3. Измените класс Meta в модели Article , чтобы сохранить старое имя таблицы. В данном случае это была article_artilce

    1. class Meta:
    2. db_table = 'article_article'

  4. Изменить все ссылки на модели данных во всем проекте. Это означает изменение всего импорта и ForeignKey .

  5. Создайте миграции для обоих приложений в следующем порядке.

    1. python manage.py makemigrations blog
    2. python manage.py makemigrations article

  6. Применить миграцию в поддельном режиме. Это позволит вам добавлять миграции по мере их применения, но никаких изменений вноситься не будет.

    1. python manage.py migrate --fake

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

  7. Это можно исправить следующими действиями. Теперь нам нужно изменить имя таблицы, и это можно сделать, удалив db_table из модели данных и создав и применив новую миграцию к новому приложению. Тип контента будет создан автоматически при применении миграции.

    1. python manage.py makemigrations blog
    2. python manage.py migrate

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

Очистка и корректировка базы данных

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

У меня был самый простой случай — это удаление старого Content Type . Для этого нужно подключиться к базе данных и удалить информацию из всех таблиц, которые могут ссылаться на Content Type старой модели.

Такие таблицы могут быть:

  • auth_user_user_permissions
  • auth_permission
  • django_content_type
  • и т.д. другие модели, в которых можно было использовать старые модели до передачи

Я покажу пример старого Content Type из моей базы данных.

Процесс очистки

  1. Подключиться к базе данных

    1. sudo -u postgres psql
    2. \c myprojectdb

  2. Давайте посмотрим на таблицу django_content_type, чтобы увидеть, какие идентификаторы имеют устаревший тип контента.

    1. SELECT * FROM django_content_type;

    Table output
    1. id | app_label | model
    2. ----+------------------+-----------------------
    3. 1 | accounts | userprofile
    4. 2 | knowledge | section
    5. 3 | knowledge | article
    6. 4 | admin | logentry
    7. 5 | auth | permission
    8. 6 | auth | group
    9. 7 | auth | user
    10. 8 | contenttypes | contenttype
    11. 9 | sessions | session

  3. Найдя старые id мы можем попробовать их удалить (например 50 и 51)

    1. DELETE FROM django_content_type WHERE id BETWEEN 50 AND 51;

  4. Если не получилось, то нужно найти, где используются данные ID. В ошибке будет указано, в каких таблицах они задействованы. В моем случае это были auth_permission и auth_user_user_permissions.

    1. SELECT * FROM auth_permission WHERE content_type_id BETWEEN 50 AND 51;

    Вывод таблицы
    1. id | name | content_type_id | codename
    2. -----+--------------------+-----------------+----------------
    3. 154 | Can add chat | 50 | add_chat
    4. 155 | Can change chat | 50 | change_chat
    5. 156 | Can delete chat | 50 | delete_chat
    6. 157 | Can add message | 51 | add_message
    7. 158 | Can change message | 51 | change_message
    8. 159 | Can delete message | 51 | delete_message
    9. 276 | Can view Chat | 50 | view_chat
    10. 277 | Can view Message | 51 | view_message

    Далее ищем интересующий id в auth_user_user_permissions
    1. SELECT * FROM auth_user_user_permissions WHERE permission_id=154;

    Вывод таблицы
    1. id | user_id | permission_id
    2. -----+---------+---------------
    3. 102 | 2 | 154

  5. Убираем из таблицы auth_user_user_permissions все разрешения для старого Content Type.

    1. DELETE FROM auth_user_user_permissions WHERE permission_id=154;

  6. Убираем разрешение из таблицы auth_permission.

    1. DELETE FROM auth_permission WHERE content_type_id BETWEEN 50 AND 51;

  7. Удалить типы контента.

    1. DELETE FROM django_content_type WHERE id BETWEEN 50 AND 51;

Вывод

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

For Django, I recommend Timeweb VDS-server .

Вам это нравится? Поделитесь в социальных сетях!

Макар Карабасов
  • 2 июля 2019 г. 3:45

Дружище, ну подскажи. Совершенно не понял 7-й пункт. А именно, а что происходит с данными, которые в таблице? Или всё делалось, когда в таблице нет данных?

Дело в том, что у меня как раз похожая ситуация. Только нужно перенести три модели, в другое приложение. Понимаю, что всё начинается с одной. Но вот незадача. Все эти таблицы уже заполнены. И связь с этими данными уже достаточно прочная (по факту нужно перенесети каталог, из данных которого уже полно действующих заказов).

И вот мне не ясен такой момент. Вот была article_article. Я из META убираю строчку. Делаю makemigrations и migrate. Так и что происходит с таблицей? Она становиться новенькой, или просто переименовывается?

Я понимаю, что бэкапы и всё такое, но как-то честно сказать очково. Буду потом плясать с этим бэкапом.

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

Суть в том, что старая модель данных переносится в другое приложение, но с помощью db_table = 'article_article' старая таблица сохраняется.

Когда удаляется article_article то в миграции создаётся новое имя таблицы. То есть старая таблица переименовывается, а данные в ней сохраняются.

При этом в шаге 3 таблица условно была удалена из старого приложения, за счёт fake миграции. То есть информация о миграции добавилась, но при этом изменений не произошло.

А вот шаг 7 как раз переименовывает таблицу, чтобы она фактически лежала в новом приложении. Так что ДА - она переименовывается с сохранением данных.

Макар Карабасов
  • 2 июля 2019 г. 14:56

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

Сам долго колупал форумы, чтобы найти полноценное решение, как это сделать и ничего не поломать.

Иногда там ещё бывают циклические зависимости - вот это реально проблема. Но в итоге тоже нашёл решение через сброс всех миграций и создание одной initial миграции и применение её через fake. Подробнее в этой статье .

bernar92
  • 9 ноября 2021 г. 11:46
  • (ред.)

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

  1. operations = [
  2. migrations.RunSQL('''
  3. ALTER TABLE IF EXISTS credits_operatorcreditapplication RENAME TO operator_creditapplication;
  4. create table credits_operatorcreditapplication();
  5.  
  6. delete from auth_group_permissions
  7. USING (select *
  8. from auth_permission
  9. join django_content_type dct on dct.id = auth_permission.content_type_id
  10. ) dct
  11. where dct.app_label='credits' and dct.model='operatorcreditapplication';
  12.  
  13. DELETE FROM auth_permission
  14. USING django_content_type
  15. where django_content_type.app_label='credits' and django_content_type.model='operatorcreditapplication';
  16.  
  17. DELETE FROM django_admin_log
  18. USING django_content_type
  19. where django_content_type.app_label='credits' and django_content_type.model='operatorcreditapplication';
  20.  
  21. DELETE FROM django_content_type WHERE app_label='credits' and model='operatorcreditapplication';
  22. '''),
  23. migrations.DeleteModel(
  24. name='OperatorCreditApplication',
  25. ),
  26. ]
  27.  
  28.  
  29.  
  30. operations = [
  31. migrations.CreateModel(
  32. name='OperatorCreditApplication',
  33. fields=[
  34. ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
  35. ('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Время создания')),
  36. ('changed_at', models.DateTimeField(auto_now=True, db_index=True, verbose_name='Время последнего изменения'))
  37. ],
  38. options={
  39. 'verbose_name': '....',
  40. 'verbose_name_plural': '...',
  41. 'ordering': ('created_at'),
  42. },
  43. ),
  44. migrations.RunSQL('''
  45. DROP TABLE IF EXISTS scoring_operatorcreditapplication;
  46. ALTER TABLE IF EXISTS operator_creditapplication RENAME TO scoring_operatorcreditapplication;
  47. ''')
  48.  
  49. ]

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь