Как переименовать приложение в Django

Aidas Bendoraitis, автор книги «Django 3 Web Development Cookbook», рассказал, как переименовать приложение в Django.


Когда я начал работу над MVP (минимально жизнеспособным продуктом) моего проекта 1st things 1st, я сперва думал, что этот Django-проект будет посвящен приоритизации. Несколько лет спустя я понял, что этот проект по сути SaaS (software as a service, ПО как услуга), а приоритизация — лишь часть необходимого функционала для SaaS. В конечном итоге мне понадобилось переименовать приложение, чтобы получить более чистый и лучше организованный код. Вот как я это сделал.

0. Обновите ваш код и кодовую базу

Убедитесь, что вы сделали git pull последних изменений и выполнили все миграции базы данных.

[python_ad_block]

1. Установите django-rename-app

Поместите django-rename-app в pip requirements и установите его. Либо просто запустите следующую команду:

(venv)$ pip install django-rename-app

Внесите это приложение в INSTALLED_APPS в ваших настройках:

INSTALLED_APPS = [
    # …
    "django_rename_app",
]

2. Переименуйте директории приложения

Смените старое имя (oldapp) на новое (newapp) в ваших приложениях и шаблонах.

3. Измените имя приложения там, где оно встречается в коде

Измените имя во всех ваших импортах, миграциях и путях шаблонов.

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

4. Запустите управляющую команду rename_app

Выполните следующую команду:

(env)$ python manage.py rename_app oldapp newapp

Эта команда заменит префикс имени в таблицах приложения и записях в таблицах django_content_type и django_migrations.

Если планируете обновить сервера стейджинга и продакшена, добавьте команду rename_app перед запуском миграций в своих скриптах деплоймента (Ansible, Docker и т.п..).

5. Обновите индексы и ограничения

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

(env)$ python manage.py makemigrations newapp --empty --name rename_indexes

Заполните миграцию следующим кодом:

# newapp/migrations/0002_rename_indexes.py
from django.db import migrations


def named_tuple_fetch_all(cursor):
    "Return all rows from a cursor as a namedtuple"
    from collections import namedtuple

    desc = cursor.description
    Result = namedtuple("Result", [col[0] for col in desc])
    return [Result(*row) for row in cursor.fetchall()]


def rename_indexes(apps, schema_editor):
    from django.db import connection

    with connection.cursor() as cursor:
        cursor.execute(
            """SELECT indexname FROM pg_indexes 
            WHERE tablename LIKE 'newapp%'"""
        )
        for result in named_tuple_fetch_all(cursor):
            old_index_name = result.indexname
            new_index_name = old_index_name.replace(
                "oldapp_", "newapp_", 1
            )
            cursor.execute(
                f"""ALTER INDEX IF EXISTS {old_index_name} 
                RENAME TO {new_index_name}"""
            )


def rename_foreignkeys(apps, schema_editor):
    from django.db import connection

    with connection.cursor() as cursor:
        cursor.execute(
            """SELECT table_name, constraint_name 
            FROM information_schema.key_column_usage
            WHERE constraint_catalog=CURRENT_CATALOG 
            AND table_name LIKE 'newapp%'
            AND position_in_unique_constraint notnull"""
        )
        for result in named_tuple_fetch_all(cursor):
            table_name = result.table_name
            old_foreignkey_name = result.constraint_name
            new_foreignkey_name = old_foreignkey_name.replace(
                "oldapp_", "newapp_", 1
            )
            cursor.execute(
                f"""ALTER TABLE {table_name} 
                RENAME CONSTRAINT {old_foreignkey_name} 
                TO {new_foreignkey_name}"""
            )


class Migration(migrations.Migration):

    dependencies = [
        ("newapp", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(rename_indexes, migrations.RunPython.noop),
        migrations.RunPython(rename_foreignkeys, migrations.RunPython.noop),
    ]

Запустите миграции:

(env)$ python manage.py migrate

Если что-то не работает так, как ожидалось, мигрируйте обратно, исправьте код и затем снова мигрируйте. Отменить миграцию можно путем миграции на один шаг до последней миграции. Например:

(env)$ python manage.py migrate 0001

6. Уборка

После применения миграции во всех необходимых окружениях можно подчистить их путем удаления django-rename-app из pip requirements и скриптов деплоймента.

Итоги

Редко удается построить систему, которая изначально будет отвечать всем вашим нуждам. Системы всегда требуют постоянного улучшения и рефакторинга. Используя миграции Django и django-rename-app, можно работать над сайтами в стиле Agile, чисто и гибко.

Перевод статьи «How to Rename a Django App».