Repository: cookiecutter/cookiecutter-django
Pull request: Add UserSignupForm and UserSocialSignupForm
Add UserSignupForm and UserSocialSignupForm
And docstrings clarifying how to use Custom User Model and Signup Forms.

<!-- Thank you for helping us out: your efforts mean a great deal to the project and the community as a whole! -->


## Description

<!-- What's it you're proposing? -->

The proposal is based on the discussion in issue #2708, and how hard it is to currently understand/use the Custom User Model shipper with cookiecutter-django. 

Creating the stub UserSignupForm and UserSocialSignupForm will certainly save hours (if not more) of research and trial-and-error for users wanting to expand the default User class.

Checklist:

- [x] I've made sure that tests are updated accordingly (especially if adding or updating a template option)
- [x] I've updated the documentation or confirm that my change doesn't require any updates

## Rationale

<!--
Why does this project need the change you're proposing?
If this pull request fixes an open issue, don't forget to link it with `Fix #NNNN`
-->

The proposal is to ship the explicit Form Classes that can be used to expand the Signup Process and clarify the class that concerns the Admin User Creation process that is already present.

Fix #2708
Fix #450 

Problem statement:
Adding fields to the User model
Please excuse me if the answer to my question is painfully obvious but I'm new to Django and I'm not very familiar with custom user models.

If I wanted to add another field to the user model (for example, "department" - the users are employees), where would I add it?

I figured I could add a `department` variable to `models.py` but it doesn't seem to work. When I login to the `admin` site, I don't see a "department" field when I add a user. Similarly, I don't see a "name" field in the `admin` site - I only see `First Name`, `Last Name`, and `Email Address`. 

```
# models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import

from django.contrib.auth.models import AbstractUser
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible


@python_2_unicode_compatible
class User(AbstractUser):

    # First Name and Last Name do not cover name patterns
    # around the globe.
    name = models.CharField(blank=True, max_length=255)
    department = models.CharField(blank=True, max_length=5)

    def __str__(self):
        return self.username

    def get_absolute_url(self):
        return reverse('users:detail', kwargs={'username': self.username})
```


allauth sign up doesn't use custom UserCreation form
## What happened?

I wanted to include some custom fields (django-recaptcha) to my UserCreation form. Turns out, the settings for allauth wasn't pointed to UserCreationForm.

## What should've happened instead?

Allauth should be configured to use the custom UserCreationForm.

## Steps to reproduce

1. Generate a new project.
2. Add a random field to the form.
3. Go to signup. It won't show up.
4. Add the setting:

```
ACCOUNT_FORMS = {"signup": "donate_anything.users.forms.UserCreationForm"}
```

Voila the form field is there. I think the creation of a user should be parallel regardless of where it happens (admin or user input). Taking a look at AbstractUser model, which the custom user model uses, the `email` attribute is set with `blank=True`, meaning it's not required. However, the allauth setting for email is set to `mandatory`. I think we should add that setting (and any other necessary configuration) for the custom user creation form.

Was this deliberate? If not, I'll make a PR.

Hints from issue discussion:
Nope, not painfully obvious. I've yet to fix problems with the admin change user form, in that it's using the stock Django one. One of these days, if I have free time...

I just found myself in the same boat as OP (and glad I read it here).

Pydanny, best option then is to scratch the custom user app and use Django's default user model?

> Pydanny, best option then is to scratch the custom user app and use Django's default user model?

No, this only affects the admin interface. The model is perfectly fine, it's just the custom form Django is using in the admin to display e.g. the password reset function.

The best way would be to extend the admin form to display the fields that are added. See https://github.com/pydanny/cookiecutter-django/issues/390.

Thanks @jayfk.
Since #390 is a few months old, can you point any resources on how to do that safely?

I haven't had the time to take a closer look at this, but this should get you started:

The code lives in [users/admin.py](https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.repo_name%7D%7D/%7B%7Bcookiecutter.repo_name%7D%7D/users/admin.py) and here's a question at [stack overflow](http://stackoverflow.com/questions/15012235/using-django-auth-useradmin-for-a-custom-user-model) regarding this (take a look at the second answer that basically uses the same code).

I hadn't had any success with [users/admin.py](https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.repo_name%7D%7D/%7B%7Bcookiecutter.repo_name%7D%7D/users/admin.py) before, but the SO answer seems to be what was missing.

Overriding the fieldsets, however, is only working for the user change form.
The user creation form remains unaffected - which is odd - but I can live with that.

Here's what my `users/admin.py` is looking like, in case anyone needs a quick example (or can spot what is still missing):

``` python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = User

class MyUserCreationForm(UserCreationForm):

    error_message = UserCreationForm.error_messages.update({
        'duplicate_username': 'This username has already been taken.'
    })

    class Meta(UserCreationForm.Meta):
        model = User

    def clean_username(self):
        username = self.cleaned_data["username"]
        try:
            User.objects.get(username=username)
        except User.DoesNotExist:
            return username
        raise forms.ValidationError(self.error_messages['duplicate_username'])


@admin.register(User)
class MyUserAdmin(AuthUserAdmin):
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    fieldsets = AuthUserAdmin.fieldsets + (
            ('User Profile', {'fields': ('name',
                                        'location',
                                        'department',
                                        'phone',
                                        'extension')}),
    )
    list_display = ('username', 'name','location','department')
    search_fields = ['name']
```

Again, thank you for the help.

@dezoito's snippet has been implemented. Our next step is documenting it.

This is slightly off-topic, but I didn't see an explanation in the docs so here goes: what is the reasoning/justification for creating a custom user model vs using/extending stock?

Thanks for this project!
@easherma, as your question is, as you said, off-topic, please open it as a separate issue.
@dezoito @pydanny:
I've tried the solution by @dezoito to add fields into user but after the migration, the field still does not appear on the update form in the user. My `admin.py` is like this:
```python
from __future__ import unicode_literals, absolute_import
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = User
       
class MyUserCreationForm(UserCreationForm):

    error_message = UserCreationForm.error_messages.update({
        'duplicate_username': 'This username has already been taken.'
    })

    class Meta(UserCreationForm.Meta):
        model = User

    def clean_username(self):
        username = self.cleaned_data["username"]
        try:
            User.objects.get(username=username)
        except User.DoesNotExist:
            return username
        raise forms.ValidationError(self.error_messages['duplicate_username'])


@admin.register(User)
class MyUserAdmin(AuthUserAdmin):
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    fieldsets = AuthUserAdmin.fieldsets + (
            ('User Profile', {'fields': ('name',
                                         'phone',)}),
    )
    list_display = ('username', 'name')
    search_fields = ['name']
```

and my `model.py`:
```python
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _


class User(AbstractUser):
    
    name = models.CharField(_('Nome do Usuário'), blank=True, max_length=255)
    phone = models.BigIntegerField(_('Telefone'), blank=True,
                                help_text="Preencha com DDD e o número (sem símbolos especiais")

    def __str__(self):
        return self.username

    def get_absolute_url(self):
        return reverse('users:detail', kwargs={'username': self.username})
```
any ideas or help?
ps: sry about my rusty english, I'm from Brazil.

Edit 1:
And if I want to create users I must set the new field phone to null=True, otherwise I can't even create a superuser via terminal. 

Edit 2:
What I'm trying to do is to update the user fields on the user_detail.html, django admin show all the fields but I want to give the user the ability the himself update his infos. I don't know if this changes that @dezoito made are for this, but I could use some help on this problem (new in django and new with cookiecutter)
After hours of trying to figure that out, this just saved my day. 

Isn't it a bug?
And now that the Custom UserCreationForm is in place and working, the save_user() override in app/users/adapters.py AccountAdapter isn't working. Must be a django-allauth issue. So hard to get this to work.
@mpoli this is an open source project I did last year, so some things might be outdated: https://github.com/Donate-Anything/Donate-Anything Hope you find it useful in setting stuff up.
> Was this deliberate? If not, I'll make a PR.

I think it is deliberate, yes. The module for this form is just called `forms`, but the forms in it are only used in the admin, and I think the intent is to limit their usage to be admin only. The base forms are used in a way that seems to imply that too:

https://github.com/cookiecutter/cookiecutter-django/blob/0d8a91ebfeb08579bac7f200847016558086d6a5/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/users/forms.py#L13

Another clue in that direction is that the `UserChangeForm` [inherits](https://github.com/django/django/blob/0e656c02fe945389246f0c08f51c6db4a0849bd2/django/contrib/auth/forms.py#L150) `fields = '__all__` from Django's form, which [is discouraged](https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#selecting-the-fields-to-use) outside the admin:

> It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute. Failure to do so can easily lead to security problems when a form unexpectedly allows a user to set certain fields, especially when new fields are added to a model.

I think that creating a user from the admin vs when signing up are 2 operations which are generally different enough to justify a separate form. In some situation, they may be similar enough to use the same, but it's not something we can assume to be true to incorporate into the template.

I think you raised a valid problem though, which we should look at improving IMO:

1. We should make it clearer that the forms are for admin only: we could rename them or at least add a docstring to explain that.
2. Optional: create a `SignupForm` inheriting `allauth.account.forms.SignupForm` with a docstring explaining what it's for, and that new fields suitable for signup should be added here.
Although it inherits from `admin_forms`, the name itself confused me. Renaming it to be AdminUserCreationForms would be helpful. I think adding the second form inheriting from allauth's forms would also be useful as it seems like many people are having to customize their User models.

> 1. We should make it clearer that the forms are for admin only: we could rename them or at least add a docstring to explain that.
> 2. Optional: create a `SignupForm` inheriting `allauth.account.forms.SignupForm` with a docstring explaining what it's for, and that new fields suitable for signup should be added here.

I would go for both, it will make the life of anyone trying to customize sign up, or even to use the powers of the custom user model way easier.


@mpoli I won't have time to make a PR; if you'd like to contribute, that'd be great!
> @mpoli I won't have time to make a PR; if you'd like to contribute, that'd be great!

I have some clue on where to start. I will conduct some research on that and try to come up with something useful in a PR.

Commit message: Add UserSignupForm and UserSocialSignupForm
And docstrings clarifying how to use Custom User Model and Signup Forms.

Patch to review:
diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py
index 021dd7491e..5177c92fa9 100644
--- a/{{cookiecutter.project_slug}}/config/settings/base.py
+++ b/{{cookiecutter.project_slug}}/config/settings/base.py
@@ -306,8 +306,12 @@
 ACCOUNT_EMAIL_VERIFICATION = "mandatory"
 # https://django-allauth.readthedocs.io/en/latest/configuration.html
 ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter"
+# https://django-allauth.readthedocs.io/en/latest/forms.html
+ACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSignupForm"}
 # https://django-allauth.readthedocs.io/en/latest/configuration.html
 SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter"
+# https://django-allauth.readthedocs.io/en/latest/forms.html
+SOCIALACCOUNT_FORMS = {"signup": "{{cookiecutter.project_slug}}.users.forms.UserSocialSignupForm"}
 {% if cookiecutter.use_compressor == 'y' -%}
 # django-compressor
 # ------------------------------------------------------------------------------
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
index 80cd97aed8..0cba595ffd 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
@@ -1,5 +1,7 @@
 from django.contrib.auth import forms as admin_forms
 from django.contrib.auth import get_user_model
+from allauth.account.forms import SignupForm
+from allauth.socialaccount.forms import SignupForm as SocialSignupForm
 from django.utils.translation import gettext_lazy as _
 
 User = get_user_model()
@@ -11,9 +13,56 @@ class Meta(admin_forms.UserChangeForm.Meta):
 
 
 class UserCreationForm(admin_forms.UserCreationForm):
+    """
+    Form for User Creation in the Admin Area.  
+    To change user signup, see UserSignupForm and UserSocialSignupForm.
+    """
     class Meta(admin_forms.UserCreationForm.Meta):
         model = User
 
         error_messages = {
             "username": {"unique": _("This username has already been taken.")}
         }
+
+class UserSignupForm(SignupForm):
+    """
+    Form that will be rendered on a user sign up section/screen.
+    Default fields will be added automatically.
+    Check UserSocialSignupForm for accounts created from social.
+    """
+
+    def save(self, request):
+
+        # Ensure you call the parent class's save.
+        # .save() returns a User object.
+        user = super(UserSignupForm, self).save(request)
+
+        # Add your own processing here.
+
+        # You must return the original result.
+        return user
+
+
+    class Meta:
+        model = User
+    
+class UserSocialSignupForm(SocialSignupForm):
+    """
+    Renders the form when user has signed up using social accounts.
+    Default fields will be added automatically.
+    See UserSignupForm otherwise.
+    """
+
+    def save(self, request):
+
+        # Ensure you call the parent class's save.
+        # .save() returns a User object.
+        user = super(UserSocialSignupForm, self).save(request)
+
+        # Add your own processing here.
+
+        # You must return the original result.
+        return user
+
+    class Meta:
+        model = User
\ No newline at end of file
diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py
index 935eee9a2e..1f6f61bc06 100644
--- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py
+++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py
@@ -5,7 +5,11 @@
 
 
 class User(AbstractUser):
-    """Default user for {{cookiecutter.project_name}}."""
+    """
+    Default custom user model for {{cookiecutter.project_name}}.
+    If adding fields that need to be filled at user signup,
+    check forms.SignupForm and forms.SocialSignupForms accordingly.
+    """
 
     #: First and last name do not cover name patterns around the globe
     name = CharField(_("Name of User"), blank=True, max_length=255)


Code review comments:
diff_hunk: @@ -11,9 +13,56 @@ class Meta(admin_forms.UserChangeForm.Meta):
 
 
 class UserCreationForm(admin_forms.UserCreationForm):
path: {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
text: @user1:
```suggestion
class AdminUserCreationForm(admin_forms.UserCreationForm):
```

@author:
There are some mentions of that class that need to be altered in tandem in admin.py etc and documentation.

I will take a look in a while and commit them all together.

@author:
Solved by https://github.com/cookiecutter/cookiecutter-django/pull/3515/commits/14cdb4ad2799ce618986f7f575d11952dc2af4b9

diff_hunk: @@ -11,9 +13,56 @@ class Meta(admin_forms.UserChangeForm.Meta):
 
 
 class UserCreationForm(admin_forms.UserCreationForm):
+    """
+    Form for User Creation in the Admin Area.  
+    To change user signup, see UserSignupForm and UserSocialSignupForm.
+    """
     class Meta(admin_forms.UserCreationForm.Meta):
         model = User
 
         error_messages = {
             "username": {"unique": _("This username has already been taken.")}
         }
+
+class UserSignupForm(SignupForm):
+    """
+    Form that will be rendered on a user sign up section/screen.
+    Default fields will be added automatically.
+    Check UserSocialSignupForm for accounts created from social.
+    """
+
+    def save(self, request):
+
+        # Ensure you call the parent class's save.
+        # .save() returns a User object.
+        user = super(UserSignupForm, self).save(request)
+
+        # Add your own processing here.
+
+        # You must return the original result.
+        return user
+
+
+    class Meta:
+        model = User
path: {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py
text: @user1:
A few comments here:

- I'm not convinced we should to override the save method, it's explaining something which is quite standard Python/Django: call the super implementation and return the same thing
- Styling is complaining about too many blank lines before `Meta`

```suggestion
class UserSignupForm(SignupForm):
    """
    Form that will be rendered on a user sign up section/screen.
    Default fields will be added automatically.
    Check UserSocialSignupForm for accounts created from social.
    """

    class Meta:
        model = User
```

@author:
Agreed. I was just folowing the example from allauth documentation here. But I don't think we need to get to that level of detail/training wheels.