overclock
overclock

Reputation: 625

How create a user and a profile with a single form in Django?

I have created a Clients model in models.py that is intended to be a Client (user) Profile.

class Clients(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=30, verbose_name="Primeiro Nome")
    last_name = models.CharField(max_length=30, verbose_name="Apelido")
    address = models.CharField(max_length=200, verbose_name="Morada")
    nif = models.CharField(max_length=9, verbose_name="NIF", validators=[RegexValidator(r'^\d{1,10}$')], primary_key=True)
    mobile = models.CharField(max_length=9, verbose_name="Telemóvel", validators=[RegexValidator(r'^\d{1,10}$')])
    email = models.CharField(max_length=200, null=True, verbose_name="Email")
    avatar = models.ImageField(null=True)

    def __str__(self):
        return "%s %s" % (self.first_name, self.last_name)

    class Meta:
        verbose_name_plural = "Clientes"

    @receiver(post_save, sender=User)
    def update_user_profile(sender, instance, created, **kwargs):
        if created:
            Clients.objects.create(user=instance)
            instance.profile.save()

This model is connected to the Django Users through a OneToOneField called user.

I created a form that is capable of adding data to the Clients model in forms.py:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
from django.core.validators import RegexValidator

class SignUpForm(UserCreationForm):
    first_name = forms.CharField(max_length=30, label="Primeiro Nome")
    last_name = forms.CharField(max_length=30, label="Apelido")
    address = forms.CharField(max_length=200, label="Morada")
    nif = forms.CharField(max_length=9, label="NIF", validators=[RegexValidator(r'^\d{1,10}$')])
    mobile = forms.CharField(max_length=9, label="Telemóvel", validators=[RegexValidator(r'^\d{1,10}$')])
    email = forms.CharField(max_length=200, label="Email")

    class Meta:
        model = User
        fields = ('username', 'password1', 'password2', 'first_name', 'last_name', 'address', 'nif', 'mobile', 'email')

How can I, through this single form, add a username and password field so that, through the OneToOneField, it creates an user connected to this profile?

EDIT

The new version of the files above. Now, it creates the user, but all other fields for the Clients get passed empty.

My views.py

def signup(request):
    if request.method == 'POST':
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            user.refresh_from_db()  # load the profile instance created by the signal
            user.first_name = form.cleaned_data.get('first_name')
            user.last_name = form.cleaned_data.get('last_name')
            user.address = form.cleaned_data.get('address')
            user.nif = form.cleaned_data.get('nif')
            user.mobile = form.cleaned_data.get('mobile')
            user.email = form.cleaned_data.get('email')
            user.save()
            raw_password = form.cleaned_data.get('password1')
            user = authenticate(username=user.username, password=raw_password)
            login(request, user)
            return redirect('clientes')
    else:
        form = SignUpForm()
    return render(request, 'backend/new_client.html', {'form': form})

Upvotes: 1

Views: 85

Answers (1)

dirkgroten
dirkgroten

Reputation: 20682

The fields you added are just regular form fields, django doesn't know anything about where to save these values. So you need to override the form's save() method or save them in your view. Here's how to save them in your view, since this is what you started to do:

if form.is_valid():
    user = form.save()  # this creates the user with first_name, email and last_name as well!
    user.refresh_from_db()  # load the profile instance created by the signal
    user.clients.address = form.cleaned_data.get('address')
    user.clients.nif = form.cleaned_data.get('nif')
    user.clients.mobile = form.cleaned_data.get('mobile')
    user.clients.save()
    login(request, user)
    return redirect('clientes')

Note: I don't do anything with first_name, last_name and email in the view, they are fields of the User model so they will already be saved automatically when you do form.save(). You should remove them from your Clients model.

Note 2: Renaming your Clients model to Client would make your code more readable. You should always use singular for your models. This way you can do user.client.address which makes more sense than user.clients.address since it's a one-to-one field.

Alternatively, you can override the form's save() method, which is a method I would prefer as I don't think the view should care about how to save the user's profile:

# in SignupForm(UserCreationForm):
def save(self, commit=True):
    user = super().save(commit)  # this creates the new user
    if commit:
        user.refresh_from_db()  # not sure if this is needed
        user.clients.nib = self.cleaned_data.get('nib')
        user.clients.address = self.cleaned_data.get('address')
        user.clients.mobile = self.cleaned_data.get('mobile')
        user.clients.save()
    return user

Upvotes: 1

Related Questions