I have problems with saving my custom signup form using django-allauth. The form displays correctly on the page. When I click the "register" button, a POST request is sent which does not save anything to the database. The save()
method is not called on the form.
models.py:
import uuid
from django.contrib.auth import password_validation
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.core.exceptions import ValidationError
from django.core.mail import send_mail
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .managers import UserManager
from edu.models import Course, Group
class User(AbstractBaseUser, PermissionsMixin):
MODERATION_STATUS_SIGNUP = 'signup'
MODERATION_STATUS_ON_REVIEW = 'on_review'
MODERATION_STATUS_REJECTED = 'rejected'
MODERATION_STATUS_APPROVED = 'approved'
MODERATION_STATUSES = [
(MODERATION_STATUS_SIGNUP, 'В процессе регистрации'),
(MODERATION_STATUS_ON_REVIEW, 'На рассмотрении'),
(MODERATION_STATUS_REJECTED, 'Отклонен'),
(MODERATION_STATUS_APPROVED, 'Подтвержден'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
secret_hash = models.UUIDField('хэш', default=uuid.uuid4, blank=True, editable=False)
email = models.EmailField('email', unique=True)
first_name = models.CharField('имя', max_length=150, blank=False)
middle_name = models.CharField('отчество', max_length=150, blank=False)
last_name = models.CharField('фамилия', max_length=150, blank=False)
is_student = models.BooleanField('курсант', default=False)
is_elder = models.BooleanField('старшина', default=False)
is_ct = models.BooleanField('классный руководитель', default=False)
is_hc = models.BooleanField('воспитатель', default=False)
is_watch = models.BooleanField('работник вахты', default=False)
is_staff = models.BooleanField('работник института', default=False)
is_admin = models.BooleanField('администратор', default=False)
is_active = models.BooleanField('активный пользователь', default=False)
moderation_status = models.CharField(
'статус аккаунта',
max_length=32,
choices=MODERATION_STATUSES,
default=MODERATION_STATUS_SIGNUP,
)
date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
updated = models.DateTimeField('обновлено', auto_now=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'middle_name', 'last_name']
class Meta:
abstract = False
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def save(self, *args, **kwargs):
self.first_name = self.first_name.capitalize()
self.middle_name = self.middle_name.capitalize()
self.last_name = self.last_name.capitalize()
status_counter = sum([self.is_student, self.is_elder, self.is_watch, self.is_staff])
if status_counter > 1:
raise ValidationError(
'Только 1 значение должно быть выбрано: is_student, is_elder, is_watch, is_staff'
)
if status_counter < 1:
raise ValidationError(
'Хотя бы 1 значение должно быть выбрано: is_student, is_elder, is_watch, is_staff'
)
super().save(*args, **kwargs)
if self._password is not None:
password_validation.password_changed(self._password, self)
def get_absolute_url(self):
return f'/id{self.pk}'
def get_full_name(self):
full_name = '%s %s %s' % (self.first_name, self.middle_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email, [self.email], **kwargs)
def get_related_by_role(self, role=None, **kwargs):
user = None
if self.is_student:
if role == 'elder':
user = StudentProfile.objects.get(
user__is_elder=True,
group=StudentProfile.objects.get(user=self).group,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED,
**kwargs
)
elif role == 'ct':
user = ClassTeacherProfile.objects.get(
user__is_ct=True,
groups=StudentProfile.objects.get(user=self).group,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED,
**kwargs
)
elif role == 'hc':
user = HeadOfCourseProfile.objects.get(
user__is_hc=True,
courses=StudentProfile.objects.get(user=self).group.course,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED,
**kwargs
)
return user
@classmethod
def active_users(cls, method=None, **kwargs):
queryset = cls.objects.filter(is_active=True, moderation_status=User.MODERATION_STATUS_APPROVED, **kwargs)
if method == 'get':
queryset = queryset.get()
return queryset
class StudentProfile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='пользователь', related_name='student')
birthdate = models.DateField('дата рождения', help_text='Используйте формат "ДД.ММ.ГГГГ"')
group = models.ForeignKey(Group, on_delete=models.PROTECT, verbose_name='учебная группа')
phone = models.CharField('телефон', max_length=12, unique=True, help_text='Используйте формат +7xxxxxxxxxx')
is_hostel = models.BooleanField('проживает в общежитии', default=False)
class StudentRelativeProfile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='пользователь', related_name='relative')
first_name = models.CharField('имя', max_length=150)
middle_name = models.CharField('отчество', max_length=150)
last_name = models.CharField('фамилия', max_length=150)
phone = models.CharField('телефон', max_length=12, help_text='Используйте формат +7xxxxxxxxxx')
type = models.CharField('кем приходится', max_length=100)
class ClassTeacherProfile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='пользователь', related_name='ct')
groups = models.ManyToManyField(Group, verbose_name='учебные группы')
class HeadOfCourseProfile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='пользователь', related_name='hc')
courses = models.ManyToManyField(Course, verbose_name='курсы')
class StaffProfile(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='пользователь', related_name='staff')
can_moderate_users = models.BooleanField('право модерировать пользователей', default=False)
managers.py:
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
if not email:
raise ValueError('Поле email не может быть пустым! ')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Администратор должен иметь is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Администратор должен иметь is_superuser=True.')
return self._create_user(email, password, **extra_fields)
signals.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import ClassTeacherProfile, HeadOfCourseProfile, StaffProfile, StudentProfile, User
from .helpers import moderate_new_user, delete_user
from .notifications.models import UserNotificationSettings
@receiver(post_save, sender=User)
def create_or_update_user(instance, created, **kwargs):
if created:
UserNotificationSettings.objects.create(user=instance)
if instance.is_student:
StudentProfile.objects.create(user=instance)
if instance.is_ct:
ClassTeacherProfile.objects.create(user=instance)
if instance.is_hc:
HeadOfCourseProfile.objects.create(user=instance)
if instance.is_staff:
StaffProfile.objects.create(user=instance)
else:
# Проверяем, если у пользователя поле moderation_status установлено как MODERATION_STATUS_ON_REVIEW
if instance.moderation_status is User.MODERATION_STATUS_ON_REVIEW :
# Проверяем, что у пользователя выбран только 1 статус
if sum(
[
instance.is_student,
instance.is_elder,
instance.is_watch,
instance.is_staff
]
) == 1:
moderator = None
if instance.is_student or instance.is_elder:
# отправляем данные пользователя на проверку классному руководителю
if instance.get_related_by_role(role='ct') is not None:
moderator = instance.get_related_by_role(role='ct')
elif instance.get_related_by_role(role='hc') is not None:
moderator = instance.get_related_by_role(role='hc')
elif instance.get_related_by_role(role='elder') is not None:
moderator = instance.get_related_by_role(role='elder')
elif StaffProfile.objects.filter(
can_moderate_users=True,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED
).exists():
moderator = StaffProfile.objects.filter(
can_moderate_users=True,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED
).order_by('?').first()
if instance.is_ct or instance.is_hc or instance.is_watch:
# отправляем данные пользователя на проверку сотруднику института
if StaffProfile.objects.filter(
can_moderate_users=True,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED
).exists():
moderator = StaffProfile.objects.filter(
can_moderate_users=True,
user__is_active=True,
user__moderation_status=User.MODERATION_STATUS_APPROVED
).order_by('?').first()
if instance.is_staff:
# отправляем данные пользователя на проверку администратору
if User.active_users(is_admin=True).exists():
moderator = User.active_users(is_admin=True).order_by('?').first()
moderate_new_user(user=instance, moderator=moderator)
else:
# Отправляем пользователю письмо, что регистрация не прошла проверку и пользователь удален
delete_user(user=instance, reason='new_user_roles_validation_failed')
forms.py:
from datetime import date
from allauth.account.forms import SignupForm
from django import forms
from edu.models import Course, Group
from users.models import StudentProfile, User
class UserCreationForm(SignupForm):
first_name = forms.CharField(label='Имя', max_length=150)
middle_name = forms.CharField(label='Отчество', max_length=150)
last_name = forms.CharField(label='Фамилия', max_length=150)
is_student = forms.BooleanField(label='Курсант', required=False)
is_elder = forms.BooleanField(label='Старшина', required=False)
is_ct = forms.BooleanField(label='Классный руководитель', required=False)
is_hc = forms.BooleanField(label='Начальник курса', required=False)
is_watch = forms.BooleanField(label='Работник вахты', required=False)
is_staff = forms.BooleanField(label='Сотрудник института', required=False)
# для курсанта
birth_date = forms.DateField(label='Дата рождения')
group = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, help_text="Выберете вашу группу", queryset=Group.objects.all())
phone = forms.CharField(label='Телефон', max_length=12, help_text='Используйте формат +7xxxxxxxxxx')
is_hostel = forms.BooleanField(label='Общежитие')
# для классного руководителя
groups = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, help_text="Выберете ваши группы", queryset=Group.objects.all())
# для воспитателя
courses = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, help_text="Выберете ваши курсы", queryset=Course.objects.all())
# для сотрудника института
can_moderate_users = forms.BooleanField(label='Право модерировать пользователей', help_text='Выберите, если хотите подтвержать или отклонять регистрацию новых пользователей', required=False)
field_order = ('first_name', 'middle_name', 'last_name', 'is_student', 'is_elder', 'is_ct', 'is_hc', 'is_watch', 'is_staff', 'birth_date', 'group', 'phone', 'is_hostel', 'groups', 'courses', 'can_moderate_users', 'email', 'password1', 'password2')
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
self.fields['first_name'].widget = forms.TextInput(attrs={'placeholder': 'Иван'})
self.fields['middle_name'].widget = forms.TextInput(attrs={'placeholder': 'Иванович'})
self.fields['last_name'].widget = forms.TextInput(attrs={'placeholder': 'Иванов'})
# для курсанта
self.fields['birth_date'].widget = forms.SelectDateWidget(
empty_label=("Выберете год", "Выберете месяц", "Выберете день"),
years=range(1980, date.today().year)
)
self.fields['phone'].widget = forms.TextInput(attrs={'placeholder': '+79000000000'})
def save(self, request):
user = super(UserCreationForm, self).save(request)
user.first_name = self.cleaned_data['first_name']
user.middle_name = self.cleaned_data['middle_name']
user.last_name = self.cleaned_data['last_name']
user.is_student = self.cleaned_data['is_student']
user.is_elder = self.cleaned_data['is_elder']
user.is_ct = self.cleaned_data['is_ct']
user.is_hc = self.cleaned_data['is_hc']
user.is_watch = self.cleaned_data['is_watch']
user.is_staff = self.cleaned_data['is_staff']
user.save()
if user.is_student:
user.student.birth_date = self.cleaned_data['birth_date']
user.student.group = self.cleaned_data['group']
user.student.phone = self.cleaned_data['phone']
user.student.is_hostel = self.cleaned_data['is_hostel']
user.student.save()
if user.is_ct:
groups = self.cleaned_data['groups']
user.ct.groups.set(groups)
user.ct.save()
if user.is_hc:
courses = self.cleaned_data['courses']
user.hc.courses.set(courses)
user.hc.save()
user.moderation_status = User.MODERATION_STATUS_ON_REVIEW
user.save()
return user
adapter.py:
from allauth.account.adapter import DefaultAccountAdapter
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
from allauth.account.utils import user_email, user_field
data = form.cleaned_data
first_name = data.get("first_name")
middle_name = data.get("middle_name")
last_name = data.get("last_name")
is_student = data.get("is_student")
is_elder = data.get("is_elder")
is_ct = data.get("is_ct")
is_hc = data.get("is_hc")
is_watch = data.get("is_watch")
is_staff = data.get("is_staff")
email = data.get("email")
user_email(user, email)
if first_name:
user_field(user, "first_name", first_name)
if middle_name:
user_field(user, "middle_name", middle_name)
if last_name:
user_field(user, "last_name", last_name)
if is_student:
user_field(user, "is_student", is_student)
if is_elder:
user_field(user, "is_elder", is_elder)
if is_ct:
user_field(user, "is_ct", is_ct)
if is_hc:
user_field(user, "is_hc", is_hc)
if is_watch:
user_field(user, "is_watch", is_watch)
if is_staff:
user_field(user, "is_staff", is_staff)
if "password1" in data:
user.set_password(data["password1"])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
user.save()
return user
settings.py:
POSTGRESQL_DATABASE = False
if not POSTGRESQL_DATABASE:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': '****',
'USER': '****',
'PASSWORD': '****',
'HOST': '****',
'PORT': '****',
}
}
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.vk',
...
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
]
AUTH_USER_MODEL = 'users.User'
ACCOUNT_ADAPTER = 'users.adapter.AccountAdapter'
ACCOUNT_FORMS = {'signup': 'users.forms.UserCreationForm'}
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = '/'
ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = '/'
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
# ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https'
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 3
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
SOCIALACCOUNT_AUTO_SIGNUP = False
This is what the console outputs when trying to open the signup page and save the form:
[06/Oct/2020 23:10:13] "GET /signup/ HTTP/1.1" 200 7145
[06/Oct/2020 23:11:04] "POST /signup/ HTTP/1.1" 200 7420
I tried using solutions from here, here and here - nothing solved the problem. The official documentation suggests using this method, but it also does not work:
# forms.py
from allauth.account.forms import SignupForm
class MyCustomSignupForm(SignupForm):
def save(self, request):
# Ensure you call the parent class's save.
# .save() returns a User object.
user = super(MyCustomSignupForm, self).save(request)
# Add your own processing here.
# You must return the original result.
return user
# settings.py
ACCOUNT_FORMS = {'signup': 'mysite.forms.MyCustomSignupForm'}