OneToOne Relationship: Linking your user model to your custom profile model in Django

OneToOne Relationship: Linking your user model to your custom profile model in Django

Phew... It's database again!?

20200821_152614_0000.png

There are three main relationships in relational database. These include:

  1. One To One relationship
  2. One To Many relationship
  3. Many To Many relationship

In this article we will be dealing with the first one; one-to-one relationship.

In Django, there is usually a user model that ships with the framework, this model comes with its fields, methods, attributes etc. The down side to this user model is that it does not allow for the addition of tailored fields separate from the defaults already provided by django. This can pose a serious challenge on how a developer is able to fully customize the user/customer profile of authenticated users. A blog website might want to have writers profile that includes profile picture, contact address, hobbies, niche etc and the user model that ships with django does not allow for such customization.

To solve this problem, developers create a custom profile model and connect this model to the default django user model through a one-to-one relationship. This ensures that a user can only be connected to one profile and vice versa. Also this allows for maximum control over the customization of a profile model.

Now I will walk you through how to achieve this customization in Django.

1) Make use of the default django user model

  • In the app where you want to create the profile, create a new forms.py file. Inside the forms.py file, import the following modules;
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
  • Create a class that will inherit from UserCreationForm. Within that class you will create another meta class that will have two variables; model and fields. The 'model' variable will hold your user model and the 'fields' variable will hold the form fields that will be created.
class createUserForm(UserCreationForm):
    class meta:
        model = User
        fields = ['username', 'password1', 'password2']

The code above will create a user form with fields for username, password and password confirmation.

2) Create your custom user profile model

  • In your models.py, import the default user model
from django.contrib.auth.models import User
  • Next is to create your profile model and in addition create a user field with a OneToOne relationship to django default user model
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, null=True,)
    name = models.CharField(max_length=200, null=True)
    email = models.CharField(max_length=200, null=True)
    address = models.CharField(max_length=200, null=True)

    def __str__(self):
        return self.name

3) Create the form for your profile model

  • Open up your forms.py and import your profile model from your models.py and run some other imports that we will be using in creating the profile form
from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import Profile
  • Next, create a class that will inherit from forms.ModelForm. Within that class you will create another meta class that will have two variables; model and fields. The 'model' variable will hold your profile model and the 'fields' variable will hold the form fields that will be created.
class profileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['name', 'email', 'address']
#The labels attribute is optional. It is used to define the labels of the form fields created   
        labels = {
                "name": _("Name     "),
                "email": _("Email Address"),
                "address": _("Street Address"),
                }

Now that our forms are created we will then define the logic in views.py before rendering it into our templates.

4) Create the logic in views.py

  • To work with the forms created in forms.py we have to import them into our views.py and also import some other modules that we will be needing to create our logic
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login,
from django.contrib import messages
from .models import Profile
from .forms import createUserForm, profileForm
  • We will now create our register/sign-up page. We will just call it registerPage. We will create an empty context dictionary and return a render
def registerPage(request):    
    context = {}
    return render(request, 'app_name/register.html', context)
  • We will then assign the values of the forms to variables calling the POST method. Then we pass the variables into the context dictionary
def registerPage(request):
    if request.method == 'POST':
        form = createUserForm(request.POST)
        profile_form = profileForm(request.POST)

    context = {'form': form, 'profile_form': profile_form}
    return render(request, 'app_name/register.html', context)
  • Next, we will handle the validation of both forms and save them after validation
def registerPage(request):
    if request.method == 'POST':
        form = createUserForm(request.POST)
        profile_form = profileForm(request.POST)

        if form.is_valid() and profile_form.is_valid():
            user = form.save()

            #we don't save the profile_form here because we have to first get the value of profile_form, assign the user to the OneToOneField created in models before we now save the profile_form. 

            profile = profile_form.save(commit=False)
            profile.user = user

            profile.save()

    context = {'form': form, 'profile_form': profile_form}
    return render(request, 'app_name/register.html', context)
  • If our forms are validated and saved we will then display a success message and redirect the user to the login page.
def registerPage(request):
    if request.method == 'POST':
        form = createUserForm(request.POST)
        profile_form = profileForm(request.POST)

        if form.is_valid() and profile_form.is_valid():
            user = form.save()

            #we don't save the profile_form here because we have to first get the value of profile_form, assign the user to the OneToOneField created in models before we now save the profile_form. 

            profile = profile_form.save(commit=False)
            profile.user = user

            profile.save()

            messages.success(request,  'Your account has been successfully created')

            return redirect('login')

    context = {'form': form, 'profile_form': profile_form}
    return render(request, 'app_name/register.html', context)

5) Render the template

  • In our register.html file, we will create a form tag with a POST method and action set to an empty string. Within the form tag we will put our csrf_token in django template format and then we will render the forms(both the user form and the profile form) dynamically. We will also remember to create our submit button.
           <form method="POST" action="">
                {% csrf_token %}
                    <h3>Register Profile</h3>

                    <div class="form-field">
                        {{profile_form.name.label_tag}}
                        {{profile_form.name}}
                    </div>
                    <div class="form-field">
                        {{form.username.errors}}
                        {{form.username.label_tag}}
                        {{form.username}}
                    </div>
                    <div class="form-field">
                        {{profile_form.email.label_tag}}
                        {{profile_form.email}}
                    </div>
                    <div class="form-field">
                        {{profile_form.address.label_tag}}
                        {{profile_form.address}}
                    </div>
                    <div class="form-field">
                        {{form.password1.errors}}
                        {{form.password1.label_tag}}
                        {{form.password1}}
                    </div>
                    <div class="form-field">
                        {{form.password2.errors}}
                        {{form.password2.label_tag}}
                        {{form.password2}}
                    </div>

                <hr>
                <input id="form-button" class="btn btn-success btn-block" type="submit" value="Create Profile">
                <br>
                    {{form.non_field_errors}}
                <p>Already have an account? <a href="{% url 'login' %}">Login</a></p>
            </form>
  • Because after filling the form and it has been validated and saved we are redirected to the login page, the success message will be rendered on the login page, at the bottom, right before the 'Don't have an account? Register'
            <form method="POST" action="">
                   ...
                 <hr>
                <input id="form-button" class="btn btn-success btn-block" type="submit" value="Login">
                <br>
                {% for message in messages %}
                <p>{{message}}</p>
                {% endfor %}
                <p>Don't have an account? <a href="{% url 'store:register' %}">Register</a></p>
            </form>

And that is how you create a profile model linked to your user model through a one-to-one relationship for your website.

If you have further questions, you can drop them in the comments below and I will try to answer them.