Учебное пособие по созданию пользовательской модели пользователя Django

Оглавление

Понимание модели Django User

Модель Django User находится в центре системы аутентификации Django. Это механизм для идентификации пользователей вашего веб-приложения.

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

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

Поля пользователя Django

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

Аутентификация

  • username
  • password
  • last_login
  • date_joined

Коммуникация

  • first_name
  • last_name
  • email

Авторизация

  • groups
  • user_permissions
  • is_staff
  • is_active
  • is_superuser

Нужна ли мне пользовательская модель Django User?

Короткий ответ таков: Нет, но все равно используйте его.

Данные, специфичные для конкретного приложения

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

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

Хранение данных приложения в модели User

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

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

Хранение данных приложения в модели профиля

Альтернативой хранению этих данных является хранение специфических для приложения данных в другой модели, которая имеет связь один-к-одному с моделью User. Это позволит сохранить вашу модель простой, компактной и специфичной для аутентификации.

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

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

Дополнительные или иные данные аутентификации

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

Зачем вообще это делать?

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

Учебник: Определение пользовательской модели

Для демонстрации предположим, что у нас есть недавно созданный проект Django под названием Foo. Для этого проекта мы хотим использовать адрес электронной почты вместо имени пользователя для аутентификации, но по-прежнему хотим использовать встроенную систему разрешений Django.

foo_auth application

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

./manage.py startapp foo_auth

Создав новое приложение, мы можем зарегистрировать его в Django, добавив его в INSTALLED_APPS так, чтобы в foo/settings.py оно выглядело следующим образом:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'foo_auth',
]

Определение модели

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

Чтобы помочь в этом, Django предоставляет класс AbstractBaseUser и класс BaseUserManager, которые помогают обеспечить некоторые стандартные модели поведения и интерфейсы, которые Django ожидает иметь. Поскольку мы также хотим, чтобы наш пользователь поддерживал разрешения по умолчанию, мы можем использовать предоставленный PermissionsMixin, чтобы убедиться, что наш класс User работает с системой разрешений Django.

Складывая все это вместе в foo_auth/models.py:

from django.contrib.auth.models import (
    AbstractBaseUser, BaseUserManager, PermissionsMixin
)
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _


class UserManager(BaseUserManager):
    def create_user(
            self, email, first_name, last_name, password=None,
            commit=True):
        """
        Creates and saves a User with the given email, first name, last name
        and password.
        """
        if not email:
            raise ValueError(_('Users must have an email address'))
        if not first_name:
            raise ValueError(_('Users must have a first name'))
        if not last_name:
            raise ValueError(_('Users must have a last name'))

        user = self.model(
            email=self.normalize_email(email),
            first_name=first_name,
            last_name=last_name,
        )

        user.set_password(password)
        if commit:
            user.save(using=self._db)
        return user

    def create_superuser(self, email, first_name, last_name, password):
        """
        Creates and saves a superuser with the given email, first name,
        last name and password.
        """
        user = self.create_user(
            email,
            password=password,
            first_name=first_name,
            last_name=last_name,
            commit=False,
        )
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(
        verbose_name=_('email address'), max_length=255, unique=True
    )
    # password field supplied by AbstractBaseUser
    # last_login field supplied by AbstractBaseUser
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)

    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'
        ),
    )
    # is_superuser field provided by PermissionsMixin
    # groups field provided by PermissionsMixin
    # user_permissions field provided by PermissionsMixin

    date_joined = models.DateTimeField(
        _('date joined'), default=timezone.now
    )

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def __str__(self):
        return '{} <{}>'.format(self.get_full_name(), self.email)

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

Регистрация модели

Теперь, когда мы определили нашу пользовательскую модель User, нам нужно сказать Django использовать ее. В foo/settings.py мы можем добавить:

AUTH_USER_MODEL = 'foo_auth.User'

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

Генерирование миграций базы данных

Теперь, когда наша пользовательская модель User определена и зарегистрирована в Django, мы можем сгенерировать миграции, необходимые для создания таблицы (таблиц) базы данных для нашей новой структуры:

./manage.py makemigrations

Модель в действии

Теперь мы хотим увидеть нашу модель User в действии. Самый простой способ сделать это в нашем приложении - посмотреть, как она выглядит в админке Django.

Настройка базы данных

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

./manage.py migrate

Создание пользователя admin

Чтобы получить доступ к сайту Django Admin, нам нужно создать пользователя с доступом администратора. Самый простой способ сделать это - создать суперпользователя через клиент командной строки:

./manage.py createsuperuser

Вы заметите, что эта команда запрашивает ваш email (поле, которое мы отметили как поле имени пользователя в нашей пользовательской модели), ваше имя, фамилию (поля, которые мы отметили как обязательные) и, наконец, пароль. После выполнения команды пользователь будет создан!

Сделать пользовательского пользователя доступным в админке

Используя адрес электронной почты и пароль, указанные в команде createsuperuser, вы можете войти на сайт администратора. При локальной разработке с настройками по умолчанию сайт администратора должен быть доступен по адресу: http://localhost:8000/admin/

Когда вы вошли в систему, вы заметите, что в админке нет ничего для управления пользователями. Это потому, что мы не зарегистрировали нашу новую модель User на сайте Django Admin. Чтобы сделать это, в foo_auth/admin.py у нас есть:

Когда вы вошли в систему, вы заметите, что в админке нет ничего для управления пользователями. Это потому, что мы не зарегистрировали нашу новую модель User на сайте Django Admin. Чтобы сделать это, в foo_auth/admin.py у нас есть:

from django.contrib import admin

from .models import User


admin.site.register(User)

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

Настройка страницы пользователя администратора

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

Любой человек с доступом администратора может изменить статус суперпользователя любого пользователя, поэтому давайте настроим страницу Admin User и изменим foo_auth/admin.py:

from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from .models import User


class AddUserForm(forms.ModelForm):
    """
    New User Form. Requires password confirmation.
    """
    password1 = forms.CharField(
        label='Password', widget=forms.PasswordInput
    )
    password2 = forms.CharField(
        label='Confirm password', widget=forms.PasswordInput
    )

    class Meta:
        model = User
        fields = ('email', 'first_name', 'last_name')

    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords do not match")
        return password2

    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user


class UpdateUserForm(forms.ModelForm):
    """
    Update User Form. Doesn't allow changing password in the Admin.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = User
        fields = (
            'email', 'password', 'first_name', 'last_name', 'is_active',
            'is_staff'
        )

    def clean_password(self):
        # Password can't be changed in the admin
        return self.initial["password"]


class UserAdmin(BaseUserAdmin):
    form = UpdateUserForm
    add_form = AddUserForm

    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', )
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal info', {'fields': ('first_name', 'last_name')}),
        ('Permissions', {'fields': ('is_active', 'is_staff')}),
    )
    add_fieldsets = (
        (
            None,
            {
                'classes': ('wide',),
                'fields': (
                    'email', 'first_name', 'last_name', 'password1',
                    'password2'
                )
            }
        ),
    )
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email', 'first_name', 'last_name')
    filter_horizontal = ()


admin.site.register(User, UserAdmin)

Эти изменения позволяют нам создать нового пользователя с правильно установленным паролем в Admin: мы можем обновить "безопасные" поля пользователя, а все поля затем красиво сгруппировать и организовать.

Обращение к модели пользователя

Если вы хотите поддерживать проекты, в которых изменяется настройка AUTH_USER_MODEL, вы, к сожалению, не можете просто импортировать модель User или ссылаться на нее с помощью appName.ModelName.

Django предоставляет безопасные механизмы для доступа к модели User по мере необходимости.

Ссылка на модель User (без использования реального класса модели)

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

from django.conf import settings
from django.db import models

class MyModel(models.Model):
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE
    )

Получение модели пользователя

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

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

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

from django.contrib.auth import get_user_model
from django.shortcuts import render


def user_list_view(request):
    User = get_user_model()
    return render(request, 'users_list.html', {'users': User.objects.all()})

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

Что дальше?

Теперь у нас есть пользовательская модель User, полностью интегрированная в Django, на которую мы можем корректно ссылаться в наших приложениях!

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

Основные выводы

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

Django позволяет очень легко настроить вашу собственную пользовательскую модель User. Каждый проект может меняться со временем, и установка пользовательской модели с самого начала может избавить вас от большой головной боли в будущем!

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

Правильная ссылка на модель пользователя

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

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