BUILDING E-COMMERCE WEBSITE USING DJANGO PYTHON: AUTHENTICATION MIDDLEWARE, CART ITEMS COUNT AND LISTING CUSTOMER ORDER (PART - 09)

Hello everyone, welcome to the last part tutorial of our series Building E-Commerce Website Using Django Python". In the last part, we implemented order placement and checkout processes. In this tutorial, we are going to list order in customer page and show order status for customer, show number of items in the cart. And finally, we will implement a simple middleware to stop users from accessing order page if not logged in and redirect them back to the login page if they try to access the order page. If you want to visit the previous part or start from the beginning, you can find the links to all previous parts below.

First Part: Getting Started
Second Part: Admin, Models and Data Rendering
Third Part: Template Inheritance, Bootstrap and Static Files
Fourth Part: Signup Form Validation
Fifth Part: Login Functionality, Admin Dashboard Customization
Sixth Part: Add to Cart Functionality
Seventh Part: Hiding Navbars, Logout, Fetching Cart Products and Using Django Filter for Currency
Eighth Part: Order Placement and Checkout Form

First we want to show customer orders in their page. For this, we will need to create a new template for showing order page. We will reuse cart page design for order page too. Go to store/templates/store and create a new HTML file called order.html. We will place this code inside this new file. Note that this is just for demo purpose. Later in this tutorial, we will modify this code for showing corresponding customer's order.

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

{% load cart %}

{% block content %}


<div class="container text-white p-4">
    <p class="display-4">Your Orders</p>
    <hr style="background: white;">
    <table class="table">
        <thead>
        <tr class="bg-primary">
            <th>S.N</th>
            <th>Image</th>
            <th>Product</th>
            <th>Price</th>
            <th>Quantity</th>
            <th>Total</th>
        </tr>
        </thead>
        <tbody>
        {% for i in cart_product %}
        <tr>
            <td>{{forloop.counter}}</td>
            <td><img class="rounded-circle" height="60px" src="{{i.image.url}}"></td>
            <td>{{i.name}}</td>
            <td>{{i.price|currency}}</td>
            <td>{{i|cart_quantity:request.session.cart}}</td>
            <td>{{i|total_price:request.session.cart|currency}}</td>
        </tr>
        {% endfor %}
        </tbody>

    </table>

{% endblock %}


Now the next thing is lets create a simple view for order page to see how this template looks. Go to store/views.py and write this simple function view.

def orderpage(request):
    return render(request, 'store/order.html')


The next thing we need to do is URL mapping to this view. For this open store/urls.py and add this path to the list of urlpatterns at the end.

urlpatterns = [
    path('order', views.orderpage, name="order")
]


Now go to your terminal and run the server using python manage.py runserver command. Then go to browser and type http://127.0.0.1:8000/order. You will see the following blank screen.

image.png

We will now fetch the products from our order database to this one. For this we need to know, which customer is logged in. So in our views we will grab customer information using the session. Then in our store/models.py, we will create a function that will return an order of a customer bases on its id. So in store/models.py just after the date in the same line, we will create this method.

class Order(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    price = models.IntegerField()
    address = models.CharField(max_length=100)
    phone = models.CharField(max_length=10)
    date = models.DateTimeField(default=datetime.datetime.today)

    def get_order_by_customer(customer_id):
        return Order.objects.filter(customer=customer_id)


Open store/views.py and add this additional code to Orderpage view.

def orderpage(request):
    customer = request.session.get('customer')
    order = Order.get_order_by_customer(customer)
    print(order)
    return render(request, 'store/order.html', {'order': order})


Now, its time to render data dynamically to our order page. Open templates/store/order.html and place this code inside it.

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

{% load cart %}

{% block content %}


<div class="container text-white p-4">
    <p class="display-4">Your Orders</p>
    <hr style="background: white;">
    <table class="table">
        <thead>
        <tr class="bg-primary">
            <th>S.N</th>
            <th>Image</th>
            <th>Product</th>
            <th>Price</th>
            <th>Quantity</th>
            <th>Total</th>
        </tr>
        </thead>
        <tbody>
        {% for i in order %}
        <tr>
            <td>{{forloop.counter}}</td>
            <td><img class="rounded-circle" height="60px" src="{{i.product.image.url}}"></td>
            <td>{{i.product.name}}</td>
            <td>{{i.price|currency}}</td>
            <td>{{i.quantity}}</td>
            <td>{{i|total_price:request.session.cart|currency}}</td>
        </tr>
        {% endfor %}
        </tbody>

    </table>

{% endblock %}


If you view your order page now then you can see this:

image.png

The total column in the table is not working properly. For this one, we need to multiply the quantity with the price. So we will create a filter called multiply that will take two values and return their multiplication. Then we will apply this filter to this table column that will take quantity and price as two arguments and return value accordingly. We have already discussed about what is filter and how to use it. So open store/templatetags/cart.py and add this filter to the end.

@register.filter(name="multiply")
def multiply(n1, n2):
    return n1 * n2



Now lets replace that total column html code with this one in our order.html.

<td>{{i.quantity|multiply:i.price|currency}}</td>



Now if you refresh your order page in browser, you will see the following output.

image.png

Also lets add one more column to this order table that will show when your order was placed. The code inside order.html reflecting this new function will be as below:

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

{% load cart %}

{% block content %}


<div class="container text-white p-4">
    <p class="display-4">Your Orders</p>
    <hr style="background: white;">
    <table class="table">
        <thead>
        <tr class="bg-primary">
            <th>S.N</th>
            <th>Image</th>
            <th>Product</th>
            <th>Date</th>
            <th>Price</th>
            <th>Quantity</th>
            <th>Total</th>
        </tr>
        </thead>
        <tbody>
        {% for i in order %}
        <tr>
            <td>{{forloop.counter}}</td>
            <td><img class="rounded-circle" height="60px" src="{{i.product.image.url}}"></td>
            <td>{{i.product.name}}</td>
            <td>{{i.price|currency}}</td>
            <td>{{i.date}}</td>
            <td>{{i.quantity}}</td>
            <td>{{i.quantity|multiply:i.price|currency}}</td>
        </tr>
        {% endfor %}
        </tbody>

    </table>

{% endblock %}



The output is now as below:

image.png


Now lets focus on adding another functionality i.e. order status to our application. For this we will modify our order model by adding new database field called status that will be a Boolean value i.e. true or false. If it is set to true then it means that order is completed and if it is set to false then it means that order is pending. So open store/models.py and lets modify our Order model by adding this one line of code:

class Order(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
    quantity = models.IntegerField()
    price = models.IntegerField()
    address = models.CharField(max_length=100)
    phone = models.CharField(max_length=10)
    date = models.DateTimeField(default=datetime.datetime.today)
    status = models.BooleanField(default=False)

    def get_order_by_customer(customer_id):
        return Order.objects.filter(customer=customer_id)

Now lets apply migration by typing python manage.py makemigrations command first and then python manage.py migrate second. Now you run your server and check one of your order in the admin dashboard. You will see a new field there as below:

image.png

The status by default, we have set to false. So that's why it is unchecked there. The admin after delivery of products can check the status as true and it will be shown as order completed in Customer page. We need to add some lines of code to show status column in our order.html and its status accordingly to each of its cells. For this we add a new table heading (<th>) inside table head (<thead>).

<th>Status</th>

After that inside table body (<tbody>), we will add a new table data (<td>) with Django logic. If order status is true then we need to show Completed in table data and if not we need to show Pending in table data. This is a simple logic.

{% if i.status %}
<td class="text-success">Completed</td>
            {% else %}
            <td class="text-warning">Pending</td>
            {% endif %}

The final code in our order.html page is now:

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

{% load cart %}

{% block content %}


<div class="container text-white p-4">
    <p class="display-4">Your Orders</p>
    <hr style="background: white;">
    <table class="table">
        <thead>
        <tr class="bg-primary">
            <th>S.N</th>
            <th>Image</th>
            <th>Product</th>
            <th>Date</th>
            <th>Price</th>
            <th>Quantity</th>
            <th>Total</th>
            <th>Status</th>
        </tr>
        </thead>
        <tbody>
        {% for i in order %}
        <tr>
            <td>{{forloop.counter}}</td>
            <td><img class="rounded-circle" height="60px" src="{{i.product.image.url}}"></td>
            <td>{{i.product.name}}</td>
            <td>{{i.price|currency}}</td>
            <td>{{i.date}}</td>
            <td>{{i.quantity}}</td>
            <td>{{i.quantity|multiply:i.price|currency}}</td>
            {% if i.status %}
            <td class="text-success">Completed</td>
            {% else %}
            <td class="text-warning">Pending</td>
            {% endif %}
        </tr>
        {% endfor %}
        </tbody>

    </table>

{% endblock %}

Now refresh your browser to see the order page.

image.png

By default all orders are shown pending. Lets set one of our status to True through admin dashboard. I will set the first order i.e. Red T-shirt to true from dashboard and save it.

image.png

Now if you refresh your browser and see the order page, you will now see this change. Customer now can see which order is pending and which one is completed.

image.png


Now lets move on showing the number of items in the cart. We want to show the number of items in that cart menu in the navbar. For this go to store/templates/base.html. We will then add a span tag to the cart menu and add a Bootstrap badge class to show the items in the cart.

<li class="nav-item">
    <a class="nav-link text-white" href="{% url 'cart' %}">Cart
        <span class="badge badge-danger">{{request.session.cart.keys|length}}</span>
    </a>
</li>


If you remember from previous tutorial while implementing Add to Cart function, we have done that through dictionary where key is the product id and the value is its quantity and we have stored every information related to cart in our session. So we grabbed the keys from cart and used default Django filters called length to find its length. The length is the number of items in the cart. Now, if you visit the homepage, you will see this.

image.png

I don't have any products added to the cart now. Let me add few products to the cart and I will show you how the count looks.

image.png

You can see now that I have four items added to the cart.


Now the final part of our tutorial is implementing the middleware functionality to prevent the logged out users or new users of our site from accessing order page. Up to this point for our application, if you clicked logged out and if you visit the order page then empty order page will be shown. We want to show this order page whether empty or filled only if the user is logged in. We will use this simple middleware from official Django documentation. Create a new directory named as "middlewares" inside store. We will create a new python file called as auth.py inside middlewares directory. Then I will copy paste the code inside this file from the django official documentation website that I just mentioned. I am copying this one:

image.png

I will rename simple_middleware to auth_middleware. And inside the middleware function, I need to specify that is there is no customer information in the session, then we want to serve the login page. So to server login page we need to make quick import of redirect from django.shortcuts. So this is our code inside auth.py:

from django.shortcuts import redirect

def auth_middleware(get_response):


    def middleware(request):
        if not request.session.get('customer'):
            return redirect("login")
        
        response = get_response(request)
        return response

    return middleware


Now we should apply this middleware to our Orderpage view in the form of decorator. Open store/views.py and just above the orderpage view function, apply this decorator. To apply this decorator, at the top of file make an import of this middleware file.

from .middlewares.auth import auth_middleware

@auth_middleware
def orderpage(request):
    customer = request.session.get('customer')
    order = Order.get_order_by_customer(customer)
    print(order)
    return render(request, 'store/order.html', {'order': order})



Now lets test our application if the logged out user is being prevented from accessing the order page. I will delete every cookie for now. Now then try to access the order page, then it should show me a login page.

bandicam 2021-04-28 14-39-25-850.gif

So this marks the end of our series. We have every common functionalities of a simple shopping cart application. You can make this project bigger one by adding new functionalities. In the future, I will be adding pagination, sending email to users about their order in pdf and maybe deploying in AWS cloud and so on.

You can find the complete source code of this project here.
For the sample images used in this project, you can find it here.


Getting Started

To get access to the admin dashboard, type http://127.0.0.1:8000/admin/ in the browser.
Username: testuser
Password: testuser321

Login Credentials for testing the application:

Email: user5@gmail.com
Password: demo123

Email: User3@gmail.com
Password: demo123

2 Comments