UserCreationForm : Can't override class name ?

Hi everyone !

I’m using UserCreationForm in order to play with django.contrib.auth.

So, I just override UserCreationForm’s fields in order to customize the experience like so :

forms.py

    class ClientForm(UserCreationForm):
    class Meta:
        model = User
        fields = ["username","email","password1","password2"]
        widgets = {
            "username": Input(attrs={
                "type":"text",
                "class":"register_box_item input_box",
            }),

            "email": Input(attrs={
                "type":"text",
                "class":"register_box_item input_box",
            }),

            "password1": Input(attrs={
                "type":"password",
                "class":"register_box_item input_box",
            }),

            "password2": Input(attrs={
                "type":"password",
                "class":"register_box_item input_box",
            }),
        }

But when I load the register.html page, the source code doesn’t seem to have received the modification for fiels which are not {{ form.email }}

register.html

                    <form id="register_box" action="" method="POST">
                    <input type="hidden" name="csrfmiddlewaretoken" value="eGZp3me34gfxG5FZ60zyCuxWbBIaIGSoDGAOMAXwlPWR93ukA9BlLDfBAbpFqzUH">
                    <div id="register_box_welcome" class="register_box_div">
                        <label type="text" id="register_box_welcome_text">Bienvenue !</label>
                        <label type="text">Connectez-vous dès maintenant</label>
                    </div>

                    <div class="register_box_div">
                        <label type="text" class="register_box_item label_box">Adresse mail</label>
                        <input type="text" name="email" class="register_box_item input_box" maxlength="254" id="id_email">
                    </div>

                    <div class="register_box_div">
                        <label type="text" class="register_box_item label_box" >Mot de passe</label>
                        <input type="password" name="password1" autocomplete="new-password" required id="id_password1">
                        <label type="text" class="register_box_item label_box" >Confirmation</label>
                        <input type="password" name="password2" autocomplete="new-password" required id="id_password2">
                    </div>

                    <input type="submit" value="Inscription" id="submit_button" class="register_box_div">
                </form>

Do you have an Idea why it deleted my override criteria in forms.py ?

Thanks in advance

Two things.

First, I’m hoping it’s a formatting issue, but your class Meta definition isn’t indented inside your class ClientForm definition.
(Again, I’m sure it’s just a copy/paste error, but it’s worth verifying.

But, more importantly, you haven’t defined these fields in the form - and, you’re not rendering those fields in your template.

You’ve specified widgets for a “password1” field, but unless you’re using an extended model named User (a really bad idea in general, an extended User object should have a different name) with a field named password1, you’ve got no field by that name. You’re defining a widget for a non-existent field.

Also, you’re not rendering your field in the form. So let’s say that you add a line to ClientForm that looks something like this:

password1 = forms.CharField(max_length=50)

Then in your template, you want to render {{ form.password1 }}

Ken

And an opinionated observation -

It seems to me like you’ve had a number of issues surrounding forms - I’ve seen a pattern where it’s appears to me like you keep trying to work around the Django forms system rather than working with it. I think it would be worth your time to really read the Working with Forms page from top to bottom to try and see everything available to you in the forms system so you can take advantage of it.

Hi @KenWhitesell !

Sorry, I posted the HTML code rendered in the browser, here is the HTML code I wrote :

                <div id="register_container">
                <form id="register_box" action="" method="POST">
                    {% csrf_token %}
                    <div id="register_box_welcome" class="register_box_div">
                        <label type="text" id="register_box_welcome_text">Bienvenue !</label>
                        <label type="text">Connectez-vous dès maintenant</label>
                    </div>

                    <div class="register_box_div">
                        <label type="text" class="register_box_item label_box">Pseudo</label>
                        {{ form.username }}
                        <label type="text" class="register_box_item label_box">Adresse mail</label>
                        {{ form.email }}
                        <label type="text" class="register_box_item label_box" >Mot de passe</label>
                        {{ form.password1 }}
                        <label type="text" class="register_box_item label_box" >Confirmation</label>
                        {{ form.password2 }}
                    </div>

                    <input type="submit" value="Inscription" id="submit_button" class="register_box_div">
                </form>
            </div>

As you can see, I did manage to render the fields inside the form :wink:

My issue is that the customization made in the forms.py file are not accepted in the final HTML rendering.

I used this video to learn about the authentication / registration system in Django :

Can we see your view for this? Can you confirm / validate that your form is being rendered?

(Sorry, I can’t spend 35+ minutes going through a video tutorial just to try and answer a question here. If I’m going to try to help, I need to approach it outside that context.)

Here it is :

    from django.http import HttpResponse
    from django.shortcuts import render, redirect
    from django.contrib.auth import authenticate, login
    from django.contrib.auth.forms import UserCreationForm
    from .forms import ClientForm

    # Create your views here.
    def register(request):
    if request.method == "POST":
        form = ClientForm(request.POST)

        if form.is_valid():
            form.save()
            return redirect("/")
            
        else:
            print(form.errors)

    else:
        form = ClientForm()

    template_name = "client/register.html"
    context = {
        "form":form,
    }

    return render(request, template_name, context)

    def login(request):
    if request.method == "POST":
        email = request.POST.get("email")
        password = request.POST.get("password")

        user = authenticate(request,email=email,password=password)

        if user is not None:
            login(request, user)
            return redirect("")
        else:
            return HttpResponse("Error")

    template_name = "client/login.html"
    context = {}

    return render(request, template_name, context)

I’m not sure I have a complete answer, but I did find a clue.

In the documentation for “Overriding the default fields”, there’s this paragraph:

Fields defined declaratively are left as-is, therefore any customizations made to Meta attributes such as widgets , labels , help_texts , or error_messages are ignored; these only apply to fields that are generated automatically.

If you look at your parent class, UserCreationForm, you’ll see that those four fields are manually defined - and so the widgets information in the Meta class won’t apply.

So, it seems like what you want to do is declare the fields in the class itself, including the options that you want regarding css classes, instead of trying to override them in Meta.

Ken

Hi @KenWhitesell ! Thanks again !

So if I’ve understood well you want me to basically create my own “Client Model” and “Client Form” instead of using the pre built-in UserCreationForm ?

I’ve rewritten my code like so in order to make it cleaner with labels included :

register.html

    {% extends "base.html" %}

    {% block page_title %}Django Auth : Register{% endblock %}

    {% block page_content %}
    <form action="{% url 'client:user_register' %}" method="POST">
    {{ form.username.label }}
    {{ form.username }}

    {{ form.email.label }}
    {{ form.email }}

    {{ form.password1.label }}
    {{ form.password1 }}

    {{ form.password2.label}}
    {{ form.password2 }}
    <input type="submit" value="Sign Up">
    </form>
    {% endblock %} 

forms.py

    from django.forms import ModelForm
    from django.contrib.auth.forms import UserCreationForm
    from django.forms.widgets import Input
    from django.contrib.auth.models import User

    class CreateUserForm(UserCreationForm):
    class Meta:
        model = User
        fields = "__all__"
        widgets = {
            "username": Input(attrs={
                "type":"text",
                "placeholder":"Type in username",
            }),

            "email": Input(attrs={
                "type":"text",
                "placeholder":"Type in email",
            }),

            "password1": Input(attrs={
                "type":"password",
                "placeholder":"Type in password",
            }),

            "password2": Input(attrs={
                "type":"password",
                "placeholder":"Type in password",
            }),
        }

        labels = {
            "username":"Username :",
            "email":"Email :",
            "password1":"Password :",
            "password2":"Confirm password :",
        }

Well, I’m guessing most of the functionality is ok, so I’m envisioning that you might want to do something like this:

class CreateUserForm(UserCreationForm):
    # Copied from UserCreationForm
    #    change the attributes you want to change
    #    to override what would be inherited
    #    including specifying custom css classes if desired
    password1 = forms.CharField(
        label=_("Password"),
        strip=False,
        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
        help_text=password_validation.password_validators_help_text_html(),
    )
    password2 = forms.CharField(
        label=_("Password confirmation"),
        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
        strip=False,
        help_text=_("Enter the same password as before, for verification."),
    )
    class Meta:
        model = User
        fields = ("username", "email")
        # Copied from your forms.py
        # Note that you're only specifying entries for the auto-generated fields
        widgets = {
            "username": Input(attrs={
                "type":"text",
                "placeholder":"Type in username",
            }),
            "email": Input(attrs={
                "type":"text",
                "placeholder":"Type in email",
            }),
        }
        labels = {
            "username":"Username :",
            "email":"Email :",
        }

There is another option.

Rather than overriding those definitions, you could alter those settings in the init method for your form.
So as an alternative (not in addition to!) you could do something like:

class CreateUserForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["username"].widget.attrs["placeholder"] = "Type in username"
        self.fields["email"].widget.attrs["placeholder"] = "Type in email"

And, if you wanted to stay with the “dictionary”-style settings, you could code this as a loop to iterate through your configuration settings to override the corresponding elements in the form.

The key point here, though, is that you can’t use the “Meta: widgets” method to override settings on explicitly-defined form fields.

Ken

Hi @KenWhitesell !

Thank you for your answer, I tried this way as I prefer to loop through each field :

    class SignupForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields :
            field.widget.attrs["class"] = "formField" 

But I get :

field.widget.attrs["class"] = "formField"
AttributeError: 'str' object has no attribute 'widget'

How can I loop through each fields in order to change their class attribute ?

Thank you in advance

In the context of a form, self.fields is a dict.

Iterating over a dict (for field in self.fields) gives you the keys of the dict, not a reference to the entity itself.

In other words, assume a single field in the form named ‘name’.

for field in self.fields:
    print(field)

will print name, not an object reference to the form field.

This gives you a couple different options:

for field_id in self.fields:
    field = self.fields[field_id]

or

for field in self.fields.values():
    # Do something with `field`, but you don't have access to the name.

or

for field_id, field in self.fields.items():
    # Iterates over the tuple of (field_id, field) 
    # such that field = self.fields[field_id]

For more information, see the Python documentation on Dictionaries and the Mapping type - Dict API docs.

Ken

HI @KenWhitesell !

Thanks again,

Here is my code updated :

    class SignupForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args,**kwargs)
        for field in self.fields.values() :
            field.widget.attrs["class"] = "form_element"

    class Meta:
        model = User
        fields = ["username", "email", "password1", "password2"]

Yet, I don’t understand why Django doesn’t render labels as :

<label>A label</label>

But instead :

    <form id="signupForm" action="/account/signup" method="POST">
    <input type="hidden" name="csrfmiddlewaretoken" value="bARi0NjLFVJB1QUP800ldI5NO2m2eYhhNkqj1Zi5a9XIubQP9usW94mW9FB01I3g">

    Username
    <input type="text" name="username" maxlength="150" autofocus class="form_element" required id="id_username">

    Email address
    <input type="email" name="email" maxlength="254" class="form_element" id="id_email">

    Password
    <input type="password" name="password1" autocomplete="new-password" class="form_element" required id="id_password1">

    Password confirmation
    <input type="password" name="password2" autocomplete="new-password" class="form_element" required id="id_password2">

    <input type="submit" value="Sign Up">
    </form>

It’s because you’re manually rendering the label field.
If you just render the form field, it will render both the label and the input field. When you render the label component directly, that’s Django giving you the option to render that label other than (just) with the label tags. (You’re overriding the default behavior.)

Ken