вторник, 8 марта 2016 г.

Замена дефолтной модели User

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

В этой статье я подробно опишу как это сделать. Для примера, рассмотрим все тот же вариант использования, когда нужна аутентификация по полю email. Весь код класса я решил разбить на четыре части для удобства читаемости. Ничего особенного в этом классе нет. За исключением нескольких измененных строк, он один в один идентичен классу django.contrib.auth.models.AbstractUser. Вот как будет выглядеть реализация этого класса:

from django.db import models
from django.contrib.auth.models import UserManager
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.utils.translation import ugettext_lazy as _
from django.core import validators
from django.utils import timezone
from django.core.mail import send_mail

Создаваемый нами класс должен наследоватся от AbstractBaseUser и PermissionsMixin. Также, необходимо внести изменения в определения полей email и username:

class BaseCustomUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username, password and email are required. Other fields are optional.
    """
    email = models.EmailField(
        _('Email Address'), unique=True,
        error_messages={
            'unique': _("A user with that email already exists."),
        }
    )
    username = models.CharField(_('username'), max_length=30, unique=True, blank=True, null=True,
        help_text=_('Required. 30 characters or fewer. Letters, digits and '
                    '@/./+/-/_ only.'),
        validators=[
            validators.RegexValidator(r'^[\w.@+-]+$',
                                      _('Enter a valid username. '
                                        'This value may contain only letters, numbers '
                                        'and @/./+/-/_ characters.'), 'invalid'),
        ],
        error_messages={
            'unique': _("A user with that username already exists."),
        }
    )

Обязательно вносим изменения в поля USERNAME_FIELD и REQUIRED_FIELDS, как показано ниже:

    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    is_staff = models.BooleanField(_('staff status'), default=False,
        help_text=_('Designates whether the user can log into this admin site.'))
    is_active = models.BooleanField(_('active'), default=True,
        help_text=_('Designates whether this user should be treated as '
                    'active. Unselect this instead of deleting accounts.'))
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

В остальном коде, в классе Meta, изменим значение поля abstract на False:

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        abstract = False

    def get_full_name(self):
        """
        Returns the first_name plus the last_name, with a space in between.
        """
        full_name = '{0} {1}'.format(self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        "Returns the short name for the user."
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """
        Sends an email to this User.
        """
        send_mail(subject, message, from_email, [self.email], **kwargs)

Итак, для большей уверенности, что я делаю все правильно, я просто копирую весь код класса django.contrib.auth.models.AbstractUser и вношу нужные изменения. Конечно же, первое что приходит на ум, когда мы копируем код какого либо класса: "А не лучше будет унаследоватся от этого класса и переопределить нужные поля и методы?". К сожалению, согласно замечанию:

In normal Python class inheritance, it is permissible for a child class to override any attribute from the parent class. In Django, this is not permitted for attributes that are Field instances (at least, not at the moment). If a base class has a field called author, you cannot create another model field called author in any class that inherits from that base class.

на данный момент, невозможно унаследоватся от модели и переопределить поля или методы.

Как только мы создали свою модель, мы готовы подключить её в систему. Для этого, в файле settings.py, указываем Django использовать именно наш класс:

...
AUTH_USER_MODEL = 'security.CustomUser'
...

Теперь, вход в систему и регистрация будет осуществлятся исключительно по полю email вместо дефолтного поля username.

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

1 комментарий:

  1. Спасибо за статью)
    Вот если бы еще все выше написанное было в git скинуто))))))

    ОтветитьУдалить

Ярлыки

Популярные записи