0

I would like to register the user with some basic info (Name, Company...) and email. After the user submits the form i would like to send an email with a link that takes the user to a page to set his password.

I have tried with a solution from this question: Django AllAuth - How to manually send a reset-password email?. The email was send and the link inside the email is working, but i get an AssertionError on my site. The error is in /allauth/account/utils.py inside the setup_user_email(request, user, addresses) function. The line: assert not EmailAddress.objects.filter(user=user).exists()

What am i doing wrong here?

My models.py:

from django.contrib.auth.models import AbstractUser
from django.db import models


from allauth.account.views import PasswordResetView

from django.conf import settings
from django.dispatch import receiver
from django.http import HttpRequest
from django.middleware.csrf import get_token


class Kompanija(models.Model):
    naziv = models.CharField(max_length=50)
    adresa = models.CharField(max_length=50, blank=True)

    def __str__(self):
        return self.naziv

    class Meta:
        verbose_name = "Kompanija"
        verbose_name_plural = "Kompanije"

class CustomUser(AbstractUser):
    ime = models.CharField(max_length=30, default='')
    prezime = models.CharField(max_length=30, default='')
    kompanija = models.ForeignKey(Kompanija, on_delete=models.CASCADE, null=True, blank=True)
    is_premium = models.BooleanField('premium status', default=False)

    def __str__(self):
        return self.ime + " " + self.prezime

@receiver(models.signals.post_save, sender=settings.AUTH_USER_MODEL)
def send_reset_password_email(sender, instance, created, **kwargs):

    if created:
        request = HttpRequest()
        request.method = 'POST'

        if settings.DEBUG:
            request.META['HTTP_HOST'] = 'www.zahtjevi.com'
        else:
            request.META['HTTP_HOST'] = 'www.zahtjevi.com'

        request.POST = {
            'email': instance.email,
            'csrfmiddlewaretoken': get_token(HttpRequest())
        }
        PasswordResetView.as_view()(request)

My adapter.py:

from allauth.account.adapter import DefaultAccountAdapter
from .models import Kompanija

class UserAccountAdapter(DefaultAccountAdapter):

    def save_user(self, request, user, form, commit=True):
        user = super(UserAccountAdapter, self).save_user(request, user, form, commit=False)
        user.ime = form.cleaned_data.get('ime')
        user.prezime = form.cleaned_data.get('prezime')
        user.kompanija = Kompanija.objects.get(id=form.cleaned_data.get('kompanija'))
        user.is_active = True
        user.save()

My forms.py:

from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from allauth.account.forms import SignupForm
from .models import CustomUser, Kompanija

class CustomUserCreationForm(UserCreationForm):
    ime = forms.CharField(max_length=30, label='Ime')
    prezime = forms.CharField(max_length=30, label='Prezime')
    kompanija = forms.CharField(widget=forms.HiddenInput())

    class Meta(UserCreationForm):
        model = CustomUser
        fields = ('username', 'email', 'ime', 'prezime')

class CustomUserChangeForm(UserChangeForm):
    ime = forms.CharField(max_length=30, label='Ime')
    prezime = forms.CharField(max_length=30, label='Prezime')
    kompanija = forms.CharField(widget=forms.HiddenInput())

    class Meta:
        model = CustomUser
        fields = ('username', 'email', 'ime', 'prezime')


class CustomSignupForm(SignupForm):
    ime = forms.CharField(max_length=30, label='Ime')
    prezime = forms.CharField(max_length=30, label='Prezime')
    kompanija = forms.CharField(widget=forms.HiddenInput())

    class Meta:
        model = CustomUser

    def signup(self, request, user):
        user.ime = self.cleaned_data['ime']
        user.prezime = self.cleaned_data['prezime']
        user.kompanija = Kompanija.objects.get(id=self.cleaned_data['kompanija'])
        user.is_active = True
        user.save()
        return user


class CustomUserAdminForm(forms.ModelForm):
  class Meta:
    model = CustomUser
    fields = ('email', 'ime', 'prezime', 'username', 'kompanija', 'is_premium')

Update 1:

Error Traceback:

Environment:
Request Method: POST
Request URL: https://www.zahtjevi.com/accounts/signup/

Django Version: 2.2.3
Python Version: 3.7.0
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'debug_toolbar',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'korisnici.apps.KorisniciConfig',
 'pages.apps.PagesConfig',
 'projekti.apps.ProjektiConfig',
 'zahtjevi.apps.ZahtjeviConfig']
Installed 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',
 'debug_toolbar.middleware.DebugToolbarMiddleware']

Traceback:

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/generic/base.py" in view
  71.             return self.dispatch(request, *args, **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/utils/decorators.py" in _wrapper
  45.         return bound_method(*args, **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/decorators/debug.py" in sensitive_post_parameters_wrapper
  76.             return view(request, *args, **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py" in dispatch
  214.         return super(SignupView, self).dispatch(request, *args, **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py" in dispatch
  80.                                             **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py" in dispatch
  192.                                                           **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/generic/base.py" in dispatch
  97.         return handler(request, *args, **kwargs)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py" in post
  103.             response = self.form_valid(form)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py" in form_valid
  230.         self.user = form.save(self.request)

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/forms.py" in save
  407.         setup_user_email(request, user, [])

File "/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/utils.py" in setup_user_email
  254.     assert not EmailAddress.objects.filter(user=user).exists()

Exception Type: AssertionError at /accounts/signup/
Exception Value: 

Update 2:

The error is raised when the user submits the Signup form. In my forms.py i have a CustomSignupForm that is displayed to the user. The views called are from django-allauth (i have in my urls.py path('accounts/', include('allauth.urls')),

I tried to delete the save() from my forms and the error is still there, also if i delete the ACCOUNT_ADAPTER variable inside the settings.py the error is still there...

Update 3:

From django-debug-toolbar i see:

SELECT (1) AS `a` 
  FROM `account_emailaddress` 
 WHERE `account_emailaddress`.`email` LIKE 'dario@igl.hr' 
 LIMIT 1
  2 similar queries.   Duplicated 2 times.

/bin/user_wsgi_wrapper.py in __call__(202)
  app_iterator = self.app(environ, start_response)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/contrib/staticfiles/handlers.py in __call__(65)
  return self.application(environ, start_response)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/generic/base.py in view(71)
  return self.dispatch(request, *args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/decorators/debug.py in sensitive_post_parameters_wrapper(76)
  return view(request, *args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in dispatch(214)
  return super(SignupView, self).dispatch(request, *args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in dispatch(80)
  **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in dispatch(192)
  **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/generic/base.py in dispatch(97)
  return handler(request, *args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in post(103)
  response = self.form_valid(form)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in form_valid(230)
  self.user = form.save(self.request)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/forms.py in save(404)
  adapter.save_user(request, user, self)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/adapter.py in save_user(243)
  user.save()
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/contrib/auth/base_user.py in save(66)
  super().save(*args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/dispatch/dispatcher.py in send(175)
  for receiver in self._live_receivers(sender)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/dispatch/dispatcher.py in <listcomp>(175)
  for receiver in self._live_receivers(sender)
/home/filozof/nadzor/korisnici/models.py in send_reset_password_email(51)
  PasswordResetView.as_view()(request)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/generic/base.py in view(71)
  return self.dispatch(request, *args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/views/generic/base.py in dispatch(97)
  return handler(request, *args, **kwargs)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in post(103)
  response = self.form_valid(form)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/views.py in form_valid(644)
  form.save(self.request)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/forms.py in save(516)
  temp_key = token_generator.make_token(user)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/contrib/auth/tokens.py in make_token(21)
  return self._make_token_with_timestamp(user, self._num_days(self._today()))
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/django/contrib/auth/tokens.py in _make_token_with_timestamp(60)
  self._make_hash_value(user, timestamp),
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/forms.py in _make_hash_value(41)
  sync_user_email_addresses(user)
/home/filozof/.virtualenvs/django2/lib/python3.7/site-packages/allauth/account/utils.py in sync_user_email_addresses(350)
  and EmailAddress.objects.filter(email__iexact=email).exists():
Community
  • 1
  • 1
filozof
  • 91
  • 1
  • 6
  • Can you add some detail to your question: Which form and view is being used when? You show Your first sentence mentions a form being submitted, which one is that? Also when exactly is the error produced? It looks like a POST is submitted, but when is that POST submitted? (what does the user have to do to submit the post, which form is being filled?) Looking at your trace, non of your own views is being called on that error, so I don't understand how your own custom forms are relevant here. – dirkgroten Aug 05 '19 at 10:03
  • The error seems to indicate that you're creating the same user twice. Check the flow and when what is being called, (a debugger to step-by-step go through the process would help, otherwise add some print statements to console on lines where you're saving the user) I'm not sure why you duplicate user saving in your `signup()` method and in your `save_user()` adapter method. Again, looks like your code isn't being used at all in your error trace, but somewhere you're doing a double signup. – dirkgroten Aug 05 '19 at 10:08
  • I have added some more info in my question, im note sure where i'm saving the user twice...i have just installed the django-debug-toolbar and inside i see all SQL queries, one is flaged as ```2 similar queries. Duplicated 2 times.``` I will add this to the original question. – filozof Aug 05 '19 at 10:25
  • I just tried with the ```print``` to console before user.save() in the ```adapter.py``` and in the ```forms.py```, the only one displayed is from the ```adapter.py``` – filozof Aug 05 '19 at 10:47

1 Answers1

0

The problem is with the allauth SignupForm's save() method: It first saves the user, then right at the end it sets up the user's EmailAddress (it has a separate model for the user's email addresses).

def save(self, request):
    ...
    adapter.save_user(request, user, self)  # this calls your post-save signal
    self.custom_signup(request, user)  # this should call your custom signup method in your custom signup form
    setup_user_email(request, user, [])  # this raises the exception
    return user

Now the password reset view/form ensure the user's EmailAddress is setup properly (so it adds the user.email as a EmailAddress model). Since this is done by your post_save signal, it happens before setup_user_email is called.

You should:

  1. Remove your post_save signal handler because it's always going to be called before the setup_user_email.
  2. Move the code to send the reset password email into your form's save() method (calling the above mentioned super().save() method first, then send the password reset email.

    # in your custom form
    def save(self, request):
        user = super().save(request)
        new_request = HttpRequest()
        new_request.method = 'POST'
        new_request.META['HTTP_HOST'] = 'www.zahtjevi.com'
    
        new_request.POST = {
            'email': user.email,
            'csrfmiddlewaretoken': get_token(new_request)
        }
        PasswordResetView.as_view()(new_request)
    
        return user
    

Note: I have the feeling your custom form isn't being used at all, have you defined it in ACCOUNT_FORMS as specified here?

Note2 (EDITED): Your form's signup() method is superflous, it's doing the same thing as your custom adapter's save_user() method. Remove one of the two, probably the form's signup() method so you don't hit the db twice to save the user object.

dirkgroten
  • 20,112
  • 2
  • 29
  • 42
  • Yes, i have it in the ```settings.py```, ```ACCOUNT_FORMS = {'signup': 'korisnici.forms.CustomSignupForm',}```, can you please provide an example how to do your suggestion? I'm not sure how this code should look like inside the form... – filozof Aug 05 '19 at 11:06
  • Thanks! Now the user is saved and the email delivered, but im getting a Invalid token error when clicking the link...? – filozof Aug 05 '19 at 15:05
  • I have moved all that code to ```adapter.py```, just to see if it will be anything diferent. The code was after ```user.save()```. The result, again AssertionError but the link inside the email is working... Is it possible that the problem is with how the user is set (```user = super().save(request)```)? – filozof Aug 05 '19 at 19:57