Расширение модели пользователя в Django: сравнение нескольких стратегий с примерами кода

Встроенная система аутентификации Django очень хороша и безопасна. Ее можно использовать, не меняя ни строчки кода, что экономит силы на разработку и тестирование. Стандартной функциональности хватает для большинства случаев.

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

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

Способы расширения существующей модели пользователей

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

  • использование прокси-модели;
  • использование связи один-к-одному с пользовательской моделью;
  • создание модели пользователя с помощью расширения класса AbstractBaseUser;
  • создание модели пользователя с помощью расширения класса AbstractUser.

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

Использование прокси-модели

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

Когда следует использовать прокси-модель?

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

Это то, что мне нужно! Перейти к коду!

Использование связи один-к-одному с пользовательской моделью

В этом случае создается обычная модель Django, у которой будет собственная таблица базы данных и которая будет одна-к-одной связана с существующей моделью пользователя через OneToOneField.

Когда следует использовать связи один-к-одному?

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

Это то, что мне нужно! Перейти к коду!

Создание модели пользователя через расширение AbstractBaseUser

Это совершенно новая модель пользователя, которая наследуется от AbstractBaseUser. Ее грамотная интеграция требует дополнительных усилий и обновления некоторых связей через settings.py. При выборе этого варианта настоятельно рекомендуется провести все манипуляции с моделями до начала работы над проектом, так как это повлияет на всю схему базы данных. Использование этого способа в готовом проекте может вызвать проблемы при внедрении новой модели.

Когда следует использовать этот способ?

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

Это то, что мне нужно! Перейти к коду!

Создание модели пользователя через расширение AbstractUser

Этот способ также подразумевает создание новой модели пользователя, но которая наследуется уже от AbstractUser. Здесь актуальны все те же замечания, что и для пункта выше: необходимость дополнительных усилий для внедрения и обновления некоторых связей через settings.py, сложности при интеграции в готовый проект.

Когда следует использовать этот способ?

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

Это то, что мне нужно! Перейти к коду!

Расширение модели пользователя через прокси-модель

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

Вот как это можно сделать:

В приведенном выше примере создается прокси-модель с именем Person. Тот факт, что она является прокси-моделью, указывается внутри класса Meta: proxy = True.

Сама прокси-модель в этом примере используется для переопределения сортировки по умолчанию, назначения нового Manager и определения нового метода do_something.

Отметим, что User.objects.all() и Person.objects.all() будут обращаться к одной и той же таблице базы данных. Единственное различие заключается в поведении, которое определяется для прокси-модели. Вот и все.

Расширение модели пользователя с помощью связи один-к-одному

Вероятно, этот метод и есть то, что вам нужно, потому что именно его используют чаще всего. Мы создадим новую модель для хранения дополнительной информации о пользователе.

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

Обычно в Django такие модели называют Profile:

А теперь установим сигналы для Profile на автоматическое создание/обновление, когда мы создаем/обновляем стандартную модель пользователя (User):

Как можно видеть, основная нагрузка — это добавление вызовов create_user_profile и save_user_profile всякий раз, когда происходит сохранение (в том числе создание) объекта. Этот вид сигнала называется post_save.

Дадим еще несколько поясняющих примеров. Можно использовать следующий код в шаблоне:

Или такой внутри представления:

Вообще говоря, вам никогда не придется вызывать метод сохранения профиля. Все делается методами User.

Отдельно обсудим вопрос использования форм. Можно использовать более одной формы сразу. Как в этом примере:

А теперь о дополнительных запросах к базе данных.

Django будет формировать запрос к базе данных только при доступе к одному из связанных свойств. Иногда это вызывает нежелательные эффекты, такие как запуск сотен или тысяч запросов. Этот эффект можно смягчить, используя метод select_related.

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

Расширение модели пользователя с помощью наследования AbstractBaseUser

Это самый сложный вариант, старайтесь избегать его любой ценой. Однако иногда это невозможно.

Допустим, нам нужно использовать адрес электронной почты в качестве логина и использование username совершенно бесполезно. Также у нас нет необходимости в использовании флага is_staff, поскольку мы не будем использовать админку Django.

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

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

  • USERNAME_FIELD — строка, описывающая имя поля в модели пользователя, которая используется как идентификатор. Поле должно быть уникальным (то есть иметь значение unique=True, установленное в его определении);
  • REQUIRED_FIELDS — список имен полей, которые будут запрашиваться при создании пользователя через команду управления createsuperuser;
  • is_active — логический атрибут, указывающий, является ли пользователь активным;
  • get_full_name() — более длинный формальный идентификатор для пользователя. В этом примере будем использовать полное имя пользователя, но это может быть любая строка, которая идентифицирует пользователя;
  • get_short_name() — короткий «неофициальный идентификатор» пользователя. В нашем примере — имя пользователя.

Также нужно определить UserManager. Это связано с тем, что существующий менеджер определяет методы create_user и create_superuser.

А вот как выглядит UserManager, удовлетворяющий перечисленным выше требованиям:

Он удаляет существующий UserManager, а также имя пользователя и свойство is_staff.

Теперь последний шаг. Нужно обновить settings.py, а именно свойство AUTH_USER_MODEL:

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

Как ссылаться на эту модель? Есть два пути. Рассмотрим модель с названием Course:

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

Расширение модели пользователя с помощью наследования AbstractUser

Здесь все довольно просто, поскольку класс django.contrib.auth.models.AbstractUser обеспечивает полную реализацию пользователя по умолчанию как абстрактную модель:

Затем вам как и в предыдущем способе нужно обновить settings.py, определяя свойство AUTH_USER_MODEL:

Это нужно сделать перед началом работ над проектом, так как это повлияет на всю схему базы данных. Также старайтесь создавать внешние ключи для модели пользователя, импортируя параметры из from django.conf import settings и ссылаясь на  settings.AUTH_USER_MODEL вместо прямого обращения к новой пользовательской модели.

Выводы

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

Перевод статьи «How to Extend Django User Model»