Настройки
Открываем и добавляем настройки в файл settings.py:
SITE_SSL = False
SITE_DOMAIN = '127.0.0.1:8000'
ACCOUNTS_VCODE_TIMEOUT = 60 * 5 # время жизни проверочного кода (в секундах)
DEFAULT_FROM_EMAIL = '{title} <{email}>'.format(title=SITE_TITLE, email='test@test.test')
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Первые две настройки показывают используется ли безопасный протокол SSL и указывает название домена, служат в основном для обеспечения совместимости настроек на боевом и тестовом сервере. Например, для правильной генерации абсолютных ссылок с указанием используемого протокола.
Далее мы задаем "время жизни" кода для подтверждения адреса электронной почты.
Затем мы указываем адрес, с которого мы будем отправлять всю корреспонденцию.
И в завершении мы указываем бэкэнд для имитации отправки почты, все сообщения, которые будут отправлены с помощью этого класса попадут в консоль, для удобства отладки во время разработки проекта.
Утилиты
Для начала опишем утилиты, которые могут найти применение не только в приложении accounts, в директории core создаем utils.py:
import random
import hashlib
import time
from django.conf import settings
def get_random():
try: # по возможности используем системный рандом
return random.SystemRandom()
except NotImplementedError:
random.seed(hashlib.sha256('{}{}{}'.format(
random.getstate(), time.time(), settings.SECRET_KEY
).encode('utf-8')).digest())
return random
def serializing_datetime(dt):
dt = dt.isoformat()
if dt.endswith('+00:00'):
dt = dt[:-6] + 'Z'
return dt
def get_domain():
protocol = 'https' if settings.SITE_SSL else 'http'
return '{}://{}'.format(protocol, settings.SITE_DOMAIN)
Первая функция просто предоставляет нам доступ к системному рандому, если это возможно, иначе вызывает стандартный алгоритм "Вихрь Мерсенна" каждый раз с новой затравкой.
Следующая функция позволяет правильно сериализовать дату в обычную строку. Например, данную функцию мы будем использовать при сохранении информации, в том числе меток времени в сессиях пользователей, так как последние при сохранении использует формат JSON.
Заключительная функция просто возвращает нам правильный протокол и используемый домен. Например, при генерации ссылки для подтверждения адреса электронной почты.
Переходим в каталог accounts и создаем файл utils.py:
import datetime
from django.utils.crypto import get_random_string
from django.utils import timezone
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.utils.dateparse import parse_datetime
from django.conf import settings
from core.utils import serializing_datetime, get_domain
from .models import User
class Vcode:
def __init__(self, request):
self.request = request
self.vcode_name = 'accounts_vcode'
self.user_pk = None
self.data = None
def send(self, user, action, data=None):
vcode = get_random_string()
self.request.session[self.vcode_name] = [vcode, action, data, user.pk, serializing_datetime(timezone.now())]
subject = render_to_string('accounts/vcode/subject.txt')
message = render_to_string('accounts/vcode/{action}.txt'.format(action=action), {
'domain': get_domain(),
'vcode': vcode,
})
addressee = data['email'] if action == 'change_email' else user.email
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [addressee])
def is_valid(self, code, action):
if self.request.session.get(self.vcode_name, False):
# получаем и удаляем вкод (одноразовый код)
true_code, true_action, self.data, self.user_pk, created = self.request.session.pop(self.vcode_name)
expiration_date = parse_datetime(created) + datetime.timedelta(seconds=settings.ACCOUNTS_VCODE_TIMEOUT)
if code == true_code and action == true_action and expiration_date > timezone.now():
return True
def get_inactive_user(self):
return User.inactive.get(pk=self.user_pk)
def get_content(self):
return User.active.get(pk=self.user_pk), self.data
Метод send принимает объект пользователя, название действия для подтверждения и дополнительные данные, например, новый адрес электронный почты.
Затем мы вызываем стандартную функцию для генерации случайной строки, здесь можно поиграться с настройками, например, ввести ограничение на количество символов или задать свой список для генерации.
Далее мы сохраняем в сессию пользователя сгенерированный код подтверждения, название действия, дополнительные данные, ID пользователя и временную метку от текущего момента.
Указываем путь к шаблонам для генерации отправляемого письма. Заголовок сообщения будет общий для всех, а вот содержимое будет меняться в зависимости от совершаемого действия. Сами шаблоны мы будем создавать позже, в соответствующем разделе.
После генерации письма переходим к отправлению используя стандартную функцию send_mail которая принимает: заголовок и содержимое письма, и электронные адреса, от кого и кому.
Метод is_valid принимает код и название проверяемого действия. Получаем из сессии ранее сгенерированные данные путем изъятия. Код в виде ссылке подтверждения будет либо копироваться и вставляться в браузер, либо вызываться сразу из письма. Риск неправильного ввода кода ничтожен и скорее причина будет в "просрочке" проверочного кода, в таком случае пользователь сможет запросить ссылку для подтверждения заново.
И в заключении get_inactive_user мы будем вызывать, когда пользователь успешно подтвердил свой адрес электронной почты, а метод get_content для действий по смене информации о пользователе, например, смена пароля.