Всем привет!


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


Когда мне понадобилось реализовать этот функционал, оказалось что существует не так много туториалов, особенно на русском, в которых бы описывалось как сделать регистрацию по email, отправку верифицирующего письма, сброс пароля и другие, в общем то вполне обычные вещи.
Я решил исправить эту несправедливость.


Django


Покопавшись немного в интернетах, мой выбор пал на модуль django-user-accounts.
Этот модуль — часть экосистемы Pinax, имеет неплохую документацию и, что самое приятное, немного легко-читаемого кода.


Модуль умеет:


  • Регистрировать пользователя по Email;
  • Отправлять письмо с подтверждающей ссылокой;
  • Аутентифицировать пользователя при помощи Email и пароля;
  • Изменять пароль из интерфейса;
  • Сбрасывать и восстанавливать пароль;
  • Отслеживать "протухание" пароля;
  • Изменять параметры аккаунта (например локаль или часовой пояс);
  • Удалять аккаунт.

Инсталяция


Для установки выполняем команду:


pip install django-user-accounts

Не забываем добавить зависимость в файл requirements.txt:


django-user-accounts==2.0.3

В settings.py добавляем INSTALLED_APPS:


INSTALLED_APPS =[
    .....
    'django.contrib.sites',
    'account'
]

Важно добавить стандартный джанговский модуль sites, так как account от него зависит.
Дальше добавляем MIDDLEWARE_CLASSES:


MIDDLEWARE_CLASSES = [
    .....
    'account.middleware.LocaleMiddleware',
    'account.middleware.TimezoneMiddleware'
]

И context_processors:


TEMPLATES = [
    {
        .....
        'OPTIONS': {
            'context_processors': [
                .....
                'account.context_processors.account'
            ],
        },
    },
]

Для того, чтобы у нас email был уникальный и чтобы требовалось подтвеждение добавим два ключа в settings.py:


ACCOUNT_EMAIL_UNIQUE = True
ACCOUNT_EMAIL_CONFIRMATION_REQUIRED = True

Теперь можно выполнить миграции:


python manage.py migrate

В результате в БД появятся новые таблицы:


account_account
account_accountdeletion
account_emailaddress
account_emailconfirmation
account_passwordexpiry
account_passwordhistory
account_signupcode
account_signupcoderesult
django_site

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


python manage.py shell
>>> from django.contrib.sites.models import Site
>>> site = Site(domain='localhost:8000', name='localhost:8000')
>>> site.save()
>>> site.id
2

И добавить в setting.py id нового сайта:


SITE_ID = 2

Все templates для необходимых страниц и текстов писем можно скачать из pinax-theme-bootstrap и просто поместить их по адресу yourproject/yourapp/templates/account.


Настройка


Если вы собираетесь подключать этот модуль на уровне Django-приложения, а не проекта, то для корректной работы маршрутизаций, добавим в settings.py следующие строки:


ACCOUNT_LOGIN_URL = 'yourapp:account_login'
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = ACCOUNT_LOGIN_URL
ACCOUNT_PASSWORD_RESET_REDIRECT_URL = ACCOUNT_LOGIN_URL
ACCOUNT_EMAIL_CONFIRMATION_URL = "yourapp:account_confirm_email"
ACCOUNT_SETTINGS_REDIRECT_URL = 'yourapp:account_settings'
ACCOUNT_PASSWORD_CHANGE_REDIRECT_URL = "yourapp:account_password"

И добавим урлы в файл yourapp/urls.py:


urlpatterns = [
    .....
    url(r"^account/", include("account.urls")),
    .....
]

Теперь доступны следующие адреса:


    account/signup/
    account/login/
    account/logout/
    account/confirm_email/<key>
    account/password/
    account/password/reset/
    account/password/reset/<token>
    account/settings/
    account/delete/

Осталось лишь добавить настройки для почтового сервера в settings.py:


DEFAULT_FROM_EMAIL = 'support@yoursite.ru'
EMAIL_HOST = "smtp.yoursmtpserver.ru"
EMAIL_PORT = 25
EMAIL_HOST_USER = "user"
EMAIL_HOST_PASSWORD = "pass"

Если вы всё правильно сделали, то по указанным урлам можно логиниться при помощи логина/пароля, а при создании учетки вам на почту будет отправлено письмо для подтверждения email-а.


Лирическое отступление


Была обнаружена бага, которая не позволяет корректно сформировать ссылку на восстановление пароля если вы добавили ссылки в приложение, а не в проект.
Пока не принят пулл-реквест можно вбить костыль в файл yourproject/urls.py


from account.views import PasswordResetTokenView

urlpatterns = [
    .....
    url(r"^account/password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$", PasswordResetTokenView.as_view(), name="account_password_reset_token"),
    .....
]

В пулл-реквесте появится новая настройка:


ACCOUNT_PASSWORD_RESET_TOKEN_URL = 'yourapp:account_password_reset_token'

Аутентификация по Email


Для начала добавляем соответсвующий backend в settings.py:


AUTHENTICATION_BACKENDS = [
    'account.auth_backends.EmailAuthenticationBackend',
]

Затем удаляем из формы регистрации username, для этого в файл yourapp/forms.py добавляем следующее:


import account.forms

class SignupForm(account.forms.SignupForm):

    def __init__(self, *args, **kwargs):
        super(SignupForm, self).__init__(*args, **kwargs)
        del self.fields["username"]

А в файл yourapp/views.py это:


import yourapp.forms
import account.forms
import account.views

class LoginView(account.views.LoginView):
    form_class = account.forms.LoginEmailForm

class SignupView(account.views.SignupView):

    form_class =  yourapp.forms.SignupForm

    def generate_username(self, form):
        username = form.cleaned_data["email"]
        return username

    def after_signup(self, form):
        # do something
        super(SignupView, self).after_signup(form)

Здесь мы для вьюшек задаем классы форм: LoginEmailForm и нашу SignupForm соответсвенно, а так же переопределяем метод after_signup, чтобы можно было подмешать в него какое-нибудь нужное нам поведение. По-умолчанию полю username присваивам значение email.


Осталось лишь переопределить наши url-ы в файле yourapp/urls.py:


from . import views

urlpatterns = [
    .....
    url(r"^account/login/$", views.LoginView.as_view(), name="account_login"),
    url(r"^account/signup/$", views.SignupView.as_view(), name="account_signup"),
    url(r"^account/", include("account.urls")),
    .....
]

Обращаю внимание, что вызов кастомных вьюшек должен идти перед подключением account.urls, иначе они не переопределятся.


Миграция старых данных


Для того чтобы текущие пользователи могли залогиниться нужно добавить их email адреса в таблицу account_emailaddress:


insert into account_emailaddress(email, verified, "primary", user_id) 
select au.email, True, True, au.id
from auth_user au
where au.email is not null

В данном случае в поле verified вставляется значение true, т.е. мы сразу их подтверждаем.


Для того, чтобы у всех пользователей появился аккаунт, заполнить таблицу account_account:


insert into account_account (timezone, "language", user_id)
select '','ru', id
from auth_user

Заключение


Подробнее о модуле django-user-account вы можете узнать в официальной документации.
Исходный код находится тут. Его полезно почитать, чтобы чуть лучше разобраться в том, как работает механизм auth в Django.


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

Комментарии (7)


  1. asmrnv777
    10.11.2017 20:45

    Спасибо!

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

    Крон не особо подходит, а свой велосипед пилить не хочется.


    1. random1st
      11.11.2017 15:11

      что используете в качестве брокера?


      1. asmrnv777
        12.11.2017 16:24

        Пробовал redis и RabbitMQ, в обоих одна и та же проблема.


    1. Mogost
      12.11.2017 09:56

      Celery отличный инструмент. Если cron'a не хватает, можно попробовать решить задачу через uWSGI. Там есть cron-подобный вариант. Также есть spooler.
      При том можно очень удобно все организовать с помощью декораторов.


    1. Devroman
      12.11.2017 12:09

      Celery довольно неплох, но может быть у вас есть какие-то проблемы с окружением. Однако попробуйте еще посмотреть в сторону rq (http://python-rq.org/) может покажется более удобным :)


  1. Mogost
    12.11.2017 09:40

    Именно в аспекте аутентификации для django есть ещё один хороший пакет, который можно использовать — django-allauth. Так по пунктам:

    1. Регистрировать пользователя по Email;
      возможна через ACCOUNT_AUTHENTICATION_METHOD='email'.
    2. Отправлять письмо с подтверждающей ссылкой;
      возможно из коробки (ACCOUNT_EMAIL_VERIFICATION='mandatory'), при том там очень гибкие настройки.
    3. Аутентифицировать пользователя при помощи Email и пароля;
      возможно из коробки.
    4. Изменять пароль из интерфейса;
      allauth.account.views.PasswordChangeView присутствует.
    5. Сбрасывать и восстанавливать пароль;
      allauth.account.views.PasswordResetView присутствует.
    6. Отслеживать «протухание» пароля;
      тут придется дописать кода, но не выглядит ничем сложным.
    7. Изменять параметры аккаунта (например локаль или часовой пояс);
      к email аутентификации не относится никак, нужны дополнительные поля к стандартной юзер модели, наследуетесь и определяет их.
    8. Удалять аккаунт.
      тут тоже код дописывать придется (если надо инициировать удаление на стороне пользователя, а если со стороны админа, то в стандартной django-admin).


    Плюс ко всему вы получите простой способ прикрутить аутентификацию грубо говоря через что угодно.
    Документация django-allauth.readthedocs.io/en/latest
    P.S. Инструмент надо выбирать себе с умом, смотреть какие доступны и какие удовлетворяют вашим задач. Конечно желательно чтоб инструмент развивался и обновлялся, всё-таки аутентификация довольно критичная часть системы.
    P.S.2 djangopackages.org — хороший сайт чтоб найти инструмент именно под django.
    P.S.3 Есть очень хорошая книга «Two Scoops of Django» Best Practices for Django, книга очень продуманная, обновляется с выходами новых версий Django и рассказывает о разных инструментах (в частности про django-allauth).


    1. lepism Автор
      12.11.2017 10:00

      Спасибо большое за ценную информацию!
      Я django-allauth тоже смотрел, но мне он показался слишком громоздким, по крайней мере для моих задач. Возможно в другом проекте я буду использовать именно его.