I don't know how to use UserCreationForm

I want to make user registration form by using UserCreationForm and CreateView but I don’t know how to do it.
It works fine with ModelForm and CreateView but when I did that, I didn’t know existence of UserCreationForm.
So I changed it to UserCreationForm and it doesn’t work.
I don’t know how to solve it and I can’t find readable document related to UserCreationForm.

forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import Group
from django.forms import ModelChoiceField

from branches.models import Branch
from users.models import User


class UserForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user")
        super(UserForm, self).__init__(*args, **kwargs)
        self.fields["password1"].widget.attrs["class"] = "form-control"
        self.fields["password2"].widget.attrs["class"] = "form-control"
        if self.user.is_superuser:
            self.fields["branch"] = ModelChoiceField(
                queryset=Branch.objects.all(),
                required=True,
                label="지점",
                widget=forms.Select(
                    attrs={
                        "class": "form-select",
                    },
                ),
            )
            self.fields["groups"] = ModelChoiceField(
                queryset=Group.objects.all(),
                required=True,
                label="그룹",
                widget=forms.Select(
                    attrs={
                        "class": "form-select",
                    },
                ),
            )
        else:
            self.fields["branch"] = ModelChoiceField(
                queryset=Branch.objects.filter(branch=self.user.branch),
                required=True,
                label="지점",
                widget=forms.Select(
                    attrs={
                        "class": "form-select",
                    }
                ),
            )
            self.fields["groups"] = ModelChoiceField(
                queryset=Group.objects.filter(name__in=["회원", "직원"]),
                required=True,
                label="그룹",
                widget=forms.Select(
                    attrs={
                        "class": "form-select",
                    },
                ),
            )

    def clean_phone(self):
        phone_number = self.cleaned_data["phone"]
        phone_number = phone_number.replace("-", "")

        if len(phone_number) == 0:
            return phone_number
        elif phone_number[0:2] == "02" and len(phone_number) == 9:
            phone_number = [phone_number[0:2], phone_number[2:5], phone_number[5:]]
            phone_number = "-".join(phone_number)
        elif phone_number[0:2] == "02" and len(phone_number) == 10:
            phone_number = [phone_number[0:2], phone_number[2:6], phone_number[6:]]
            phone_number = "-".join(phone_number)
        elif phone_number[0] != 0 and len(phone_number) == 8:
            phone_number = [phone_number[0:4], phone_number[4:]]
            phone_number = "-".join(phone_number)
        elif len(phone_number) == 10:
            phone_number = [phone_number[0:3], phone_number[3:6], phone_number[6:]]
            phone_number = "-".join(phone_number)
        elif len(phone_number) == 11:
            phone_number = [phone_number[0:3], phone_number[3:7], phone_number[7:]]
            phone_number = "-".join(phone_number)

        return phone_number

    class Meta:
        model = User
        fields = (
            "username",
            "full_name",
            "groups",
            "birthday",
            "gender",
            "phone",
            "branch",
            "license_type",
            "plan_type",
        )
        widgets = {
            "username": forms.TextInput(
                attrs={
                    "class": "form-control",
                }
            ),
            "full_name": forms.TextInput(
                attrs={
                    "class": "form-control",
                }
            ),
            "birthday": forms.DateInput(
                attrs={
                    "type": "date",
                    "class": "form-control",
                }
            ),
            "gender": forms.RadioSelect(
                attrs={
                    "class": "form-check-input",
                },
            ),
            "phone": forms.TextInput(
                attrs={
                    "type": "tel",
                    "class": "form-control",
                },
            ),
            "license_type": forms.Select(
                attrs={
                    "class": "form-select",
                },
            ),
            "plan_type": forms.Select(
                attrs={
                    "class": "form-select",
                },
            ),
            "staff": forms.RadioSelect(
                choices=[(True, "예"), (False, "아니오")],
                attrs={
                    "class": "form-check-input",
                },
            ),
        }

models.py

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Group
from django.core.validators import RegexValidator
from django.db import models
from django.urls import reverse
from django.utils import timezone

from branches.models import Branch

# Create your models here.
phone_validator = RegexValidator(
    regex="\d{2,4}-?\d{3,4}(-?\d{4})?",
    message="This is not valid phone number format.",
)


class UserManager(BaseUserManager):
    def create_user(
        self,
        username,
        full_name,
        birthday,
        gender,
        phone,
        branch,
        password=None,
        **kwargs,
    ):
        user = self.model(
            username=username,
            full_name=full_name,
            group=Group.objects.get_or_create(name="Member"),
            birthday=birthday,
            gender=gender,
            phone=phone,
            license_type=kwargs["license_type"],
            plan_type=kwargs["plan_type"],
            branch=Branch.objects.get(srl=branch),
        )

        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_staff(
        self, username, full_name, birthday, gender, phone, branch, password, **kwargs
    ):
        user = self.create_user(
            username, full_name, birthday, gender, phone, branch, password, kwargs
        )
        user.staff = True
        user.group = (Group.objects.get_or_create(name="Staff"),)
        user.save(using=self._db)

        return user

    def create_superuser(
        self, username, full_name, birthday, gender, phone, branch, password, **kwargs
    ):
        user = self.create_user(
            username, full_name, birthday, gender, phone, branch, password, kwargs
        )
        user.staff = True
        user.superuser = True
        user.group = (Group.objects.get_or_create(name="Superuser"),)
        user.save(using=self._db)

        return user


class User(AbstractBaseUser):
    objects = UserManager()

    GENDERS = (
        (None, "Not chosen"),
        ("M", "Male"),
        ("F", "Female"),
    )
    LICENSE_TYPES = (
        (None, "Not chosen"),
        ("1L", "Class 1 Large"),
        ("1O", "Class 1 Ordinary"),
        ("1OA", "Class 1 Ordinary (Automatic)"),
        ("2O", "Class 2 Ordinary"),
        ("2OA", "Class 2 Ordinary (Automatic)"),
        ("P", "Practice"),
    )
    PLAN_TYPES = (
        (None, "Not chosen"),
        ("T", "Time-based"),
        ("G", "Guarantee"),
        ("P", "Practice"),
    )
    srl = models.BigAutoField(
        primary_key=True,
        verbose_name="Serial",
    )
    username = models.TextField(
        unique=True,
        verbose_name="Username",
    )
    full_name = models.TextField(
        verbose_name="Full name",
    )
    password = models.TextField(
        verbose_name="Password",
    )
    groups = models.ManyToManyField(
        to=Group,
        verbose_name="Groups",
    )
    birthday = models.DateField(
        verbose_name="Date of birth",
    )
    gender = models.TextField(
        verbose_name="Gender",
        choices=GENDERS,
    )
    phone = models.TextField(
        verbose_name="Phone number",
        validators=(phone_validator,),
    )
    branch = models.ForeignKey(
        "branches.Branch",
        verbose_name="Branch",
        on_delete=models.DO_NOTHING,
    )
    license_type = models.CharField(
        max_length=3,
        verbose_name="License type",
        choices=LICENSE_TYPES,
        blank=True,
        null=True,
        default=None,
    )
    plan_type = models.CharField(
        max_length=1,
        verbose_name="Plan type",
        choices=PLAN_TYPES,
        blank=True,
        null=True,
        default=None,
    )
    staff = models.BooleanField(
        verbose_name="Is staff",
        default=False,
    )
    active = models.BooleanField(
        verbose_name="Is active",
        default=True,
    )
    superuser = models.BooleanField(
        verbose_name="Is active",
        default=False,
    )
    last_login = models.DateTimeField(
        verbose_name="Last login",
        default=timezone.now,
    )
    date_joined = models.DateTimeField(
        verbose_name="Date joined",
        default=timezone.now,
    )

    USERNAME_FIELD = "username"

    REQUIRED_FIELDS = (
        "name",
        "birthday",
        "gender",
        "phone",
        "branch",
    )

    @property
    def is_staff(self):
        return self.staff

    @property
    def is_active(self):
        return self.active

    @property
    def is_superuser(self):
        return self.superuser

    def has_perm(self, perm, obj=None):
        return self.staff

    def has_module_perms(self, app_label):
        return self.staff

    class Meta:
        verbose_name = "User"
        verbose_name_plural = "Users"
        ordering = [
            "branch",
            "srl",
        ]

    def __str__(self):
        if self.gender == "M":
            gender_short = "M"
        elif self.gender == "F":
            gender_short = "F"

        return f"{self.full_name} ({self.branch}/{self.birthday.strftime('%y%m%d')}/{gender_short})"

    def get_absolute_url(self):
        return reverse("users:detail", kwargs={"srl": self.srl})

views.py

from django.urls import reverse_lazy
from django.views.generic.edit import CreateView

from users.forms import UserForm
from users.models import User


# Create your views here.
class UserCreateView(CreateView):
    model = User
    form_class = UserForm
    success_url = reverse_lazy("users:list")

    def get_form_kwargs(self):
        kwargs = super(UserCreateView, self).get_form_kwargs()
        kwargs.update({"user": self.request.user})
        return kwargs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = "Add user"

        return context

Template

<!DOCTYPE html>
{% extends "main/base.html" %}
{% load static %}
{% block content %}
    <form action="" method="POST" class="container d-grid gap-3 justify-content-evenly align-items-center">
        {% csrf_token %}
        {{ form.non_field_errors }}
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.username.id_for_label }}">{{ form.username.label }}</label>
            <div class="col">{{ form.username }}</div>
            {{ form.username.errors }}
        </div>

        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.full_name.id_for_label }}">{{ form.full_name.label }}</label>
            <div class="col">{{ form.full_name }}</div>
            {{ form.full_name.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.password1.id_for_label }}">{{ form.password1.label }}</label>
            <div class="col">{{ form.password1 }}</div>
            {{ form.password1.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.password2.id_for_label }}">{{ form.password2.label }}</label>
            <div class="col">{{ form.password2 }}</div>
            {{ form.password2.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.birthday.id_for_label }}">{{ form.birthday.label }}</label>
            <div class="col">{{ form.birthday }}</div>
            {{ form.birthday.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.gender.id_for_label }}">{{ form.gender.label }}</label>
            <div class="col">
                {% for choice in form.gender %}
                    <div class="form-check">
                        {{ choice.tag }}
                        <label class="form-check-label" for="{{ choice.id_for_label }}">{{ choice.choice_label }}</label>
                    </div>
                {% endfor %}
            </div>
            {{ form.gender.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
            <div class="col">{{ form.phone }}</div>
            {{ form.phone.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.branch.id_for_label }}">{{ form.branch.label }}</label>
            <div class="col">{{ form.branch }}</div>
            {{ form.branch.errors }}
        </div>
        
        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.license_type.id_for_label }}">{{ form.license_type.label }}</label>
            <div class="col">
                {{ form.license_type }}
            </div>
            {{ form.license_type.errors }}
        </div>

        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.plan_type.id_for_label }}">{{ form.plan_type.label }}</label>
            <div class="col">
                {{ form.plan_type }}
            </div>
            {{ form.plan_type.errors }}
        </div>

        <div class="row">
            <label class="col-4 col-form-label text-nowrap" for="{{ form.staff.id_for_label }}">{{ form.staff.label }}</label>
            <div class="col">
                {% for choice in form.staff %}
                    <div class="form-check">
                        {{ choice.tag }}
                        <label class="form-check-label" for="{{ choice.id_for_label }}">{{ choice.choice_label }}</label>
                    </div>
                {% endfor %}
            </div>
            {{ form.staff.errors }}
        </div>
        
        <div class="row">
            <div class="col d-flex justify-content-end">
                <button class="btn btn-primary" type="submit">Save</button>
            </div>
        </div>
    </form>
{% endblock %}

What is not working?

  • What is happening that you’re not expecting to have happen, or what isn’t happening that you are expecting to see?

Are you getting any error messages in the console where you’re running runserver?

There is no error message and it drives me crazy.
I expected page redirects to “users:list” page when user is registered successfully and so I can see new user in the DB table and Django admin site.
But what I saw was just turning back to user creation form page.
I tried to print what happened by overriding def form_invalid(self, form) but what was printed was kind of <TemplateResponse code=200> thing. (I can’t remember exact message)
I even tried copy-and-paste code on these Django documentations, but it still not working.

That’s definitely an indication of the submitted form being invalid.

If you’re looking to print out the form errors on the console as a debugging tool, see Form.errors. That should show you why your submitted form is failing the is_valid test.

I found the problem.
I missed groups field in the form.
By the way, I want to limit that groups field to specific one like how I wrote in UserManager
Can I do that with UserCreationForm?

If you’re talking about assigning the groups field to a specific value, then I wouldn’t do it in the form. I’d do that in the view.

The groups field is a many-to-many relationship with Group. This means that adding or removing groups does not change the User object. These are entries added or removed from the ManyToMany join table.
So if you know (or can determine) what groups are going to be assigned to a user at the time that user is created, I would remove groups as a field from the form. I’d process and save the form for that new user, then figure out and assign the groups.

Would you mind if I ask you how to assign group to user from view when user is created?
As you can see from the models.py that I uploaded in this question, I wrote the code to assign users to Member group by using Group.objects.get_or_create(name="Member") (It’s on line 31)
But I didn’t know it have no relation with creating user with form and I see the users created with form don’t have group assigned from DB.

How do you make any assignment to a many-to-many relationship?

See Many-to-many relationships | Django documentation | Django

Wow, Thanks.
I wrote the code below and it worked.

class UserCreateView(CreateView):
...
    def form_valid(self, form):
        if form.is_valid:
            self.object = form.save()
            member_group = Group.objects.get(name="Member")
            self.object.groups.set([member_group])
            return super().form_valid(form)
...