ModelForm is not saving UserModel

Hi everyone,

I’m stucked at saving my ModelForm whereas I did save() the form :

models.py :

class UserModel(AbstractBaseUser):
    username = models.CharField(max_length=20, unique=True)
    email = models.EmailField(max_length=20)
    password1 = models.CharField(max_length=20)
    password2 = models.CharField(max_length=20)

    USERNAME_FIELD = "username"

views.py

def signupUser(request):
    if request.method == "POST":
        form = signupUserForm(request.POST)

        if form.is_valid():
            print("SIGNUP FORM IS VALID")
            form.save()
            return redirect("site_base:home")
        else:
            print("ERROR : ", form.errors)
    else:
        form = signupUserForm()

    template_name = "accounts/signup.html"
    context = {
        "form":form,
    }

    return render(request, template_name, context)

forms.py

class signupUserForm(forms.ModelForm):
    class Meta:
        model = UserModel
        fields = [
            "username",
            "email",
            "password1",
            "password2",
        ]

        widgets = {
            "password1":PasswordInput(),
            "password2":PasswordInput(),
        }

Indeed when I try to create a User via my signupUserForm I do obtain a User object but then I get nothing in my user database…

>>> from accounts.models import UserModel
>>> from accounts.forms import signupUserForm
>>> request_post = {"username":"myuser","email":"myuser@mail.com","password1":"myuser","password2":"myuser"}
>>> form = signupUserForm(request_post)
>>> form.is_valid()
True
>>> form.save()
<UserModel: myuser>

But then when I go into the Djagno admin board I cannot see my user stored in the User section.

Why ?

Thank you in advance

Did you make the settings change for AUTH_USER_MODEL?

1 Like

Hi @KenWhitesell !

Thank you for your answer.

Well, I did add this to my settings.py :

from accounts.models import UserModel
AUTH_USER_MODEL = UserModel

I then deleted my db in order to reset everything but when I try to migrate I get an error :

django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty.   

From the docs:

AUTH_USER_MODEL = ‘myapp.MyUser’

Notice that the setting is looking for the name of the class (a string), not the class model itself.

Remove the import and specify the model as a string.

You’re right !

I now have a new bug :

user = UserModel._default_manager.get_by_natural_key(username)     
AttributeError: 'Manager' object has no attribute 'get_by_natural_key'  

Looks like I still have to change something else ?

One of the differences between AbstractUser and AbstractBaseUser is that AbstractUser defines the objects attribute on the model, while AbstractBaseUser does not. So if you’re building your model based on AbstractBaseUser, you will need to provide a manager.
This is covered in the section Writing a manager for a custom user model

Thank you @KenWhitesell, I understand it better now.

For the moment being I will stick with AbstractUser.

If I understood it right I know have to also specify a custom authentication backend ?

Because the authentication will fail according to my debug information :

[05/Nov/2020 17:07:40] "GET /accounts/signup HTTP/1.1" 200 850
SIGNUP FORM IS VALID
[05/Nov/2020 17:07:54] "POST /accounts/signup HTTP/1.1" 302 0
[05/Nov/2020 17:07:54] "GET / HTTP/1.1" 200 340
[05/Nov/2020 17:07:56] "GET /accounts/login HTTP/1.1" 200 630
<QueryDict: {'csrfmiddlewaretoken': ['MY7OCwXaiwQwdglEuCWobY0AlfTCOnLQimKhRR6Er8CkOrjnot0GNnPbbaobRp2D'], 'username': ['myuser'], 'password': ['myuser']}>
LOGIN FORM IS VALID
ERROR : Failed to authenticate user
[05/Nov/2020 17:08:01] "POST /accounts/login HTTP/1.1" 200 645  

This may go back to the previous reply you received in your earlier thread where @mblayman points out the need to store passwords encrypted. (Create User ModelForm and working with it)

In your model above, you’re storing the two password fields as text rather than storing a single encrypted password field. You might want to look at the Django source code for AbstractUser / User to see how it’s being handled by the system.

Yes I remember but as I use AbstractUser instead of AbstractBaseUser I shouldn’t have any issues.

Here is the code snippet I found in AbstractBaseUser code :

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if self._password is not None:
            password_validation.password_changed(self._password, self)
            self._password = None

    def set_password(self, raw_password):
        self.password = make_password(raw_password)
        self._password = raw_password

=> Does that mean that I have to implement these methods myself ?
=> Or maybe is it better in that case to use AbstractBaseUser instead of AbstractUser to benefit from the already written methods ? (Not opting for the premade User model in order to learn more)

Take a look at the standard Django UserCreationForm to see how they handle it.

It’s not that you need to implement the set_password method, but you will need to call it.

Well that’s interesting, I’ve seen that there is both UserCreationForm and AuthenticationForm in the source code.

I’ve tried to inherit from the two hereafter :

class signupUserForm(UserCreationForm):
    class Meta:
        model = UserModel
        fields = [
            "username",
            "email",
            "password1",
            "password2",
        ]

        widgets = {
            "password1":PasswordInput(),
            "password2":PasswordInput(),
        }

class loginUserForm(AuthenticationForm):
    username = forms.CharField(label="Username : ")
    password = forms.CharField(label="Password : ", widget=PasswordInput)

But I can’t authenticate my user, I get a blank error in my debug output.

<conjecture>
Notice that in the UserCreationForm, password1 and password2 are designed as form fields, not as model fields. I’m guessing that there’s some sort of conflict between the two definitions such that your model fields named password1 and password2 are not being populated from the form.
</conjecture>

So according to you I should rewrite my UserModel in order to match with the UserCreationForm ?

Because when I go to the admin board I now have the users which were created through my signupUserForm.

The issue is when using the loginUserForm which inherits from AuthenticationForm.

No, I’m not actually saying that. I don’t know what your specific needs, requirements, or objectives are for this exercise. I am suggesting that if you’re going to make these types of changes, that you’re not going to be able to rely upon the built-in forms to work with your modified model.

You’ve defined a model with a password1 and password2 field in the models. Ok, that’s fine, but that’s not how Django does it. Django stores the single (encrypted) password in a field named password (AbstractBaseUser, and so by extension, AbstractUser and User) - it doesn’t store the password1 and password2 fields in the model.

Since you’re inheriting from AbstractBaseUser, that means you’d have all of password, password1 and password2 in the model.

You’re inheriting a Form that was designed for a different model with fields named the same as model fields that you have, creating the possibility of one of them being hidden by the other. (And I don’t know which would be used at any particular point.)

So you mention that the users are being created - but is the password field being populated with the encrypted password? More importantly, is it being encrypted with the password supplied by the form, and not from the (assumed) blank / null field being hidden by the other instance?

It is that I was searching things on the Web and found this article : https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html

My goal is to learn more about Django through an exercise where :

  • The user can authenticate himself with his/her mail adress.
  • The user can have a biography

According to the article above, there is no choice but to use AbstractBaseUser and implement everything myself right ?

Regarding that article, I’d be a little concerned given its age. July 2016 puts it at Django 1.9. I don’t specifically know what, if anything, has changed in the authentication system since then, but it is a factor I would be looking at closely. (I typically find two common issues with old articles - one is advice that is wrong due to underlying system changes and the other is advice that is suboptimal because of advances within Django since the time that article was written. Again, I have no direct knowledge regarding this specific topic relative to that post, but it’s something you should be aware of.)

So it appears to me that you have identified two different and somewhat divergent goals. Yes, there are times when doing something yourself provides a much better education than just reading someone else’s code - and it’s a practice I’ve done myself.

However, outside that exercise, if I wanted to create a system using email address as the username, I would be looking at djangopackages.org to find one of the third-party packages to keep from having to do the work myself.

Regardless of the specific approach, for something like the biography, I would always implement that as a “profile model” associated with the User model by a one-to-one field. (The User model is likely going to be the most frequently accessed model on any system with a high percentage of authenticated users. I want that model to be as lean as possible.)

But you always have choices, and there are always tradeoffs. To say that “there is no choice but to…” isn’t accurate. How much you’ll need to implement yourself will depend upon how close your requirements are to the existing implementation.

For example, someone else on here on this forum posted a thread of messages where they wanted the person to be able to log in using either their username or their email address. They ended up writing a custom authenticator that had the single form field for entry and checked what the user had entered to both the username and email fields to find the User object to use. In that particular place, they didn’t change the user object at all, other than making email address unique.
Sometimes it pays just to get a bit creative.