Своя аутентификация в Django

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

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

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

Вы можете расширить стандартную модель User или полностью заменить эту модель.

Другие сервисы аутентификации

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

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

В такой ситуации возможно интегрировать систему аутентификации Django с другой системой аутентификации. Вы можете переопределить стандартную систему аутентификации Django или объединить её с другими системами.

Обратитесь к справочнику по бэкенду аутентификации для информации по бэкендам аутентификации, поставляемым с Django.

Использование бэкендов аутентификации

Система аутентификации Django поддерживает список бэкендов, которые она применяет в свой работе. Когда кто-то вызывает django.contrib.auth.authenticate() – в соответствии с документом Как авторизовать пользователя – Django пробует авторизовать пользователя с помощью бекэндов из списка. Если первый бэкенд не подошёл, Django пробует использовать второй и так далее, пока не кончится список.

Список бэкендов аутентификации определён в параметре AUTHENTICATION_BACKENDS файла конфигурации в виде кортежа путей к классам Python’а, в которых определены методы аутентификации. Эти классы могут располагаться где угодно, лишь бы были доступны интерпретатору Python.

По умолчанию, AUTHENTICATION_BACKENDS устанавливается как:

('django.contrib.auth.backends.ModelBackend',)

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

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

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

Примечание

После успешной аутентификации пользователя Django сохраняет в сессии указание на соответствующий бэкенд и повторно использует его всё время жизни сессии при обращении к текущему пользователю. Это показывает, что источники аутентификации кэшируются на уровне сессий, следовательно, при изменении параметра конфигурации AUTHENTICATION_BACKENDS вам потребуется очистить сессию при необходимости заставить пользователей выполнить аутентификацию другими методами. Проще всего это сделать с помощью Session.objects.all().delete().

Создание бэкенда аутентификации

Бэкенд аутентификации - это класс, реализующий два обязательных метода: get_user(user_id) и authenticate(**credentials), а так же ряд необязательных прав, относящихся к методам аутентификации.

Метод get_user принимает параметр user_id, который может быть как именем пользователя, так и первичным ключом в базы данных или чем-либо ещё, но передаваемое значение обязательно должно быть первичным ключом модели User, и возвращает объект User.

Метод authenticate принимает учётные данные в виде именованных аргументов. В основном, это будет выглядеть так:

class MyBackend(object):
    def authenticate(self, username=None, password=None):
        # Check the username/password and return a User.
        ...

Также метод может принимать токен, вот так:

class MyBackend(object):
    def authenticate(self, token=None):
        # Check the token and return a User.
        ...

В любом случае, метод authenticate должен проверить переданные ему учётные данные и вернуть объект User, соответствующий этим данным в случае их корректности. В другом случае он должен вернуть None.

Интерфейс администратора Django тесно связана с объектом User, описанным в начале этого документа. Наилучшим способом взаимодействия с данной системой будет создание объекта User для каждого пользователя, который будет работать с вашим бэкендом (т.е., в вашем LDAP, во внешней SQL БД и так далее.) Вы можете написать скрипт, который заранее выполнит необходимые действия, или добавить логику в метод authenticate, которая будет создавать объект автоматически при первом обращении.

Ниже представлен пример бэкенда, который выполняет аутентификацию относительно переменных имени и пароля пользователя, определённых в файле settings.py и создаёт объект User при первой аутентификации пользователя:

from django.conf import settings
from django.contrib.auth.models import User, check_password

class SettingsBackend(object):
    """
    Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

    Use the login name, and a hash of the password. For example:

    ADMIN_LOGIN = 'admin'
    ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
    """

    def authenticate(self, username=None, password=None):
        login_valid = (settings.ADMIN_LOGIN == username)
        pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
        if login_valid and pwd_valid:
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                # Create a new user. Note that we can set password
                # to anything, because it won't be checked; the password
                # from settings.py will.
                user = User(username=username, password='get from settings.py')
                user.is_staff = True
                user.is_superuser = True
                user.save()
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Управление методами авторизации в своих бэкендах

Бэкенды аутентификации могут представлять дополнительные права доступа.

Модель пользователя передаст функции по проверке прав (get_group_permissions(), get_all_permissions(), has_perm() и has_module_perms()) любому бэкенду аутентификации, который их реализует.

Предоставленные пользователю права являются набором прав, представленных пользователю всеми бэкендами. Следовательно, Django представляет пользователю права, которые выдал хотя бы один бэкенд.

Добавлено в Django 1.8:

Если бэкенд вызовет исключение PermissionDenied в методах has_perm() или has_module_perms(), процесс авторизации будет немедленно остановлен и остальные бэкенды вызваны не будут.

Организовать проверку прав для простого бэкенда из примера выше довольно просто:

class SettingsBackend(object):
    ...
    def has_perm(self, user_obj, perm, obj=None):
        if user_obj.username == settings.ADMIN_LOGIN:
            return True
        else:
            return False

В примере, показанном выше, пользователю выдаётся полный набор прав доступа. Следует отметить, что в дополнение к передачи аргументов стандартных для функций django.contrib.auth.models.User, для всех функций аутентификации необходимо передавать объект пользователя, который может быть и анонимным.

Полная реализация бэкенда аутентификации может быть найдена в классе ModelBackend, определённом в файле django/contrib/auth/backends.py, который является стандартным бэкендом и в основном работает с таблицей auth_permission. Если вам потребуется реализовать нестандартное поведение для определённой части API бэкендов, воспользуйтесь возможностями наследования в Python и унаследуйте ModelBackend, вместо реализации полного API в своём бэкенде.

Авторизация анонимных пользователей

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

Компонент Django, обеспечивающий работу с правами доступа, не выделяет место под хранение данных о правах анонимных пользователей. Тем не менее, объект пользователя, передаваемый в бэкенд аутентификации может являться экземпляром класса django.contrib.auth.models.AnonymousUser, что позволяет определять определённое поведение для анонимных пользователей. Это очень полезно для авторов повторно используемых приложений, которые могут делегировать задачу авторизации соответствующему бэкенду, а не изменят определённые настройки для контроля за доступом анонимных пользователей.

Авторизация неактивных пользователей

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

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

Не забывайте проверять атрибут is_active в методах проверки прав пользователей вашего бекэнда.

Обработка прав для объектов

В Django механизм управления правами подготовлен для работы с правами объектов, хотя и ещё не реализован в ядре фреймворка. Это означает, что проверка прав объекта всегда будет возвращать False или пустой список (в зависимости от вида выполняемой проверки). Бэкенд аутентификации получает именованные параметры obj и user_obj для каждого объекта использованного в методе авторизации и соответственно может вернуть информацию о правах этого объекта.

Создаем свои права доступа

Чтобы создать свои права доступа для вашей модели необходимо определить их в переменной permissions атрибута Meta вашей модели.

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

class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "Can see available tasks"),
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        )

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

user.has_perm('app.view_task')

Расширяем модель User

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

Если вы хотите хранить дополнительную информацию, относящуюся к модели User, вы можете использовать связь один к одному с полями модели, хранящей эту информацию. Эту связанную модель часто называют профилем пользователя, так как она хранит информацию не относящуюся к аутентификации пользователей. Например, вы можете создать модель Employee:

from django.contrib.auth.models import User

class Employee(models.Model):
    user = models.OneToOneField(User)
    department = models.CharField(max_length=100)

Если предположить, что сотрудник Fred Smith имеет записи как в модели User, так и в модели Employee, вы можете получить связанную информацию, используя стандартные соглашения Django для использования моделей:

>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department

Чтобы добавить поля из модели профиля в интерфейс администратора нужно определить InlineModelAdmin в файле admin.py вашего приложения (для этого примера используется StackedInline), добавить его к классу UserAdmin и заново зарегистрировать вместе с классом User:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import Employee

# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
    model = Employee
    can_delete = False
    verbose_name_plural = 'employee'

# Define a new User admin
class UserAdmin(UserAdmin):
    inlines = (EmployeeInline, )

# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

Модель профиля - это обычная модель Django. От других моделей её отличает только наличие связи один к одному с моделью User. Как правило записи в этой модели не создаются автоматически вместе с созданием пользователя, но вы можете воспользоваться методом django.db.models.signals.post_save для создания или обновления записей в профиле по мере необходимости.

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

Заменяем стандартную модель User

Для некоторых проектов возможностей встроенной в Django модели:class:~django.contrib.auth.models.User будет недостаточно. Например, вам требуется использовать в качестве идентификатора пользователя адрес его электронной почты вместо имени пользователя.

Вы можете переписать стандартную модель User, указав в параметре конфигурации AUTH_USER_MODEL ссылка на вашу модель:

AUTH_USER_MODEL = 'myapp.MyUser'

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

Предупреждение

Изменение AUTH_USER_MODEL может сильно отразиться на структуре вашей базы данных. Также это изменит доступность таблиц и отразится на конструкции внешних ключей и отношениях многие ко многим. Если вы собираетесь использовать альтернативную модель, вы должны указать её в параметре конфигурации AUTH_USER_MODEL до того как выполните какие либо миграции или первого запуска manage.py migrate.

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

Предупреждение

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

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

Ссылка на модель User

Если вы напрямую ссылаетесь на модель User (например, используя внешние ключи), ваш код не будет работать в проектах где стандартная модель заменена на другую в параметре конфигурации AUTH_USER_MODEL.

get_user_model()

Вместо того, чтобы ссылаться на модель User напрямую, вы должны делать это, используя метод django.contrib.auth.get_user_model(). Этот метод возвращает действующую модель, указанную в настройках приложения или стандартную модель User, в случае, если не изменялась.

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

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

class Article(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL)
Добавлено в Django 1.7:

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

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

def post_save_receiver(signal, sender, instance, **kwargs):
    pass

post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)

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

Определение своей модели пользователя

Правила дизайна модели

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

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

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

  1. Ваша модель должна иметь числовой первичный ключ.

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

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

Простейший способ построить модель, совместимую с моделью User, - это унаследовать модель AbstractBaseUser. AbstractBaseUser обеспечивает базовую реализацию модели User, в том числе хэширование паролей и сброс пароля посредством токенов.

class models.CustomUser
USERNAME_FIELD

Строка, указывающая имя поля модели User, которая используется в качестве уникального идентификатора. Это, как правило, имя пользователя в некотором виде, но также может быть и адресом электронной почты или любым другим уникальным идентификатором. Поле должно быть уникальным (т.е., unique=True).

В примере ниже, поле identifier используется в качестве уникального идентификатора:

class MyUser(AbstractBaseUser):
    identifier = models.CharField(max_length=40, unique=True)
    ...
    USERNAME_FIELD = 'identifier'
Добавлено в Django 1.8.

Атрибут USERNAME_FIELD теперь поддерживает ForeignKey. Так как не существует способа передать экземпляр модели во время выполнении команды createsuperuser в командной строке, ожидается, что пользователь введёт значение to_field существующего экземпляра (по умолчанию primary_key).

REQUIRED_FIELDS

Список имён полей, которые будут запрашиваться при создании пользователя с помощью команды createsuperuser. Пользователю будет предложено задать значение для каждого из этих полей. Этот список должен содержать имена всех полей, для которых атрибут blank установлен в False или неопределён, а так же любые другие поля, какие потребуется заполнять интерактивно. Изменение параметра REQUIRED_FIELDS ни как не отразится на остальных частях фреймворка Django, таких как создание пользователя через панель администратора.

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

class MyUser(AbstractBaseUser):
    ...
    date_of_birth = models.DateField()
    height = models.FloatField()
    ...
    REQUIRED_FIELDS = ['date_of_birth', 'height']

Примечание

Атрибут REQUIRED_FIELDS должен содержать все обязательные поля для вашей модели User, кроме полей USERNAME_FIELD или password, так как эти поля являются обязательными по умолчанию.

Добавлено в Django 1.8.

Атрибут REQUIRED_FIELDS теперь поддерживает ForeignKey. Так как не существует способа передать экземпляр модели во время выполнении команды createsuperuser в командной строке, ожидается, что пользователь введёт значение to_field существующего экземпляра (по умолчанию primary_key).

is_active

Булевый атрибут, указывающий является ли пользователь активным. По умолчанию класс AbstractBaseUser устанавливает его в True. Вы можете изменить это поведение в вашем бекэнде аутентификации. Для подробностей обратитесь к атрибуту is_active встроенной модели пользователя.

get_full_name()

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

get_short_name()

Короткий неформальный идентификатор пользователя. В общем случае должна представлять имя пользователя, но также может быть любой строкой, определяющей пользователя. Может возвращать такое же значение, что и django.contrib.auth.models.User.get_full_name().

Следующие методы доступны из любого класса, наследующего:class:~django.contrib.auth.models.AbstractBaseUser:

class models.AbstractBaseUser
get_username()

Возвращает значение поля, указанного в USERNAME_FIELD.

is_anonymous()

Всегда возвращает False. Это способ отличать от объектов AnonymousUser. Обычно предпочтительнее использовать is_authenticated() метод.

is_authenticated()

Всегда возвращает True. Это способ определить был ли пользователь авторизован. Метод не подразумевает проверку прав доступа или статус активности пользователя. Он только указывает что пользователь указал правильные имя пользователя и пароль.

set_password(raw_password)

Меняет пароль пользователя на переданный в параметре, предварительно захэшировав его. Объект AbstractBaseUser не сохраняется.

Если raw_password равен None, то назначается неиспользуемый пароль, как-будто был использован метод set_unusable_password().

check_password(raw_password)

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

set_unusable_password()

Помечает пользователя как не использующего пароля. Но это не тоже самое, что указать пустую строку в качестве пароля. Метод check_password() никогда не вернёт True. Объект AbstractBaseUser не сохраняется.

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

has_usable_password()

Вернёт False если для пользователя был вызван метод set_unusable_password().

get_session_auth_hash()
Добавлено в Django 1.7.

Вернет HMAC (хэш) пароля. Используется для переопределения сессии после смены пароля.

Вам также необходимо определить собственный менеджер пользователей вашей модели User. Если ваша модель User определяет поля username, email, is_staff, is_active, is_superuser, last_login и date_joined, аналогично стандартной модели User, вы можете использовать стандартный UserManager. Однако, если вы определили другие поля в вашей модели, вам следует определить свой собственный менеджер пользователей, который будет расширять функционал BaseUserManager, предоставляя два обязательных метода:

class models.CustomUserManager
create_user(*username_field*, password=None, **other_fields)

Метод create_user() должен принимать имя пользователя и другие обязательные поля. Например, если ваша модель пользователя использует email в качестве логина и требует заполнения поля date_of_birth, то ваш метод create_user должен быть определен как:

def create_user(self, email, date_of_birth, password=None):
    # create user here
    ...
create_superuser(*username_field*, password, **other_fields)

Метод create_superuser() должен принимать имя пользователя и другие обязательные поля. Например, если ваша модель пользователя использует email в качестве логина и требует заполнения поля date_of_birth, то ваш метод create_user должен быть определен как:

def create_superuser(self, email, date_of_birth, password):
    # create superuser here
    ...

В отличие от create_user(), create_superuser() должен обязательно требовать пароль.

BaseUserManager предоставляет следующие методы:

class models.BaseUserManager
normalize_email(email)

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

get_by_natural_key(username)

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

make_random_password(length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')

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

  • i, l, I и 1 (строчная буква i, строчная буква L,прописная буква i и цифра один)

  • o, O и 0 (строчная бука о, прописная буква о и ноль)

Расширяем встроенную модель User

Если вас полностью удовлетворяет встроенная модель User и нужно только добавить хранение дополнительной информации о пользователе, вы можете просто расширить модель django.contrib.auth.models.AbstractUser и добавить дополнительные поля, хотя мы рекомендуем создать отдельную модель в соответствии с заметкой “Разработка пользовательской модели” в Определение своей модели пользователя. AbstractUser предоставляет полную реализацию User в виде абстрактной модели.

Свои пользователи и встроенные формы авторизации

Как вы могли ожидать, встроенные в Django формы и:ref:представления <built-in-auth-views> делают определенные предположения о модели User с которой они работают.

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

  • UserCreationForm

    Зависит от модели User. Должен быть переписан для любой новой пользовательской модели.

  • UserChangeForm

    Зависит от модели User. Должен быть переписан для любой новой пользовательской модели.

  • AuthenticationForm

    Работает с любым потомком AbstractBaseUser и должна быть адаптирована для использования поля, определенного в USERNAME_FIELD.

  • PasswordResetForm

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

  • SetPasswordForm

    Работает с любым потомком AbstractBaseUser

  • PasswordChangeForm

    Работает с любым потомком AbstractBaseUser

  • AdminPasswordChangeForm

    Работает с любым потомком AbstractBaseUser

Пользовательская модель и django.contrib.admin

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

class models.CustomUser
is_staff

Возвращает True, если пользователю разрешён доступ к интерфейсу администратора.

is_active

Возвращает True, если пользователь активен.

has_perm(perm, obj=None):

Возвращает True, если пользователь имеет указанное право. Если указан obj, право должно быть проверено в отношении конкретного экземпляра объекта.

has_module_perms(app_label):

Возвращает True, если пользователь имеет доступ к модели указанного приложения.

Вам так же надо зарегистрировать вашу модель пользователя в интерфейсе администратора. Если ваша модель унаследована от django.contrib.auth.models.AbstractUser, вы можете использовать класс django.contrib.auth.admin.UserAdmin. Однако, если вы унаследовались от класса AbstractBaseUser, необходимо определить свой класс ModelAdmin. Для этого вы можете унаследовать стандартный класс django.contrib.auth.admin.UserAdmin, однако вам потребуется переопределить все определения, которые ссылаются на поля в django.contrib.auth.models.AbstractUser, так как последний не является классом вашего пользователя.

Свой пользователь и права

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

Класс PermissionsMixin предоставляет следуюющие методы и атрибуты:

class models.PermissionsMixin
is_superuser

Булевый тип. Указывает, что пользователь имеет все права без их явного назначения.

get_group_permissions(obj=None)

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

Если передается obj, возвращает групповые права доступа только для указанного объекта.

get_all_permissions(obj=None)

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

Если передается obj, возвращает права доступа только для указанного объекта.

has_perm(perm, obj=None)

Возвращает True, если пользователь имеет указанное право доступа, причём perm указывается в формате "<app label>.<permission codename>" (обратитесь к правам доступа). Если пользователь не активен, будет всегда возвращать False.

Если передан obj, этот метод проверит права доступа только для указанного объекта.

has_perms(perm_list, obj=None)

Возвращает True, если пользователь имеет все перечисленные права доступа, переданные в формате "<app label>.<permission codename>". Если пользователь не активен, метод вернёт False.

Если передан obj, этот метод проверит права доступа только для указанного объекта.

has_module_perms(package_name)

Вернет True, если пользователь имеет какие либо права доступа для указанного пакета (приложения Django). Вернет False, если пользователь не активен.

ModelBackend

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

Свой пользователь и Proxy модели

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

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

Свой пользователь и сигналы

Другим ограничением модели пользователя является то, что вы не можете использовать метод django.contrib.auth.get_user_model() в качестве отправителя или приемника обработчика сигналов. Вместо этого вы должны регистрировать обработчик в вашей модели пользователя. Обратитесь к Сигналы для подробной информации по регистрации и отправке сигналов.

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

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

Чтобы убедиться, что ваш набор тестов будет успешно выполняться в любой конфигурации, django.contrib.auth.tests.utils определяет декоратор @skipIfCustomUser. Этот декоратор позволяет пропустить тест, если используется модель позователя, отличная от стандартной модели. Может применяться как для одного теста, так и для всего тестового класса.

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

class tests.custom_user.CustomUser

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

class tests.custom_user.ExtensionUser

Модель пользователя, расширяющая django.contrib.auth.models.AbstractUser добавлением поле date_of_birth.

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

from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.custom_user import CustomUser, ExtensionUser
from django.test import TestCase, override_settings


class ApplicationTestCase(TestCase):
    @skipIfCustomUser
    def test_normal_user(self):
        "Run tests for the normal user model"
        self.assertSomething()

    @override_settings(AUTH_USER_MODEL='auth.CustomUser')
    def test_custom_user(self):
        "Run tests for a custom user model with email-based authentication"
        self.assertSomething()

    @override_settings(AUTH_USER_MODEL='auth.ExtensionUser')
    def test_extension_user(self):
        "Run tests for a simple extension of the built-in User."
        self.assertSomething()

Полный пример

Здесь представлен пример приложения, совместимого с интерфейсом администратора. Модель пользователя использует адрес электронной почты в качестве имени пользователя и содержит обязательное поле с датой рождения; она не предоставляет никакой проверка правд доступа, кроме простого флага admin. Эта модель будет совместима со всеми встроенными формами и представлениями аутентификации,за исключением форм создания пользователя. Этот пример показывает, как большинство компонентов взаимодействует друг с другом, не его не следует напрямую копировать в свои проекты для использования в реальной работе.

Этот код будет располагаться в файле models.py вашего приложения аутентификации:

from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)


class MyUserManager(BaseUserManager):
    def create_user(self, email, date_of_birth, password=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
            date_of_birth=date_of_birth,
        )

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

    def create_superuser(self, email, date_of_birth, password):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        user = self.create_user(email,
            password=password,
            date_of_birth=date_of_birth
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class MyUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
    )
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['date_of_birth']

    def get_full_name(self):
        # The user is identified by their email address
        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        return self.email

    def __str__(self):              # __unicode__ on Python 2
        return 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

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

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

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField

from customauth.models import MyUser


class UserCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = MyUser
        fields = ('email', 'date_of_birth')

    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 don't match")
        return password2

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


class UserChangeForm(forms.ModelForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin')

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]


class MyUserAdmin(UserAdmin):
    # The forms to add and change user instances
    form = UserChangeForm
    add_form = UserCreationForm

    # The fields to be used in displaying the User model.
    # These override the definitions on the base UserAdmin
    # that reference specific fields on auth.User.
    list_display = ('email', 'date_of_birth', 'is_admin')
    list_filter = ('is_admin',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal info', {'fields': ('date_of_birth',)}),
        ('Permissions', {'fields': ('is_admin',)}),
    )
    # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
    # overrides get_fieldsets to use this attribute when creating a user.
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'date_of_birth', 'password1', 'password2')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

# Now register the new UserAdmin...
admin.site.register(MyUser, MyUserAdmin)
# ... and, since we're not using Django's built-in permissions,
# unregister the Group model from admin.
admin.site.unregister(Group)

И, наконец, укажите вашу модель пользователя в качестве модели по умолчанию для вашего проекта с помощью параметра конфигурации AUTH_USER_MODEL:

AUTH_USER_MODEL = 'customauth.MyUser'