Extending the existing User model

Hi, now I try to extend the user model of my django app to store a pin and a balance with the useraccount when the new user registers.

My models.py looks like this:

from django.db import models
from django.contrib.auth.models import User

from django.utils import timezone

# Create your models here.
class Member(models.Model):

    user = models.OneToOneField(User, on_delete=models.CASCADE)
    pin = models.CharField(max_length=100)
    balance = models.IntegerField()

    def __str__(self):
        return "@{}".format(self.user.name)

now I try extending the registration form at forms.py

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import forms
from accounts.models import Member

class MemberCreateForm(UserCreationForm):

    class Meta:
        fields = ("username", "email", "password1", "password2", "pin")
        model = get_user_model()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["username"].label = "Name"
        self.fields["email"].label = "email"
        self.fields["pin"].label = "pin"

and call it from the views.py

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

# Create your views here.
class SignUp(CreateView):

    form_class = forms.MemberCreateForm
    success_url = reverse_lazy("accounts:login")
    template_name = "accounts/signup.html"

but
django.core.exceptions.FieldError: Unknown field(s) (pin) specified for User

I found this in the docs and this on stackoverflow, but I am lost…

Hey there!
The problem that i see is that you’re expecting that the Member model is the default User model.
But that’s not the case, when you extend the UserModel you need to Inherit from AbstractBaseUser and do another few settings.
Right now, django is getting the django.contrib.auth.models.User model (the django’s default) and looking up for a field called pin that does not exists there, and that’s why you’re receiving:

But the way you’re declaring the Member model is is not wrong, since it’s a way of doing as mentioned on the docs. the problem itself it’s how you write the form. If you only want to set the pin to the Member model, you need to add a field pin (or any other name) on the body of the MemberCreateForm.

And now it’s my assumption on how to solve this problem:

from django import forms

class MemberCreateForm(UserCreationForm):
     pin = forms.CharField(max_length=100)

    class Meta:
        fields = ("username", "email", "password1", "password2", "pin")
        model = get_user_model()

    def save(self, *args, **kwargs):
        # Let the UserCreationForm handle the user creation
        user = super().save(*args, **kwargs)
        # With the user create a Member
        Member.objects.create(user=user, pin=self.cleaned_data.get("pin"), other_arg=other_value)

What you’re doing here is creating a Profile model for the User - it’s a completely separate model, related to User by the OneToOne field.

This means that your views, etc, need to work with two separate and independent objects if you’re going to work with them at the same time.

My recommendation in those cases is usually to avoid using the Django-provided CBVs beyond (perhaps) View.

See some of the other conversations here in the forum at:

As you can see, this is a frequently discussed topic.

The basic summary is that you have a couple different ways to approach this, and you should decide which is going to make the most sense to you.

Thanks @KenWhitesell

Thanks for both you help!

I tried the suggested approach to see where it gets me and read the links provided.

The suggested MemberCreateForm seems to work, I have the new field in the form and when submitting, it is saved!

But there is a new error after submitting the form:

AttributeError at /accounts/signup/
Exception Value: 'NoneType' object has no attribute '__dict__'
Exception Location:	/usr/local/lib/python3.9/site-packages/django/views/generic/edit.py, line 113, in get_success_url

Now I am confused why there seems to be a problem with the success_url that I have not changed.
I am also not sure if the save function replaces the init function, but adding to the MemberCreateForm class (in forms.py) seems not make it better (or worse).

class MemberCreateForm(UserCreationForm):

    pin = forms.CharField(max_length=100)

    class Meta:
        fields = ("username", "email", "password1", "password2", "pin")
        model = get_user_model()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def save(self, *args, **kwargs):
        # Let the UserCreationForm handle the user creation
        username = super().save(*args, **kwargs)
        # With the user create a Member
        Member.objects.create(user=username, pin=self.cleaned_data.get("pin"))

Do I understand correctly, that this would not be the recommended way and I should avoid using class based views to do this?

I guess I need “Multiple separate models that are related, and one submit button saves all”,
because I want a user that registers to provide the additional value “pin” at the registration form and have the value stored related to their a user account
instead of extending the default django user model.
And so I guess should “use the formset facility with the formset having a limit of 1 form, and not providing any facility to add forms”, but I don’t understand this yet :frowning:

Oh, this was my bad.

That’s because the save function must return a instance of the model.

So add on the save function the following statement: return user

The CBV’s save you boilerplate code on function based view’s. But using them in some scenarios leads to more work than it saves. CBV’s is not like: DO this in this way, they are a way of doing, sometimes it’s better, sometimes it’s worse. Taking these decisions is up to you!

That’s already what your function is doing. Formsets are for saving multiple forms of the same type at the same time.
From what you issued you’re trying to save a single User and create a single Member. So no need to formsets.

Thanks again!

I just ended up having it as I wanted after fiddeling around for a while but I was very supprised that it works like this:

models.py

from django.db import models
from django.contrib.auth.models import User

class Member(models.Model):
    username = models.OneToOneField(User, on_delete=models.CASCADE)
    pin = models.CharField(max_length=100)
    balance = models.IntegerField(default=0)

    def __str__(self):
        return "@{}".format(self.username)

forms.py

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.forms import forms
from accounts.models import Member

class MemberCreateForm(UserCreationForm):

    pin = forms.CharField(max_length=100)

    class Meta:
        fields = ("username", "email", "password1", "password2")
        model = get_user_model()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

and views.py

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

class SignUp(CreateView):

    form_class = forms.MemberCreateForm
    success_url = reverse_lazy("accounts:login")
    template_name = "accounts/signup.html"

Now I have the pin in my sign up form and it gets stored to the database. It is not stored with the User table, but in a Members table, where each username is the same as the user who registered, but with an @ sign before.

My next quest is to figure out how access/use the stored pin of the user in a function. With the docs I learned how to open a shell (manage.py shell) and I can filter the Member to check which user has a specific pin:

from accounts.models import Member
Member.objects.all().filter(pin=113)
<QuerySet [<Member: @test>]>

but I struggle how to make this request in a function. Do you maybe have any advice?

Pretty much exactly as you have it written here. If you’re having problems, please post the actual code you’re trying.

Also note, it’s redundant to have both .all() and .filter() in the same expression. You can remove the .all().

Yes, sorry:

I was hoping to have this function in the views.py return the same as when I put the query in the manage.py shell:

from accounts.models import Member
def addrecord(request):
    return HttpResponse(Member.objects.all().filter(pin=113))

but as I call the view:

Request Method:	GET
Request URL:	http://127.0.0.1:8000/addrecord/
Django Version:	3.2.15
Exception Type:	AttributeError
Exception Value:	
'QuerySet' object has no attribute 'objects'
def addrecord(request):
    return HttpResponse(Member.objects.filter(pin=113))

works! :smiley:

… but why would:

def addrecord(request):
    transaction = Transaction(amount=6, pin=113)
    transaction.save()
    if (Member.objects.filter(pin=transaction.pin) != ""):
        return HttpResponse(Member.objects.filter(pin=transaction.pin))
    else:
        return HttpResponse(" unknown")

return a blank page if I enter an unknown pin? I would expect the function either to return a Member, if the pin is a match (this works) or return “unknown” to the page. There is also no error in the console.

Figured it out, this works :slight_smile:

def addrecord(request):
    transaction = Transaction(amount=6, pin=11)
    transaction.save()
    if not (Member.objects.filter(pin=transaction.pin)):
        return HttpResponse(" unknown")
    else:
        return HttpResponse(Member.objects.filter(pin=transaction.pin))

Great to hear that you managed to do it.
Just want to point out the exists method.