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

ACCOUNT_FORMS['signup'] != ACCOUNT_SIGNUP_FORM_CLASS

Open dyve opened this issue 6 years ago • 15 comments

I needed a django-allauth signup form with a few extra fields. I'd done this a few times, and thought it wouldn't be a problem. There is also a good explanation by @pennersr here: https://stackoverflow.com/a/12308807/117831

Having noticed the new ACCOUNT_FORMS setting, I thought it woud be better to start using this instead of the ACCOUNT_SIGNUP_FORM_CLASS I'd used before.

# Old method in `settings.py`
ACCOUNT_SIGNUP_FORM_CLASS = 'app.users.forms.MySignUpForm'

# New method in `settings.py`
ACCOUNT_FORMS = {
    'signup': 'app.users.forms.MySignUpForm'
}

The form seemed to work, but only my extra fields showed up. After trying some stuff, I couldn't get the new method to work. I reverted to using ACCOUNT_SIGNUP_FORM_CLASS, and everything worked like a charm.

From the documentation, I gathered that both settings should provide the same results, but the documentation does not actually say this. Or I missed something.

I would like to know if these settings are supposed to do the same thing.

If yes, then there is a bug to be fixed. If no, then the documentation needs to be made more explicit.

Thanks for a great job on this project!

dyve avatar Jun 05 '18 05:06 dyve

You can use the new ACCOUNT_FORMS method by extending the SignupForm class into your custom form class. You can follow the below example.

from allauth.account.forms import SignupForm
from django import forms

class SignupForm(SignupForm):
    first_name = forms.CharField(max_length=30, label='First Name')
    last_name = forms.CharField(max_length=30, label='Last Name')

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

return user in the above code is very important as it passes the user objects to other modules for other verification. Finally, add the settings in settings.py ACCOUNT_FORMS = {'signup': 'YourProject.forms.SignupForm'}

gajeshbhat avatar Jul 05 '18 04:07 gajeshbhat

I am not sure this is what @dyve meant.

Like him, I think having this new ACCOUNT_FORMS is way more explicit (makes more sense to override the save method of the form than create a custom signup one) and concised (having everything at the same place).

Though, I wonder why the need for keeping both syntaxes? Is it just not to mess up with people who used the ACCOUNT_SIGNUP_FORM_CLASS parameter? If it is deprecated, I thing it would be a great thing to specify that in the doc, because in my opinion it is a bit confusing having both of them. Also, when looking for on google how to make a custom user profile using django-allauth, first result is the stackoverflow one, with the ACCOUNT_SIGNUP_FORM_CLASS parameter.

Maybe @pennersr could help us with that?

KrazyMax avatar Sep 22 '18 23:09 KrazyMax

@KrazyMax Totally forgot about this, but that's exactly what I meant. Issue is still relevant I think (about to bump into it again on a new project).

dyve avatar Sep 23 '18 08:09 dyve

ACCOUNT_SIGNUP_FORM_CLASS needs to go away. I have been on divergent paths for the past hour and decided to look this up. The "old" way should be totally removed.

jensenbox avatar May 12 '19 17:05 jensenbox

I agree, clarity would be best.

dyve avatar May 13 '19 05:05 dyve

I want to add more fields like phone number, address etc by '@gajeshbhat' your process. But it's not add on my database..

biplobmahadi avatar Aug 07 '19 11:08 biplobmahadi

I believe you cannot override the signup method if you use ACCOUNT_FORMS[. The python MRO, results in the signup method of the dummy class taking preference. I'm overriding the save method without issue.

If you provide a class for ACCOUNT_SIGNUP_FORM_CLASS then the dummy class won't exist, and your signup method will be called.

cdosborn avatar Aug 19 '19 16:08 cdosborn

yes, the signup won't get called. Better to override the save.

vlinhart avatar Sep 13 '19 12:09 vlinhart

Displaimer: I'm new to this library.

Just hit an issue with this as well, I tried defining signup(self, request, user) with a form defined with ACCOUNT_FORMS['signup'], but it was never called.

The reason why I thought of using signup was because of the custom_signup method which is called prior to sending out any email. It may be desired to perform some logic before finalising the registration process, so custom_signup seems adequate, and lead to my discovery of signup.

If ACCOUNT_SIGNUP_FORM_CLASS needs to be deprecated, so be it, however it may be useful to keep a signup-like hook for performing actions prior to finalising the registration.

Also, in my case, specific actions need to be performed whether the user signs up with login/password or socially, so arguably the ACCOUNT_SIGNUP_FORM_CLASS::signup would be the right place to perform these actions as ACCOUNT_FORMS overrides are just for non-social accounts, while ACCOUNT_SIGNUP_FORM_CLASS is the base for both social and non-social sign-ups.

My working solution ends up being:

# settings.py

ACCOUNT_FORMS = {'signup': 'forms.AccountSignupForm'}
ACCOUNT_SIGNUP_FORM_CLASS = 'forms.BaseSignupForm'

# forms.py

from allauth.account.forms import SignupForm
from django import forms

class BaseSignupForm(forms.Form):
    def signup(self, request, user):
        """Custom sign up logic common to account and socialaccount"""
        pass

class AccountSignupForm(SignupForm):
    """Includes first and last name in account SignupForm"""
    first_name = forms.CharField(max_length=30)
    last_name = forms.CharField(max_length=30)

This is a simplified example, and I have not actually tested the social sign-up yet.

FMCorz avatar Oct 23 '19 08:10 FMCorz

I just wasted about two hours on this issue. @pennersr what is the official methodology to override the signup form? Can we get this documented in the official documentation, please? I've seen Stack Overflow posts from 2012, 2015, 2017, and now this issue with solutions from 2018 and 2019. None of them worked for me.

It is unclear to me why custom_signup calls signup. In fact, that doesn't work in my case due to inheritance order, as pointed out by @dyve. I opted to override custom_signup as that seems more Pythonic than calling a method via string:

# settings.py
ACCOUNT_FORMS = {'signup': 'friendly_reminder.apps.core.forms.CoreSignupForm'}

# forms.py
from allauth.account.forms import SignupForm
from django import forms


class CoreSignupForm(SignupForm):
    first_name = forms.CharField(max_length=30, label='First Name')
    phone_number = forms.CharField(max_length=50, label='Phone Number')

    def custom_signup(self, request, user):
        user.first_name = self.cleaned_data['first_name']
        user.phone_number = self.cleaned_data['phone_number']
        user.save()
        return user

clintonb avatar Nov 26 '19 08:11 clintonb

Is it advisable to write an adapter? I find it comfortable though....

Chukwunazaekpere avatar Aug 04 '20 10:08 Chukwunazaekpere

If you've inherited from allauth's SignupForm, then, do the following....

From allauth.account.adapter import

get_adapter

From allauth.account.forms import

SignupForm as UserSignupForm

class SignupForm(UserSignupForm):

first name = forms.Charfield(max_length=20)

def save(self.request):

    adapter = get_adapter(request)

    new_user = adapter.new_user(request)

    adapter.save_user(request, new_user, self)

    self.custom_signup(request, new_user)

    return new_user

Chukwunazaekpere avatar Aug 04 '20 13:08 Chukwunazaekpere

If you're inheriting from allauth's SignupForm, then, do the following:

From allauth.account.adapter import get_adapter

From allauth.account.forms import SignupForm as UserSignupForm

class SignupForm(UserSignupForm):

first name = forms.Charfield(max_length=20)

def save(self.request):

    adapter = get_adapter(request)

    new_user = adapter.new_user(request)

    adapter.save_user(request, new_user, self)

    self.custom_signup(request, new_user)

    return new_user

Test:

def test_signup_form_saves_to_database(self):

self.client.post(reverse('accounts:signup'), form_data)

self.assertEquals(User.objects.count(), 1)

Test is okay!

Chukwunazaekpere avatar Aug 04 '20 13:08 Chukwunazaekpere

please i have a problem: in fact in my site I have two categories of users and the registrations are different for both for example an investor has certain fields that the student does not have and lives towards his. I would like to register via google with django-allauth, but the problem is with ACCOUNT_FORM that I put in my setting.py I was wondering if it is possible to map ACCOUNT_FOR to two different forms? if not please help

jeanpetitt avatar May 10 '22 06:05 jeanpetitt

please i have a problem: in fact in my site I have two categories of users and the registrations are different for both for example an investor has certain fields that the student does not have and lives towards his. I would like to register via google with django-allauth, but the problem is with ACCOUNT_FORM that I put in my setting.py I was wondering if it is possible to map ACCOUNT_FOR to two different forms? if not please #2142 #

jeanpetitt avatar May 12 '22 06:05 jeanpetitt

ACCOUNT_SIGNUP_FORM_CLASS is documented as:

...that is used during signup to ask the user for additional input (e.g. newsletter signup, birth date) ...

So, it is indeed a form containing only the additional fields, and you need not worry about the default login/password fields at all when using this.

ACCOUNT_FORMS is documented as:

...Used to override forms...

So, it can be used to completely replace the allauth forms by other implementations. But, in this case, it is your job to make sure that your form is compliant and has all the needed fields in there (which could be done by subclassing the alauth forms).

So, they are indeed not the same -- this is intentional.

pennersr avatar Jun 19 '23 21:06 pennersr

thank you!

jeanpetitt avatar Jun 22 '23 15:06 jeanpetitt