Create user and user profile in CreateView using single Formset without using post_save signal

Before we begin I am using Python 3.5.2 and Django 1.11.3 and basic knowledge of Django models, forms and views is required, so let’s begin. When I first started learning Django one of the things I needed to do was create a user and a user profile at the same time in the same view using a single form.  So let’s start from the beginning with our models:

models.py:

1
2
3
4
5
6
7
from django.db import models
from django.conf import settings
 
class UserProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    phone_number = models.CharField(max_length=10, null=True, blank=True)
    date_of_birth = models.DateField(null=True, blank=True)

This is our UserProfile model that we will be creating along side creating a new user model. This should be self explanatory, next let’s take a look at our form.

forms.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django import forms
from django.contrib.auth.models import User
 
class CreateUserForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput)
    repeat_password = forms.CharField(widget=forms.PasswordInput)
    # Here we add the extra form fields that we will use to create another model object
    phone_number = forms.CharField(required=False)
    date_of_birth = forms.CharField(required=False)
 
    class Meta:
        model = User
        fields = [
            'username',
            'first_name',
            'last_name',
            'email',
            'password',
        ]

1.) On line 1 we are importing the Django form model.

2.) One line 4 we are declaring the CreateUserForm model form.

3.) Then on line 5, we are informing Django that the password field is a password input by using the widget=forms.PasswordInput(othwerwise the password will display as a text field).

4.) Then on line 6 we are declaring the repeat_password field which isn’t part of the ModelForm, so we can validate whether the passwords match in the CreateView later on.

5.) On lines 8-9 we are creating the phone_number and date_of_birth fields, which are also not part of the ModelForm and will be the data we used to create another model object.

6.) And lines 11-19 should be self explanatory. Now let’s take a look on how we are going to process that data in our CreateView:

views.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from django.shortcuts import render, get_object_or_404
from django.contrib import messages
 
from .forms import CreateUserForm
 
class CreateUserView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
    login_url = reverse_lazy('users:login')
    form_class = CreateUserForm
    template_name = 'app/forms/add-user.html'
    success_message = 'New new user profile has been created'
 
    def form_valid(self, form):
        c = {'form': form, }
        user = form.save(commit=False)
        # Cleaned(normalized) data
        phone_number = form.cleaned_data['phone_number']
        date_of_birth = form.cleaned_data['date_of_birth']
        password = form.cleaned_data['password']
        repeat_password = form.cleaned_data['repeat_password']
        if password != repeat_password:
            messages.error(self.request, "Passwords do not Match", extra_tags='alert alert-danger')
            return render(self.request, self.template_name, c)
        user.set_password(password)
        user.save()
 
        # Create UserProfile model
        UserProfile.objects.create(user=user, phone_number=phone_number, date_of_birth=date_of_birth)
 
        return super(CreateUserView, self).form_valid(form)

1.) Let’s start with line 12, here we are overriding the form_valid() method of the CreateView. We override this method when we want want to do some extra processing after the form has been validated and cleaned.

2.) On line 13, we use the form object and store it in a form dictionary inside of our context variable, “c”,  for later use by the render function.

3.) On line 14, we are retrieving the form data by using form.save(commit=False) without sending it to the database so we can use for form.cleaned_data dictionary to get the extra fields from our formset so we can populate them later. On lines 16-19 we are getting the extra form fields via form.cleaned data.

4.) On lines 20-22 we are verifying if the passwords the user supplied are indeed a match, if not then we send an error message via messages.error() function, then we render the original form.

5.) If the passwords do match then on lines 23-24 we are setting the password via the set_password() method, and saving the user model to the database via the save() method.

6) On line 27, now that we have successfully saved the user we can now use the user object just created to create a UserProfile object with the extra form fields captured by the form. 7.) And lastly on line 29, we return the HTTP response.

And that’s it, that’s how I was able to create a user and user profile within a single Django CreateView and Formset. Please keep in mind that you can also do this by using a post_save signal, which I will be writing another article on that here in the near future. Thanks, and please comment below if you have any questions.

Further reading: Form handling with CBVs

2 thoughts on “Create user and user profile in CreateView using single Formset without using post_save signal

Leave a Reply

Your email address will not be published. Required fields are marked *