1

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'}
Sergey S.
  • 111
  • 2

0 Answers0