Расширение модели User. Класс AbstractUser

Курс по Django: https://stepik.org/a/183363

Архив проекта: 71_sitewomen.zip

На данный момент мы с вами разобрали все основные компоненты, связанные с авторизацией пользователя на сайте: непосредственно авторизация, регистрация, восстановление и изменение пароля, создали профиль пользователя со стандартной моделью User. Если мы посмотрим на таблицу auth_user, то набор информационных полей будет примерно таким:

  • username – логин пользователя;
  • password – пароль для входа в систему;
  • email – электронная почта;
  • first_name – имя пользователя;
  • last_name – фамилия пользователя.

Но, что если нам дополнительно нужно хранить фотографию пользователя, его дату рождения, пол, город, семейное положение и так далее. Как это сделать? Как расширить уже существующую модель User? Во фреймворке Django для этой цели используют два разных подхода:

  • создание еще одной модели (например, Profile) со связью one-to-one (один к одному) с моделью User;
  • создание новой модели User на базе специального класса AbstractUser фреймворка Django.

У каждого подхода есть свои преимущества и недостатки. Преимущества первого состоят в использовании стандартной модели User, а значит, все модули, которые напрямую обращаются к ней, продолжат работать без каких-либо переработок. А основной недостаток – это необходимость в запросах отдельно указывать связанную модель Profile. Преимущества второго подхода, очевидно, в том, что мы, как и ранее, продолжаем работать с одной моделью пользователя, а значит, скорость обработки запросов, в среднем, несколько возрастает. И, кроме того, нам не нужно помнить о дополнительных моделях, что удобно. Главный недостаток – это уход от стандартной модели, поэтому нужно быть уверенным, что во всех компонентах проекта фреймворка Django обращение идет именно к этой новой таблице.

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

Класс AbstractUser

Согласно документации Django:

https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#extending-the-existing-user-model

мы можем расширить существующую модель User следующим образом (в файле users/models.py):

class User(AbstractUser):
    photo = models.ImageField(upload_to="users/%Y/%m/%d/", blank=True, null=True, verbose_name="Фотография")
    date_birth = models.DateTimeField(blank=True, null=True, verbose_name="Дата рождения")

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

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
 
from users.models import User
 
admin.site.register(User, UserAdmin)

Все, наша модель создана и зарегистрирована. Причем, в ней будут присутствовать все стандартные поля прежней модели User, благодаря наследованию от класса AbstractUser, и два дополнительных поля: photo и date_birth. Однако если мы сейчас попробуем создать миграцию для создания новой таблицы user, то увидим ошибки. Это связано с тем, что при замене существующей модели User на свою собственную, нам в файле settings.py необходимо еще переопределить параметр AUTH_USER_MODEL, который по умолчанию принимает значение 'auth.User'. В нашем случае параметр AUTH_USER_MODEL следует прописать так:

AUTH_USER_MODEL = 'users.User'

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

python manage.py makemigrations

никаких ошибок не возникает. Но применение созданной миграции:

python manage.py migrate

снова приводит к ошибкам. Это из-за того, что прежняя модель User в БД связана с другими таблицами. Нам придется удалить все файлы миграций из приложений users и women, а также файл базы данных.

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

Итак, удаляем все миграции из приложений users и women, удаляем файл БД и снова выполняем команды:

python manage.py makemigrations

python manage.py migrate

Теперь ошибок никаких нет и все таблицы были созданы успешно.

Следующим шагом следует убрать во всем проекте обращение к стандартной модели User, если это имело место, и вместо нее прописать функцию get_user_model().

Если при попытке запуска проекта возникает ошибка:

AUTH_USER_MODEL refers to model 'users.get_user_model()' that has not been installed

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

AUTH_USER_MODEL = 'users.User'

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

python manage.py createsuperuser

Ввести прежние данные и пробуем войти в систему. Как видите, все работает, но уже с новой расширенной таблицей User. Попробуем зарегистрироваться:

user1; sc_lib@list.ru; Sergey; Balakirev; root12345

Нажимаем на кнопку «Регистрация» и попадаем на страницу авторизации. Попробуем авторизоваться по E-mail и паролю:

sc_lib@list.ru; root12345

Успешно входим в систему и видим пользователя user1. Все работает.

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

Последним штрихом добавим отображение даты рождения и фотографию пользователя в его профиле. Сначала в файле users/forms.py в классе ProfileUserForm сделаем отображение даты рождения:

class ProfileUserForm(forms.ModelForm):
    ...
    this_year = datetime.date.today().year
    date_birth = forms.DateField(widget=forms.SelectDateWidget(years=tuple(range(this_year-100, this_year-5))))
 
    class Meta:
        model = get_user_model()
        fields = ['photo', 'username', 'email', 'date_birth', 'first_name', 'last_name']
        ...

Чтобы воспользоваться модулем datetime вначале его следует импортировать:

import datetime

Осталось сделать отображение текущего изображения в профиле пользователя. Для этого перейдем в файл шаблона users/profile.html и добавим в него строчки:

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
 
    {% if user.photo %}
    <p ><img src="{{ user.photo.url }}">
    {% else %}
    <p ><img src="/media/users/default.png">
    {% endif %}
    ...

Соответственно, в каталоге media в папке users следует разместить файл default.png.

Однако явно прописывать путь к default.png не лучшая практика. Если путь изменится, придется править все строчки, где он упоминается в шаблонах.  Поэтому мы через параметр extra_context класса ProfileUser (в файле users/views.py) передадим этот маршрут следующим образом:

extra_context = {'title': "Профиль пользователя", 'default_image': settings.DEFAULT_USER_IMAGE}

А параметр DEFAULT_USER_IMAGE определим в файле settings.py нашего проекта:

DEFAULT_USER_IMAGE = MEDIA_URL + 'users/default.png'

Теперь в шаблоне profile.html достаточно прописать переменную default_image:

<p ><img src="{{ default_image }}">

Все, расширенный профиль у нас с вами готов.

Курс по Django: https://stepik.org/a/183363

Видео по теме