django-allauth icon indicating copy to clipboard operation
django-allauth copied to clipboard

CustomSignupForm method signup() doesn't get called

Open omni-vi opened this issue 5 years ago • 3 comments

Thank you for django-allauth :-)

I tried creating a custom signup form like this

class CustomSignupForm(SignupForm):
    first_name = forms.CharField(max_length=30, label="First Name")
    last_name = forms.CharField(max_length=150, label="Last Name")
    company = forms.CharField(max_length=50, label="Company Name")

    def signup(self, request, user):
        print("hello "*10)
        user.first_name = self.cleaned_data["first_name"]
        user.last_name = self.cleaned_data["last_name"]

        user.profile.company = self.cleaned_data['company']
        user.save()

and I linked it in settings.py with

ACCOUNT_FORMS = {"signup": "users.forms.CustomSignupForm"}

I noticed that the form contains the additional fields but the signup() method doesn't get called. There ist no debug output and the company is not saved. first_name and last_name are saved, giving the impression that signup() is called when in fact they are saved by the adapter of the model. Took me a while to figure that out :-/.

Having dug a bit deeper, I think the problem is due to the custom_signup() method of the BaseSignupForm() class especially this line https://github.com/pennersr/django-allauth/blob/ef0774318993c2f7757df6ca2b962bc0df1d5674/allauth/account/forms.py#L350 Since custom_signup() is a method of BaseSignupForm() and not SignupForm() or even CustomSignupForm() the super() call always finds the signup() method of _DummyCustomSignupForm(), which is empty.

Overwriting custom_signup() in CustomSignupForm() fixes the problem.

class CustomSignupForm(SignupForm):
    first_name = forms.CharField(max_length=30, label=_("First Name"))
    last_name = forms.CharField(max_length=150, label=_("Last Name"))
    company = forms.CharField(max_length=50, label=_("Company Name"))

    def custom_signup(self, request, user):
        custom_form = self #use local signup()
        if hasattr(custom_form, 'signup') and callable(custom_form.signup):
            custom_form.signup(request, user)

    def signup(self, request, user):
        user.first_name = self.cleaned_data["first_name"]
        user.last_name = self.cleaned_data["last_name"]

        user.profile.company = self.cleaned_data['company']
        user.save()

My question: Is this even the correct approach? StackOverflow tells me I should inherit from forms.Form and have a signup() method. The documentation tells me I should subclass SignupForm but have a safe() method. custom_signup() says save() is deprecated.

Im using Django 3, Python 3.7 and django-allauth 0.41.0

omni-vi avatar Jan 10 '20 00:01 omni-vi

Probably unrelated problem, but substituting ACCOUNT_FORMS = {"signup": "users.forms.CustomSignupForm"} with ACCOUNT_SIGNUP_FORM_CLASS = "users.forms.CustomSignupForm" also does not work.

I tried it in the shell, and the form has the signup method.

~/django_frontend (master *%)$ ./manage.py shell -i ipython
Python 3.7.6 (default, Dec 30 2019, 19:38:26)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.11.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from importlib import import_module

In [2]: mod = import_module("users.forms")

In [3]: getattr(mod, "CustomSignupForm")
Out[3]: users.forms.CustomSignupForm

In [4]: fc_class = getattr(mod, "CustomSignupForm")

In [5]: hasattr(fc_class, 'signup')
Out[5]: True

When I set ACCOUNT_SIGNUP_FORM_CLASS, I get

~/django_frontend (master *%)$ ./manage.py shell -i ipython
Traceback (most recent call last):
  File "/Users/dev/.local/share/virtualenvs/django_frontend-uhtkvvFX/lib/python3.7/site-packages/allauth/account/forms.py", line 243, in _base_signup_form_class
    fc_class = getattr(mod, fc_classname)
AttributeError: module 'users.forms' has no attribute 'CustomSignupForm'
...
django.core.exceptions.ImproperlyConfigured: Module "users.forms" does not define a "CustomSignupForm" class

omni-vi avatar Jan 10 '20 01:01 omni-vi

I'm also seeing this. The docs say

ACCOUNT_SIGNUP_FORM_CLASS (=None)
A string pointing to a custom form class (e.g. ‘myapp.forms.SignupForm’) that is used during signup to ask the user for additional input (e.g. newsletter signup, birth date). This class should implement a def signup(self, request, user) method, where user represents the newly signed up user.

but other parts of the docs suggest using save not signup. So at least this reference to signup should be removed to save some headaches.

morenoh149 avatar May 24 '20 17:05 morenoh149

A slightly simpler solution is to directly override custom_signup with your custom code. @omni-vi 's code modified to show this:

class CustomSignupForm(SignupForm):
    first_name = forms.CharField(max_length=30, label=_("First Name"))
    last_name = forms.CharField(max_length=150, label=_("Last Name"))
    company = forms.CharField(max_length=50, label=_("Company Name"))

    def custom_signup(self, request, user):
        user.first_name = self.cleaned_data["first_name"]
        user.last_name = self.cleaned_data["last_name"]

        user.profile.company = self.cleaned_data['company']
        user.save()

nuarhu avatar Jun 20 '22 13:06 nuarhu

@pennersr Close as discussion


Users who want custom signup forms should simply inherit Signup Form

from allauth.account.forms import SignupForm

class CustomSignupForm(SignupForm):
    def save(self, request):
        # Ensure you call the parent class's save.
        # .save() returns a User object.
        user = super(CustomSignupForm, self).save(request)

        # Add your own processing here.

        # You must return the original result.
        return user

You can read more in the docs. You may or may not need to add an account adapter as well.

derek-adair avatar Sep 18 '23 02:09 derek-adair