Как переключиться на пользовательскую модель User в существующем проекте

Документация по Django рекомендует всегда начинать ваш проект с пользовательской модели User (даже если она идентична Django с самого начала), чтобы упростить настройку позже, если вам нужно. Но что делать, если вы не видели этого при запуске проекта, или если вы унаследовали проект без пользовательской модели User, и вам нужно добавить ее?

Предпосылки

На момент написания этой статьи в трекере Django открыта заявка #25313 для добавления дополнительной документации по этой проблеме. Этот тикет включает в себя некоторые шаги высокого уровня, которые необходимо выполнить при переходе к пользовательской модели User, и я рекомендую сначала ознакомиться с ним. Как отмечено в документации в разделе «Переход к пользовательской модели в середине проекта», «изменение AUTH_USER_MODEL после создания таблиц базы данных значительно сложнее, поскольку, например, оно влияет на внешние ключи и отношения "многие ко многим"».

Инструкции, которые я собрал ниже, несколько отличаются от инструкций высокого уровня в тикете #25313, я думаю (надеюсь) позитивными и менее разрушительными способами. Тем не менее, есть причина, по которой этот тикет был открыт более четырех лет - это сложно. Итак, как указано в тикете:

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

Обзор

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

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

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

Процесс миграции

Вот мой подход к переходу на пользовательскую модель в середине проекта:

  1. Предположения:

    • У вас есть существующий проект без пользовательской модели User.
    • Вы используете миграции Django, и все миграции актуальны (и были применены к производственной базе данных).
    • У вас есть существующий набор пользователей, который вам нужно сохранить, и любое количество моделей, которые указывают на встроенную модель пользователя Django.
  2. Во-первых, проверьте любые сторонние приложения, чтобы убедиться, что они не имеют никаких ссылок на модель User в Django, или если они имеют, что они используют общие методы Django для ссылки на пользовательскую модель.

  3. Затем сделайте то же самое для своего собственного проекта. Просмотрите код и найдите любые ссылки, которые могут у вас быть на модель User, и замените их теми же общими ссылками. Короче говоря, вы можете использовать метод get_user_model(), чтобы получить модель напрямую, или, если вам нужно создать ForeignKey или другую связь базы данных с моделью пользователя, используйте settings.AUTH_USER_MODEL (которая представляет собой просто строку, соответствующую appname.ModelName — путь к пользовательской модели).

    Обратите внимание, что get_user_model() нельзя вызывать на уровне модуля ни в одном файле models.py (и, соответственно, в любом файле, который импортирует models.py), так как в результате вы получите циклический импорт. Как правило, проще по возможности поддерживать вызовы get_user_model() внутри метода (поэтому он вызывается во время выполнения, а не во время загрузки) и использовать settings.AUTH_USER_MODEL во всех других случаях. Это не всегда возможно (например, при создании ModelForm), но чем меньше вы используете его на уровне модуля, тем меньше циклических импортов вам придется запутаться.

  4. Создайте новое приложение users (или дайте ему другое имя по вашему выбору, например, accounts). При желании вы можете использовать существующее приложение, но оно должно быть приложением без какой-либо ранее существовавшей истории миграции, потому что, как отмечено в документации Django, «из-за ограничений функции динамической зависимости Django для сменных моделей модель, на которую ссылается AUTH_USER_MODEL, должна быть создана при первой миграции ее приложения (обычно это называется 0001_initial), в противном случае у вас будут проблемы с зависимостями."

    python manage.py startapp users
    
  5. Добавьте новую модель User в users/models.py с db_table, которая позволит использовать ту же таблицу базы данных, что и существующая модель auth.User. Для простоты при последующем обновлении типов контента (и если вы хотите, чтобы имена таблиц «многие ко многим» в базовой схеме базы данных соответствовали названию вашей пользовательской модели), вам следует назвать его User, как я это сделал здесь. Вы можете переименовать его позже, если хотите.

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    
    
    class User(AbstractUser):
        class Meta:
            db_table = 'auth_user'
    
  6. Для удобства, если вы хотите по мере необходимости проверять модель пользователя через администратора, добавьте запись для нее в users/admin.py:

    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin
    
    from .models import User
    
    
    admin.site.register(User, UserAdmin)
    
  7. В settings.py, добавьте users в INSTALLED_APPS и укажите AUTH_USER_MODEL = 'users.User':

    INSTALLED_APPS = [
        # ...
        'users',
    ]
    
    AUTH_USER_MODEL = 'users.User'
    
  8. Создайте начальную миграцию для вашей новой модели User:

    python manage.py makemigrations
    

    В итоге вы должны получить новый файл миграции users/migrations/0001_initial.py.

  9. Поскольку таблица auth_user уже существует, обычно в этой ситуации мы подделываем эту миграцию с помощью команды python manage.py migrate users --fake-initial. Однако, если вы попытаетесь выполнить это сейчас, вы получите ошибку InconsistentMigrationHistory, потому что Django выполняет проверку работоспособности перед фальсификацией миграции, которая предотвращает ее применение. В частности, он не позволяет имитировать эту миграцию, потому что другие миграции, которые зависят от нее, то есть любые миграции, которые включают ссылки на settings.AUTH_USER_MODEL, уже были выполнены. Я не совсем уверен, почему Django накладывает это ограничение на фальшивые миграции, поскольку весь смысл состоит в том, чтобы сказать, что миграция, по сути, уже была применена. Вместо этого вы можете добиться того же результата, добавив начальную миграцию для вашего нового пользовательского приложения в историю миграции вручную:

    echo "INSERT INTO django_migrations (app, name, applied) VALUES ('users', '0001_initial', CURRENT_TIMESTAMP);" | python manage.py dbshell
    

    Если вы используете имя приложения, отличное от users, замените users в строке выше именем приложения Django, которое содержит вашу пользовательскую модель.

    В то же время давайте обновим таблицу django_content_types новым app_label для нашей пользовательской модели, чтобы существующие ссылки на этот тип контента не изменились. Как и в случае с предыдущим изменением базы данных, это изменение должно быть сделано перед запуском миграции. Причиной этого является то, что миграция создаст любые несуществующие типы контента, что затем не позволит вам обновить старый тип контента с помощью новой метки приложения (с ошибкой «значение ключа-дубликата нарушает уникальное ограничение»).

    echo "UPDATE django_content_type SET app_label = 'users' WHERE app_label = 'auth' and model = 'user';" | python manage.py dbshell
    

    Опять же, если вы назвали свое приложение по другому, обязательно обновите SET app_label = 'users' в приведенном выше названии выбранного вами приложения.

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

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

  11. После тестирования и исправления любых ошибок все до этого момента должно быть развернуто в рабочей среде (и/или в любых других средах, где вам необходимо сохранить существующую пользовательскую базу данных), после того, как вы убедитесь, что у вас есть хорошая резервная копия и процесс ее восстановления, если что-то пойдет не так.

  12. Теперь вы сможете вносить изменения в users.User и запускать makemigrations/migrate по мере необходимости. Например, в качестве первого шага вы можете переименовать таблицу auth_user во что-то в пространстве имен вашего пользовательского приложения. Вы можете сделать это, удалив db_table из вашей модели User, это выглядит так:

    class User(AbstractUser):
        pass
    

    Вам также нужно будет создать и запустить новую миграцию, чтобы внести эти изменения в базу данных:

    python manage.py makemigrations --name rename_user_table
    python manage.py migrate
    

Успешно?

Это должно быть так. Теперь вы сможете вносить другие изменения (и создавать миграции для этих изменений) в свою пользовательскую модель User. Типы изменений, которые вы можете вносить, и способы их выполнения выходят за рамки этого поста, поэтому я рекомендую внимательно прочитать документацию Django о замене пользовательской модели User. В случае, если вы решите переключиться с AbstractUser на AbstractBaseUser, обязательно создайте миграцию данных для любого из полей, предоставляемых AbstractUser, которые вы хотите сохранить, прежде чем удалять эти столбцы из вашей базы данных. 

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

Перевод https://www.caktusgroup.com/blog/2019/04/26/how-switch-custom-django-user-model-mid-project/

Вернуться на верх