BUILDING E-COMMERCE WEBSITE USING DJANGO PYTHON: WORKING WITH SIGNUP FORM VALIDATION AND PASSWORD HASHING(PART-04)

in Programming/Dev2 months ago

Hello everyone. Welcome to the fourth part on the series "Building E-Commerce Website Using Django Python". In this tutorial, we will be focusing solely on the signup form. We will be submitting signup info from the front-end to the server-side and then saving it. Later we will be also adding proper validations to the form so that only valid data gets submitted to the database. Please find the link to the previous tutorial below. The source code can also be found below in the same post.

First Part: Getting Started
Second Part: Admin, Models and Data Rendering
Third Part: Template Inheritance, Bootstrap and Static Files

Lets start from where we left the last time. We already have our signup page and login page designed in the last tutorial. This is our project files and directories structure till now:

image.png

Lets add a dynamic URL routing for login and signup page, which I forgot to do in the last tutorial. Its just to make sure that when someone clicks on signup link then it will show signup page and same for the login page. For this open your ecommerce/store/templates/store/base.html and replace the login and signup nav-link in the navbar with this code:

<li class="nav-item">
                <a class="nav-link text-white" href="{% url 'signup' %}">Signup</a>
            </li>
            <li class="nav-item">
                <a class="nav-link text-white" href="{% url 'login' %}">Login</a>
            </li>


Customer uses signup form. So we need somewhere to save that signup info. For this we will create "Customer" model and save all the info entered by the customer to this model. For this open store/models.py and lets create the customer model.

class Customer(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    phone = models.IntegerField()
    email = models.EmailField()
    password = models.CharField(max_length=50)

    def __str__(self):
        return self.first_name + " " + self.last_name


You can see what each fields in the Customer model is for by looking at our signup page below. We want to store their first name, last name which is Character Field. Phone number is an Integer Field and for email we have used Email Field. For password, we will be using Character Field as of now but we will be hashing later in this tutorial just to make things look easy.

image.png

Now after creating models, we need to make migrations for it and then finally migrate it, register it to be reflected in the admin dashboard. Go to terminal and type python manage.py makemigrations. After this is completed again type python manage.py migrate. This is the output of above two migrations commands.

image.png

Now open store/admin.py and register our Customer model in that file. At the end of the file, add this code: admin.site.register(Customer). Lets run our development server by typing python manage.py runserver. Everything should works fine as of now. Go to browser and type localhost address: http://127.0.0.1:8000/admin/. Login using the credentials used before and you should see the following:

image.png

If you click on "Add", you should see following fields in the dashboard.

image.png

We will be populating these fields in the database from the signup page. So lets work on the same now. In ecommerce/store/templates/store/signup.html, lets add method to our form. Replace the code that starts with <form> tag with this one:

<form action="" method="post" class="bg-dark">
                {% csrf_token %}


Every time we are working with post request method, we need to use CSRF token to handle the post data. Now we will be working on signup view that is inside store/views.py. If the request method is GET, then we will want to serve the signup page to the user and if the request method is POST, then we will handle that, save the data to the database and return the simple HttpResponse page for now. Lets add this to our views.py file.

def signup(request):
    if request.method == 'GET':
        return render(request, 'store/signup.html')
    else:
        first_name = request.POST.get('firstname')
        last_name = request.POST.get('lastname')
        phone = request.POST.get('phone')
        email = request.POST.get('email')
        password = request.POST.get('password')
        customer = Customer(first_name=first_name, last_name=last_name, phone=phone, email=email, password=password)
        customer.save()
        return HttpResponse("<h3>Signup Successful</h3>")


Lets test the code. Open signup page and lets register with these sample data:

image.png

Click register and you will see this simple HttpResponse page.

image.png

Lets check the dashboard if that has appeared to our registered Customer model or not. Well we see that we have one customer there.

image.png

Let's check the inside details of that. And boom, we have everything in the database that has been provided through signup page in their respective fields. Don't worry about password for now that is being displayed in plain text. We will be hashing it later. If you notice, we haven't added any validation till now and anyone submit form without entering any data and that will just create an empty entry in the database. We need some mechanism to prevent users from doing that. So that's where we will use validation. You can use JavaScript client-side validation or writing required field in every <input> tag in our signup.html that's provided by Bootstrap for validation .But I will be using my own custom server-side validation.

image.png

We don't want anyone to submit empty value for each fields, first name and last name with less than 3 characters and phone number with less than 10 characters. So we will be adding validation for this now. If there is no error message, then we will create the object of customer and save those data. If we get the error then we will show them the error message in the signup page along with the value in each input field that they have entered. So this will be our views.py file after adding validation.

def signup(request):
    if request.method == 'GET':
        return render(request, 'store/signup.html')
    else:
        first_name = request.POST.get('firstname')
        last_name = request.POST.get('lastname')
        phone = request.POST.get('phone')
        email = request.POST.get('email')
        password = request.POST.get('password')
        customer = Customer(first_name=first_name, last_name=last_name, phone=phone, email=email, password=password)
        err_msg = None

        if not first_name:
            err_msg = "First Name Required."
        elif len(first_name) < 3:
            err_msg = "First Name must be 3 characters long."
        elif not last_name:
            err_msg = "Last Name Required."
        elif len(last_name) < 3:
            err_msg = "Last Name must be 3 characters long."
        elif not phone:
            err_msg = "Phone is Required."
        elif len(phone) < 10:
            err_msg = "Phone Number must be 10 characters long."
        elif not email:
            err_msg = "Email is Required."
        if not err_msg:
            customer.save()
            return HttpResponse("<h3>Signup Successful</h3>")
        else:
            return render(request, 'store/signup.html', {'error_msg': err_msg})

Also add this error message code to signup.html page just after the {% csrf_token %}.

{% if error %}
                        <div class="alert alert-danger" role="alert">
                            {{error}}
                        </div>
                {% endif %}

Lets test the code now. Lets submit keeping email field empty.

image.png

This is the corresponding error:

image.png

Lets try submitting with phone number which is 9 characters long.

image.png

This is the error message we get:

image.png

But you can see every time we are submitting incorrect data, the errors are shown but the data in each input field gets lost, but we want all the data to be there except password. For this we will be holding each value sent in the post request in a dictionary named as "value" and pass this value dictionary to our render function. Then inside each input field we will use this value dictionary to show the values entered before getting error. Also, we used to show simple HttpResponse page after successful signup before. But now we want them to redirect to the home page. So ,in views.py we need to import redirect from django.shortcuts Lets make those changes to our views.py then:

def signup(request):
    if request.method == 'GET':
        return render(request, 'store/signup.html')
    else:
        first_name = request.POST.get('firstname')
        last_name = request.POST.get('lastname')
        phone = request.POST.get('phone')
        email = request.POST.get('email')
        password = request.POST.get('password')
        customer = Customer(first_name=first_name, last_name=last_name, phone=phone, email=email, password=password)

        values = {
            'firstname': first_name,
            'lastname': last_name,
            'phone': phone,
            'email': email,
        }

        err_msg = None

        if not first_name:
            err_msg = "First Name Required."
        elif len(first_name) < 3:
            err_msg = "First Name must be 3 characters long."
        elif not last_name:
            err_msg = "Last Name Required."
        elif len(last_name) < 3:
            err_msg = "Last Name must be 3 characters long."
        elif not phone:
            err_msg = "Phone is Required."
        elif len(phone) < 10:
            err_msg = "Phone Number must be 10 characters long."
        elif not email:
            err_msg = "Email is Required."
        if not err_msg:
            customer.save()
            return redirect('home')
        else:
            return render(request, 'store/signup.html', {'error_msg': err_msg, 'values': values})

And in our signup.html page, lets pass this dictionary to each input field value. (This will be our final signup.html page).

{% extends 'store/base.html' %}

{% block content %}



<div class="container text-white">
    <div class="p-2 m-2">
        <div class="col-md-6 mx-auto pt-4 bg-dark">
            <h3 class="text-white text-center pb-2">Create an Account</h3>
            <form action="" method="post" class="bg-dark">
                {% csrf_token %}
                {% if error %}
                        <div class="alert alert-danger" role="alert">
                            {{error}}
                        </div>
                {% endif %}
                <div class="form-group">
                    <label for="firstname">Firstname</label>
                    <input type="text" value="{{value.first_name}}" name="firstname" class="form-control" placeholder="Firstname">
                </div>
                <div class="form-group">
                    <label for="lastname">Lastname</label>
                    <input type="text" value="{{value.last_name}}" name="lastname" class="form-control" placeholder="Lastname">
                </div>
                <div class="form-group">
                    <label for="lastname">Phone Number</label>
                    <input type="text" value="{{value.phone}}" name="phone" class="form-control" placeholder="Phone number">
                </div>
                <div class="form-group">
                    <label for="email">Email address</label>
                    <input type="email" value="{{value.email}}" name="email" class="form-control" placeholder="Enter your email">
                </div>
                <div class="form-group">
                    <label for="password">Password</label>
                    <input type="password" name="password" class="form-control" placeholder="Password">
                </div>

                {% if messages %}
                    {% for message in messages %}
                        <h6 class="text-white">{{message}}</h6>
                    {% endfor %}
                {% endif %}

                <div class="form-group">
                    <input type="submit" value="Register" class="btn btn-primary mt-1 mr-2 mb-3">
                    <a href="{% url 'login' %}">Login</a> Instead!
                </div>
            </form>
        </div>
    </div>
</div>
{% endblock %}


Now time to test the code. Lets try to register keeping email field empty.

image.png

There we go the email field is empty and at the same time, we have all the values in their respective input field after falsely registering.

image.png

If you register with correct credentials, it will redirect to home page. Now, its time to focus on email validation. Bootstrap email input field has its own email validation inbuilt. So, we won't focus on its structure rather we want to display the error message if any user try to register with already used email address. For this we will create a function inside Customer model that returns customer details with all email that has been provided as an argument. In the views.py, we will check the same function to return true or false value. If it returns true value then we want to show error message that email address is already registered. If it returns false value, then we want to register that new user. In models.py, this is our final Customer model.

class Customer(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    phone = models.IntegerField()
    email = models.EmailField()
    password = models.CharField(max_length=50)

    def __str__(self):
        return self.first_name + " " + self.last_name

    def does_exits(self):
        return Customer.objects.filter(email=self.email)


This is our views.py for email validation.

def signup(request):
    if request.method == 'GET':
        return render(request, 'store/signup.html')
    else:
        first_name = request.POST.get('firstname')
        last_name = request.POST.get('lastname')
        phone = request.POST.get('phone')
        email = request.POST.get('email')
        password = request.POST.get('password')

        values = {
            'firstname': first_name,
            'lastname': last_name,
            'phone': phone,
            'email': email,
        }
        customer = Customer(first_name=first_name, last_name=last_name, phone=phone, email=email, password=password)

        err_msg = None

        if not first_name:
            err_msg = "First Name Required."
        elif len(first_name) < 3:
            err_msg = "First Name must be 3 characters long."
        elif not last_name:
            err_msg = "Last Name Required."
        elif len(last_name) < 3:
            err_msg = "Last Name must be 3 characters long."
        elif not phone:
            err_msg = "Phone is Required."
        elif len(phone) < 10:
            err_msg = "Phone Number must be 10 characters long."
        elif not email:
            err_msg = "Email is Required."
        elif customer.does_exits():
            err_msg = "User with this email address already registered."
        if not err_msg:
            customer.save()
            return redirect('home')
        else:
            return render(request, 'store/signup.html', {'error_msg': err_msg, 'values': values})


Lets test our code. Lets try to register with already registered email.

image.png

Then, it displays the following error.

image.png

now, our validation is working correctly. Last thing remains i.e. we don't want to store password in plain text form rather we want to store in its hash form. It is pretty simple -first we import make_password and check_password from django.contrib.auth.hashers at the top. Just after if condition of where no error message is found, we would use make_password on customer password to hash it and then only call save() function to save the customer. So this would be our final signup view in views.py.

def signup(request):
    if request.method == 'GET':
        return render(request, 'store/signup.html')
    else:
        first_name = request.POST.get('firstname')
        last_name = request.POST.get('lastname')
        phone = request.POST.get('phone')
        email = request.POST.get('email')
        password = request.POST.get('password')

        values = {
            'firstname': first_name,
            'lastname': last_name,
            'phone': phone,
            'email': email,
        }
        customer = Customer(first_name=first_name, last_name=last_name, phone=phone, email=email, password=password)

        err_msg = None

        if not first_name:
            err_msg = "First Name Required."
        elif len(first_name) < 3:
            err_msg = "First Name must be 3 characters long."
        elif not last_name:
            err_msg = "Last Name Required."
        elif len(last_name) < 3:
            err_msg = "Last Name must be 3 characters long."
        elif not phone:
            err_msg = "Phone is Required."
        elif len(phone) < 10:
            err_msg = "Phone Number must be 10 characters long."
        elif not email:
            err_msg = "Email is Required."
        elif customer.does_exits():
            err_msg = "User with this email address already registered."
        if not err_msg:
            customer.password = make_password(customer.password)
            customer.save()
            return redirect('home')
        else:
            return render(request, 'store/signup.html', {'error_msg': err_msg, 'values': values})

Now lets test the code. Lets register a new user named as Test User3.

image.png

Now, if you check the dashboard and open that newly registered user, you can see the following:

image.png

You can see the password has been saved in the hash form. Now that concludes the end of our tutorial. In the next tutorial, we will be dealing with optimizing our code as you can see the signup view got so much long code with validation in it. Also, we will be dealing with login functionality in coming tutorial.

Find the fourth part tutorial's source code here.
Find the sample product images here.

Getting Started

Admin Dashboard link: http://127.0.0.1:8000/admin/
Username: testuser
Password: testuser321