Современный способ создание настраиваемой модели User в Django
В этой статье пошагово объясняется, как создать настраиваемую модель User в Django, чтобы можно было бы использовать email в качестве основного идентификатора аутентификации вместо поля username.
Имейте в виду, что процесс, описанный в этой статье, требует значительных изменений в схеме базы данных. Из-за этого рекомендуется проводит это процесс только для новых проектов. Если вам необходимо внести подобные изменения для уже существующего проекта, вам, обязательно, нужно не забыть сделать резервную копию данных. Так же вы можете узнать больше о внесение подобных изменений в существующий проект, на следующих ресурсах:
- Замена настраиваемой модели User официальная документация
- Миграция настраиваемой модели User в Django блог пост
AbstractUser против AbstractBaseUser
По умолчанию модель User в Django использует username для уникальной идентификации во время аутентификации. Если вам необходимо использовать email, вам нужно создать собственную пользовательскую модель User, используя для этого подклассы AbstractUser или AbstractBaseUser.
AbstractUser
: Используйте этот подкласс, если вас устраивают существующими поля в модели User и вы просто хотите удалить поле username.AbstractBaseUser
: Используйте этот подкласс, если вы хотите создать с нуля собственную, совершенно новую модель User.
Шаги одинаковы для каждого выбора:
- Создайте пользовательскую модель User и новый Manager
- Обновите settings.py
- Настройте формы
UserCreationForm
иUserChangeForm
- Обновите админку
Настройка проекта
Предположим, что у вас уже установлен Pipenv. Начнем с создания нового проекта Django вместе с приложением users:
$ mkdir django-custom-user-model && cd django-custom-user-model $ pipenv shell --python 3.6 (django-custom-user-model)$ pipenv install django==2.1.5 (django-custom-user-model)$ django-admin.py startproject hello_django . (django-custom-user-model)$ python manage.py startapp users
НЕ запускай миграцию на данном шаге. Помните: перед запуском первой миграции у вас уже должна быть создана пользовательская модель User.
Добавте новое приложение в INSTALLED_APPS
в settings.py:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users', ]
Тесты
Я предпочитаю разрабатывать приложения через тестирование. Добавим тесты в users/tests.py, и убедимся что тесты возвращают ошибку.
from django.test import TestCase from django.contrib.auth import get_user_model class UsersManagersTests(TestCase): def test_create_user(self): User = get_user_model() user = User.objects.create_user(email='normal@user.com', password='foo') self.assertEqual(user.email, 'normal@user.com') self.assertTrue(user.is_active) self.assertFalse(user.is_staff) self.assertFalse(user.is_superuser) try: # username is None for the AbstractUser option # username does not exist for the AbstractBaseUser option self.assertIsNone(user.username) except AttributeError: pass with self.assertRaises(TypeError): User.objects.create_user() with self.assertRaises(TypeError): User.objects.create_user(email='') with self.assertRaises(ValueError): User.objects.create_user(email='', password="foo") def test_create_superuser(self): User = get_user_model() admin_user = User.objects.create_superuser('super@user.com', 'foo') self.assertEqual(admin_user.email, 'super@user.com') self.assertTrue(admin_user.is_active) self.assertTrue(admin_user.is_staff) self.assertTrue(admin_user.is_superuser) try: # username is None for the AbstractUser option # username does not exist for the AbstractBaseUser option self.assertIsNone(admin_user.username) except AttributeError: pass with self.assertRaises(ValueError): User.objects.create_superuser( email='super@user.com', password='foo', is_superuser=False)
Команда запуска тестов:
(django-custom-user-model)$ python manage.py test
Менеджер модели
Далее, нам нужно добавить пользовательский Manager, с помощью подкласса BaseUserManager, который использует email в качестве уникального идентификатора вместо username.
Создайте файл managers.py в директории users:
from django.contrib.auth.base_user import BaseUserManager from django.utils.translation import ugettext_lazy as _ class CustomUserManager(BaseUserManager): """ Custom user model manager where email is the unique identifiers for authentication instead of usernames. """ def create_user(self, email, password, **extra_fields): """ Create and save a User with the given email and password. """ if not email: raise ValueError(_('The Email must be set')) email = self.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save() return user def create_superuser(self, email, password, **extra_fields): """ Create and save a SuperUser with the given email and password. """ extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) extra_fields.setdefault('is_active', True) if extra_fields.get('is_staff') is not True: raise ValueError(_('Superuser must have is_staff=True.')) if extra_fields.get('is_superuser') is not True: raise ValueError(_('Superuser must have is_superuser=True.')) return self.create_user(email, password, **extra_fields)
Модель User
Решите, какой вариант вы хотели бы использовать — подклассы AbstractUser или AbstractBaseUser.
AbstractUser
Внесите в users/models.py следующие изменения:
from django.db import models from django.contrib.auth.models import AbstractUser from django.utils.translation import ugettext_lazy as _ from .managers import CustomUserManager class CustomUser(AbstractUser): username = None email = models.EmailField(_('email address'), unique=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] objects = CustomUserManager() def __str__(self): return self.email
Здесь, мы:
- Создаем новый класс
CustomUser
который базируется наAbstractUser
- Удаляем поле
username
- Делаем поле
email
обязательным и уникальным - Задаем
USERNAME_FIELD
—для определения уникального идентификатора в моделиUser
со значениемemail
- Указываем, что все objects для класса происходят от
CustomUserManager
AbstractBaseUser
Внесите в users/models.py следующие изменения :
from django.db import models from django.contrib.auth.models import AbstractBaseUser from django.contrib.auth.models import PermissionsMixin from django.utils.translation import gettext_lazy as _ from django.utils import timezone from .managers import CustomUserManager class CustomUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField(_('email address'), unique=True) is_staff = models.BooleanField(default=False) is_active = models.BooleanField(default=True) date_joined = models.DateTimeField(default=timezone.now) USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] objects = CustomUserManager() def __str__(self): return self.email
Здесь, мы:
- Создаем новый класс
CustomUser
который базируется наAbstractBaseUser
- Добваляем поля
email
,is_staff
,is_active
, иdate_joined
- Устанавливаем
USERNAME_FIELD
—которое определяет уникальный идентификатор для моделиUser
значениемemail
- Указываем что objects для класса происходят от
CustomUserManager
Настройка
Добавьте следующую строку в файл settings.py, чтобы Django знал, как использовать новый класс User:
AUTH_USER_MODEL = 'users.CustomUser'
Теперь мы можем создать и запустить миграцию, которая создаст новую базу данных, использующую пользовательскую модель User. Но прежде чем мы это сделаем, давайте предварительно посмотрим, как будет выглядеть миграция, используем флаг —dry-run:
(django-custom-user-model)$ python manage.py makemigrations --dry-run --verbosity 3
Вы должны увидеть что-то похожее на:
# Generated by Django 2.1.5 on 2019-02-06 14:24 from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): initial = True dependencies = [ ('auth', '0009_alter_user_last_name_max_length'), ] operations = [ migrations.CreateModel( name='CustomUser', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ 'verbose_name': 'user', 'verbose_name_plural': 'users', 'abstract': False, }, ), ]
Убедитесь, что миграция не включает поле username. Затем создайте и запустите миграцию:
(django-custom-user-model)$ python manage.py makemigrations (django-custom-user-model)$ python manage.py migrate
Посмотрим на созданную схему БД:
$ sqlite3 db.sqlite3 SQLite version 3.16.0 2016-11-04 19:09:39 Enter ".help" for usage hints. sqlite> .tables auth_group django_migrations auth_group_permissions django_session auth_permission users_customuser django_admin_log users_customuser_groups django_content_type users_customuser_user_permissions sqlite> .schema users_customuser CREATE TABLE "users_customuser" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "password" varchar(128) NOT NULL, "last_login" datetime NULL, "is_superuser" bool NOT NULL, "first_name" varchar(30) NOT NULL, "last_name" varchar(150) NOT NULL, "is_staff" bool NOT NULL, "is_active" bool NOT NULL, "date_joined" datetime NOT NULL, "email" varchar(254) NOT NULL UNIQUE );
Теперь вы можете использовать модель User внутри приложения с помощью get_user_model() или settings.AUTH_USER_MODEL. Если вы хотите уточнить этот момент можете почитать об этот тут: ссылка на модель пользователя из официальных документов.
Кроме того, когда вы попытаетесь создаете суперпользователя, вам будет предложено ввести email, а не username:
(django-custom-user-model)$ python manage.py createsuperuser Email address: test@test.com Password: Password (again): Superuser created successfully.
Далее убедимся, что тесты проходят:
---------------------------------------------------------------------- Ran 2 tests in 0.282s OK
Формы
Теперь давайте создадим подклассы форм UserCreationForm и UserChangeForm, чтобы они использовали новую модель CustomUser.
Создайте новый файл в каталоге users с именем forms.py:
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from .models import CustomUser class CustomUserCreationForm(UserCreationForm): class Meta(UserCreationForm): model = CustomUser fields = ('email',) class CustomUserChangeForm(UserChangeForm): class Meta: model = CustomUser fields = ('email',)
Админка
Укажем стандартной админке Django использовать эти новые формы, создав подкласс UserAdmin в файле users/admin.py:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .forms import CustomUserCreationForm, CustomUserChangeForm from .models import CustomUser class CustomUserAdmin(UserAdmin): add_form = CustomUserCreationForm form = CustomUserChangeForm model = CustomUser list_display = ('email', 'is_staff', 'is_active',) list_filter = ('email', 'is_staff', 'is_active',) fieldsets = ( (None, {'fields': ('email', 'password')}), ('Permissions', {'fields': ('is_staff', 'is_active')}), ) add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')} ), ) search_fields = ('email',) ordering = ('email',) admin.site.register(CustomUser, CustomUserAdmin)
Вот и все. Запустите сервер и войдите на сайт администратором. Вы должны иметь возможность добавлять и изменять пользователей, как обычно.
Заключение
В этой статье мы рассмотрели, как создать пользовательскую модель User, чтобы использовать поле email в качестве основного идентификатора пользователя вместо поля username.
Вы можете найти окончательный код для обоих вариантов, AbstractUser и AbstractBaseUser, в репозитории django-custom-user-model. Окончательные примеры кода так же включают шаблоны, вьюхи и URL-адреса, необходимые для аутентификации пользователя.
Хотите узнать больше о настройке модели Django? Посмотрите на следующие ресурсы:
- Options & Objects: Настройка Django User Model
- Как расширить Django User Model
- Получение максимальной отдачи от Django User Model (video)
- Настройка аутентификации в Django
- Django: How to Extend The User Model (aka Custom User Model)
Автор Michael Herman оригинал: Creating a Custom User Model in Django
Это разве нормально, что вроде бы простое действие, которое нужно делать для каждого второго проекта, выливается в инструкцию на 14 скринов? Мне казалось, фреймворки должны избавлять от рутины, а не добавлять…
Подобные статьи это подобие шпаргалки. Когда похожие действия делаются часто, то потребностей в таких статьях нет. Но когда нужно сделать что то типа такого раз в год, это либо искать в доках (что не всегда удобно) или быстро найти такую шпаргалку через поисковик и быстро вспомнить как это делается.
Спасибо, очень подробно расписано
Большое спасибо за подробную статью и ссылки!
Стало гораздо понятнее:)
AbstractUser
: Используйте этот подкласс, если вас устраивают существующими поля в модели User и вы просто хотите удалить поле username.AbstractBaseUser
: Используйте этот подкласс, если вы хотите создать с нуля собственную, совершенно новую модель User.Меня устраивают поля стандартной модели, но, возможно, я захочу добавить свои поля. Какую мне модель использовать?
Думаю, что AbstractUser, т.к. используя её, все имеющиеся поля сохраняются
А я вот не понял как можно свой пароль поставить?
Заменить поле пароля на свой имеющий
Спасибо! Подсмотрел раздел Админка, т.к. при наследовании от AbstractUser и использовании стандартного admin.py у меня были глюки с отображением пароля…