При разработке новой системы регистрации необходимо учитывать текущие тенденции и по возможности максимально отсечь неудачные варианты, типа: уникальность имен, исключительно латинский алфавит и отсутствие возможности указать, например, настоящее или двойное имя.
А для того чтобы пользователи смогли различать друг друга, мы на финальной стадии регистрации добавим им цифровой код. Например, Анна-Мария #777.
Формы
Начнем с создания форм, в директории приложения accounts добавляем файл forms.py:
import re
from django import forms
from django.db.utils import IntegrityError
from django.contrib.auth.password_validation import validate_password
from core.utils import get_random
from .models import User
from .utils import Vcode
class JoinForm(forms.ModelForm):
username = forms.CharField(label='Псевдоним или настоящее имя', min_length=2, max_length=50,
widget=forms.TextInput(attrs={'autofocus': ''}))
email = forms.EmailField(label='Адрес электронной почты', max_length=254,
error_messages={'unique': 'Данный адрес электронной почты уже используется'})
password = forms.CharField(label='Пароль', max_length=255, strip=False, widget=forms.PasswordInput)
field_order = ['username', 'email', 'password']
class Meta:
model = User
fields = ['email']
def clean_username(self):
username = self.cleaned_data.get('username')
rus_alphabet = 'а-яА-ЯёЁ'
eng_alphabet = 'a-zA-Z'
alphabets = rus_alphabet + eng_alphabet
if not re.search(r'^[{alphabets}][{alphabets}\d -]*[{alphabets}\d]$'.format(alphabets=alphabets), username):
raise forms.ValidationError('Псевдоним может содержать только буквы латинского и кириллического алфавитов, '
'цифры, пробелы и знаки дефиса. Начинаться с буквы и заканчиваться на цифру '
'или букву')
if re.search(r'[{rus_alphabet}]'.format(rus_alphabet=rus_alphabet), username) and \
re.search(r'[{eng_alphabet}]'.format(eng_alphabet=eng_alphabet), username):
raise forms.ValidationError('Псевдоним не может содержать одновременно буквы латинского и кириллического '
'алфавитов (нельзя смешивать алфавиты)')
if re.search(r' {2,}|-{2,}', username):
raise forms.ValidationError('Псевдоним не может содержать два или более пробелов (и дефисов) подряд')
if username.count(' ') > 3 or username.count('-') > 3:
raise forms.ValidationError('Псевдоним не может содержать более трех пробелов (и дефисов) для разделения '
'слов')
return username
def clean_password(self):
# проверяем пароль с помощью списка валидаторов объявленного в settings.AUTH_PASSWORD_VALIDATORS
password = self.cleaned_data.get('password')
user = User(username=self.cleaned_data.get('username'), email=self.cleaned_data.get('email'))
validate_password(password, user)
return password
def save(self, commit=True, request=None):
user = super(JoinForm, self).save(commit=False)
user.set_password(self.cleaned_data['password'])
user.is_active = False
if commit:
# создаем список кодов от 100 до 999 и затем перемешиваем
codes = [c for c in range(100, 999 + 1)]
get_random().shuffle(codes)
for code in codes:
user.username = '{} #{}'.format(self.cleaned_data['username'], code)
if not User.objects.filter(username=user.username).exists():
try: # пробуем сохранить уникальный псевдоним
user.save()
except IntegrityError:
# начинаем заново, видимо нас опередили, БД сообщает, что значения не уникальны!
continue
break
else:
raise ValueError('Цикл кодов завершен, сгенерировать уникальный псевдоним не удалось')
# отправляем проверочный код пользователю
Vcode(request).send(user, 'join')
return user
class JoinResendForm(forms.Form):
email = forms.EmailField(label='Адрес электронной почты', max_length=254,
widget=forms.EmailInput(attrs={'autofocus': ''}))
def __init__(self, *args, **kwargs):
super(JoinResendForm, self).__init__(*args, **kwargs)
self.user = None
def clean_email(self):
email = self.cleaned_data.get('email')
try:
self.user = User.inactive.get(email=email)
except User.DoesNotExist:
raise forms.ValidationError('Пользователя с данным адресом электронной почты не существует '
'или он уже успешно произвел процедуру активации '
'или по каким-то причинам был отключен')
return email
def send_vcode(self, request):
Vcode(request).send(self.user, 'join')
Формы в Django позволяют подготавливать, принимать и обрабатывать данные от пользователей с последующим сохранением результатов.
Форма регистрации пользователя
JoinForm мы наследуем от класса ModelForm, это позволит нам построить форму по уже описанной модели User и немного сократить количество кода, например, использовать проверку на уникальность адреса электронной почты — автоматически.
Далее мы переходим к созданию полей формы с указанием читабельного названия, минимальной и максимальной длины, а также установки автофокуса поля формы.
В поле пароля мы описываем виджет для сокрытия ввода содержимого и отменяем автоматическое удаление пробелов в начале и в конце введенного значения.
Затем мы задаем порядок отображения полей формы.
В классе Meta мы указываем с какими полями модели мы хотели бы поработать.
Переходим к дополнительным проверкам полей и начнем с имени пользователя.
Вначале проверки необходимо получить значения от стандартных функций очистки, типа минимальной и максимальной длин. Затем мы разносим алфавиты в разные переменные для многократного использования в проверках и одновременно повышаем удобство чтения регулярных выражений.
Разберем первое регулярное выражение. Первоначальный символ должен входить в русский или латинский алфавиты. Второй символ или более, или отсутствие такового вообще, входят помимо алфавитов: цифры, пробел и знак дефиса. Окончание имени содержит все тот же набор, но уже без использования дефиса или пробела.
Например, можно использовать имя Анна-Мария, Елизавета Первая, R2-D2.
Второе регулярное выражение проверят не смешал ли пользователей алфавиты при вводе имени пользователя. Например, буквы "c" в обоих алфавитах выглядит одинаково, и при наборе имени человек может совершить ошибку. Или вовсе недобросовестный пользователь может попытаться выдать себя за другого человека.
Цель следующего регулярного выражения не допустить два и более пробелов или дефисов подряд. Например, дефис нужен для использования двойных имен, а пробел чтоб отделить имя от фамилии, но следующие примеры, уже злоупотребление или возможная попытка кражи личности: Анна---Мария или Елизавета Первая, в последнем случае наличие второго пробела между словами сложно определить на первый взгляд.
И последнее регулярное выражение призвано не допускать более двух пробелов или дефисов в имени. Например, Анна-Мария Первая, допустима, конструкция содержит двойное имя и фамилию, а вот Е-л-и-з-а-в-е-т-а — это уже злоупотребление.
Дополнительную проверку пароля осуществляем стандартными средствами Django, в файле настроек содержится список валидаторов, которые можно расширить или дополнить собственными проверками.
Например, по умолчанию это проверки на минимальную длину, принадлежность не только к цифрам, на совпадение с наиболее популярными паролями. И проверка на сходство пароля с другими полями пользователя, типа имени, адреса электронной почты и так далее.
Переходим к сохранению формы. Переопределяем стандартный метод и затем вызывая его без первоначального сохранения мы получаем объект пользователя для дальнейшего редактирования. Задаем пароль через специальный метод и выставляем флаг неактивности аккаунта, так как пользователю необходимо подтвердить владения указанном адресом электронной почты.
Затем необходимо сгенерировать цифрой код для того чтобы пользователи c одинаковым именем смогли различать друг друга.
Создаем список кодов от 100 до 999 простым циклом for и перемешиваем его в случайном порядке, через специальную функцию get_random, о ней чуть позже. Обходим список добавляя цифровой код к имени пользователя и одновременно проверяем не занят ли этот псевдоним в базе данных. В случае успеха сохраняем данный объект и отравляем письмо с просьбой подтвердить аккаунт. Вспомогательные объекты и функции Vcode и get_random мы опишем полностью в разделе Утилиты.
Если цикл завершился и уникальный псевдоним не был создан, значит имя очень сильно популярно, или его специально массово регистрируют. Подобные случаи лучше расследовать вручную и в случае необходимости принимать соответствующие меры. Например, чем больше аудитория проекта, тем больше потребуется диапазон уникальных значений. Наборы из тысяч или десятков тысяч кодов подойдут для самых крупных проектов с многомилионными регистрациями.
Форма повторной отправки подтверждения
JoinResendForm это обычная форма, которую необходимо будет вызвать в случае если пользователь не смог по каким-либо причинам потвердеть свой аккаунт.
В методе проверки адреса электронной почты, необходимо удостовериться, что пользователь существует и находиться в статусе ожидания подтверждения почты, а не заблокирован администрацией.
И добавляем отдельный метод для отправки подтверждения, вызывая его в случае правильности заполнения формы.