BUILDING E-COMMERCE WEBSITE USING DJANGO PYTHON: IMPLEMENTING ADD TO CART FUNCTIONALITY USING SESSION (PART-06)

in Programming/Dev6 months ago

Hello everyone. Welcome to the fifth part on the series "Building E-Commerce Website Using Django Python". In the last tutorial, we worked on implementing login functionality with our custom validation when incorrect credentials were entered. From this tutorial onward, we will be working with cart functionality using session. User can add products to cart without being logged in but s/he needs to sign in to checkout. We will show the products quantity in the cart, and under each product we will show how many that product is in the cart.

To see the preceding posts of the series along with the source code, you can visit the following link. I have placed the link to source code along with other files at the bottom of each post.

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 and Admin Dashboard Customization

What is Session?

Session is basically a series of action that users make on a particular site within a specified time frame. Django has a session framework that helps to store store and receive data as per user actions. When we use sessions in Django, this information is stored in the server and also the server sends a cookie named sessionid to the browser. At each HTTP requests, this cookie is sent to the server by our browser and Django uses it to get the session data and perform actions accordingly.

For an example, lets continue with our last tutorial. We will save customer id and email into the session when login credentials are correct. Just after checking flag is true, we can store customer objects in the session. For this we will write just two lines of code just after if flag: in our login view in views.py file. This is the final code for login view.

def login(request):
    if request.method == "GET":
        return render(request, 'store/login.html')
    else:
        email = request.POST.get('email')
        password = request.POST.get('password')
        customer = Customer.get_customer_by_email(email)
        error_msg = None
        if customer:
            flag = check_password(password, customer.password)
            if flag:
                request.session['customer_id'] = customer.id
                request.session['email'] = customer.email
                return redirect("home")
            else:
                error_msg = "Email or Password is incorrect."
        else:
            error_msg = "Email or Password is incorrect."
        return render(request, 'store/login.html', {'error_msg': error_msg})


We have created a dictionary to store customer id and email in the session. Now lets see if that works by printing the id and email after logging in one of our user. Lets print this in our homePage view that is also inside views.py file. This will be our final code in homePage view.

def homePage(request):
    products = None
    categories = Category.objects.all()
    category_id = request.GET.get('category')
    if category_id:
        products = Product.objects.filter(category=category_id)
    else:
        products = Product.objects.filter(category=1)
    context = {'products': products, 'categories': categories}
    print("Your Email Address is: ", request.session.get('email'))
    return render(request, 'store/home.html', context)


Now login with one of our stored user. I will use email as [email protected] with password demo123.

image.png

After clicking login, we can see this message in the terminal.

Screenshot_1.png

You can also see server stores sessionid in our browser cookies too.

image.png

Now, lets focus on implementing "Add to Cart" functionality. Many of the e-commerce applications out there uses JavaScript to handle this functionality and since I don't know any JavaScript, I will be implementing this one using Django. We want to send product id along with its quantity when user clicks on add to cart. This will be stored in dictionary and will be saved in session too. But since, we want to send product id along with quantity to the server, we would be working with form and POST method for this. Open store/home.html file and lets wrap that blue "Add to Cart" button inside a form where we will create another field to show the product id. We know that our add to cart button is inside Bootstrap card-body. So lets write the code for same inside this div class(I am not putting the whole code of our home.html here as it is unnecessary however there would be source code after completion of this tutorial in the bottom).

<div class="card-body text-white">
    <p class="card-title">{{product.name}}</p>
    <p class="card-text"><i class="fas fa-dollar-sign" style="margin-right: 2px; font-size: 15px;"></i>{{product.price}}</p>
    <form method="post" action="/">
        {% csrf_token %}
        <input type="text" value="{{product.id}}" name="product">
        <input type="submit" class="btn btn-primary btn-sm" value="Add to Cart">
    </form>
</div>



We have created one input field where we have shown the product id as we have already set a context for product in our views. Lets see how it looks in the browser now.

image.png

When we click on add to cart, it will redirect to the same page as defined in the form action. Here we are sending post data but we haven't specified in how post data are handled in our homePage view. For now we will just print the product id if the request method is post and put every code that we have already inside else tag i.e. if request method is get. This is our homePage view now.

def homePage(request):
    if request.method == 'POST':
        product = request.POST.get('product')
        print(product)
        return redirect('home')
    else:
        products = None
        categories = Category.objects.all()
        category_id = request.GET.get('category')
        if category_id:
            products = Product.objects.filter(category=category_id)
        else:
            products = Product.objects.filter(category=1)
        context = {'products': products, 'categories': categories}
        print("Your Email Address is: ", request.session.get('email'))
        return render(request, 'store/home.html', context)


Now lets see if our product id is being printed on the terminal after clicking on Add to Cart. Go to the browser and lets add "Blue Sleeve Shirt" to the cart and check the terminal.

Screenshot_2.png

Ok, our code is working till this point. Now we want to store this cart product in our session. The cart will be represented in dictionary form where key is the product id and the value is the quantity. Now we will write a code to check if the cart is in session. If it is in session, we will get the quantity and if there is quantity then we will increase the quantity by one. If not we will create a new cart object in the session and set the product in the cart to one. The corresponsing code in homePage view is:

def homePage(request):
    if request.method == 'POST':
        product = request.POST.get('product')
        cart = request.session.get('cart')
        if cart:
            quantity = cart.get(product)
            if quantity:
                cart[product] = quantity + 1
            else:
                cart[product] = 1
        else:
            cart = {}
            cart[product] = 1

        request.session['cart'] = cart
        print(request.session['cart'])
        return redirect('home')

    else:
        products = None
        categories = Category.objects.all()
        category_id = request.GET.get('category')
        if category_id:
            products = Product.objects.filter(category=category_id)
        else:
            products = Product.objects.filter(category=1)
        context = {'products': products, 'categories': categories}
        print("Your Email Address is: ", request.session.get('email'))
        return render(request, 'store/home.html', context)



Now, I will clear the cookies and see from beginning. I will add the first three product under Men's clothing and at last I will again add product with id 3 and see in the console.

image.png

You can see that the product with id 1 has quantity 1, id 2 has 1 quantity and product with id 3 has 2 quantity represented in dictionary form. Now we will be learning a new Django concepts that is called Filter. Basically it is difficult to write logic inside html code in software. And that is where we will use this concept of Filter. Suppose, in each product in our project we want to return True if that product is in cart and false if that product is not in cart. Just now, we have put three products with id 1,2 and 3 so it will show True and rest will show False. So lets create a directory templatetags inside store. And inside templatetags, we will create a python file called cart.py that will be used to return True or False value depending on whether the product is in cart or not.
This is our cart.py code.

from django import template

register = template.Library()

@register.filter(name='is_in_cart')
def is_in_cart(product, cart):
    keys = cart.keys()
    for id in keys:
        if int(id) == product.id:
            return True
    return False


We have used a register decorator to check if the product is in cart or not. First we grab all the keys of the cart. The keys here is the product id. And if if the integer value of that id equals to the product id then it means the product is in cart else it is not in the cart.

Now lets apply this filter in our home.html file. For that at top we need to load our cart.py file. So we write.

{% load cart %}

Lets print True or False value just after the product price by applying this filter.

<div class="card-body text-white">
    <p class="card-title">{{product.name}}</p>
    <p class="card-text"><i class="fas fa-dollar-sign" style="margin-right: 2px; font-size: 15px;"></i>{{product.price}}</p>
    {{product|is_in_cart:request.session.cart}}
    <form method="post" action="/">
        {% csrf_token %}
        <input hidden type="text" value="{{product.id}}" name="product">
        <input type="submit" class="btn btn-primary btn-sm" value="Add to Cart">
    </form>
</div>


Check the fourth line here where we have used the filter tag. We need to pass one parameter to our function defined in templatetags/cart.py. So we have passed cart in the session. Now refresh our browser to see the corresponding change.

image.png

You can see the last three products are not added to cart so showing false and the first three products were added to cart and showing True value. If you add one the product with False value then after reload it will show True. You maybe thinking why I am using this filter here. Just to make you simple, if there is True value then we don't want to show that Add to Cart button rather shows the button to increment and decrement the quantity in the cart along with the quantity. To implement such, we will modify our card-footer in home.html.

<div class="card-footer p-0 no-gutters">
    {% if product|is_in_cart:request.session.cart %}
    <div class="row no-gutters">
        <input type="submit" value="-" class="col-lg-2 btn btn-dark btn-block">
        <div class=" text-center mt-2 text-white col">2 in Cart</div>
        <input type="submit" value="+" class="col-lg-2 btn btn-dark btn-block">
    </div>
    {% else %}
    <form method="post" action="/" class="btn btn-block">
        {% csrf_token %}
        <input hidden type="text" value="{{product.id}}" name="product">
        <input type="submit" class="btn btn-primary" value="Add to Cart">
    </form>
    {% endif %}
</div>
</div>


Now refresh the page to see how it looks.

image.png

Now we can see "Add to Cart" for those products which are not added to the cart and the quantity increment and decrement buttons for the product already in the cart. Now lets implement the logic for showing actual product quantity for each product rather than 2 in every products which is false. Again the logic to count the product quantity can't be done in HTML code. So we will be writing logic inside cart.py which is again inside templatetags. This code goes inside cart.py.

@register.filter(name='cart_quantity')
def cart_quantity(product, cart):
    keys = cart.keys()
    for id in keys:
        if int(id) == product.id:
            return cart.get(id)
    return 0

Now come to home.html and replace the code inside card-body with this one:

<div class="card-footer p-0 no-gutters">
    {% if product|is_in_cart:request.session.cart %}
    <div class="row no-gutters">
        <input type="submit" value="-" class="col-lg-2 btn btn-dark btn-block">
        <div class=" text-center mt-2 text-white col">{{product|cart_quantity:request.session.cart}} in Cart</div>
        <input type="submit" value="+" class="col-lg-2 btn btn-dark btn-block">
    </div>
    {% else %}
    <form method="post" action="/" class="btn btn-block">
        {% csrf_token %}
        <input hidden type="text" value="{{product.id}}" name="product">
        <input type="submit" class="btn btn-primary" value="Add to Cart">
    </form>
    {% endif %}
</div>


We have just used filter to show the product quantity from the cart. Refresh your browser to see the change.

image.png

Now we will work with incrementing and decrementing the quantity in the cart, which is the final part of our tutorial. First of all, lets work with increment. We will just create a form in the input tag that has increment sign and we will create one hidden input field as above that shows product id.

<div class="card-footer p-0 no-gutters">
    {% if product|is_in_cart:request.session.cart %}
    <div class="row no-gutters">
        <input type="submit" value="-" class="col-lg-2 btn btn-dark btn-block">
        <div class=" text-center mt-2 text-white col">{{product|cart_quantity:request.session.cart}} in Cart</div>
        <form action="/" method="post" class="col-lg-2">
            {% csrf_token %}
            <input hidden type="text" value="{{product.id}}" name="product">
            <input type="submit" value="+" class="btn btn-dark btn-block">
        </form>
    </div>
    {% else %}
    <form method="post" action="/" class="btn btn-block">
        {% csrf_token %}
        <input hidden type="text" value="{{product.id}}" name="product">
        <input type="submit" class="btn btn-primary" value="Add to Cart">
    </form>
    {% endif %}
</div>

Lets go to the browser and add some products into the cart clicking "+" sign.

image.png

It is working from my end. Now lets create form for decrementing quantity with post request method. Our home.html will be this:

<div class="card-footer p-0 no-gutters">
    {% if product|is_in_cart:request.session.cart %}
    <div class="row no-gutters">
        <form action="/" method="post" class="col-lg-2">
            {% csrf_token %}
            <input hidden type="text" value="{{product.id}}" name="product">
            <input hidden type="text" value="True" name="remove">
            <input type="submit" value="-" class="btn btn-dark btn-block">
        </form>
        <div class=" text-center mt-2 text-white col">{{product|cart_quantity:request.session.cart}} in Cart</div>
        <form action="/" method="post" class="col-lg-2">
            {% csrf_token %}
            <input hidden type="text" value="{{product.id}}" name="product">
            <input type="submit" value="+" class="btn btn-dark btn-block">
        </form>
    </div>
    {% else %}
    <form method="post" action="/" class="btn btn-block">
        {% csrf_token %}
        <input hidden type="text" value="{{product.id}}" name="product">
        <input type="submit" class="btn btn-primary" value="Add to Cart">
    </form>
    {% endif %}
</div>


Now lets write some logic in view when the user clicks the "-" button. You can see we have set one hidden input field with name "remove" and value set to True. We will see if there is remove and check the quantity. If quantity <= 1, then on clicking it, we will pop the corresponding product from the cart. The logic will be in homePage view inside views.py.

def homePage(request):
    if request.method == 'POST':
        product = request.POST.get('product')
        remove = request.POST.get('remove')
        cart = request.session.get('cart')
        if cart:
            quantity = cart.get(product)
            if quantity:
                if remove:
                    if quantity <= 1:
                        cart.pop(product)
                    else:
                        cart[product] = quantity - 1
                else:
                    cart[product] = quantity + 1
            else:
                cart[product] = 1
        else:
            cart = {}
            cart[product] = 1

        request.session['cart'] = cart
        print(request.session['cart'])
        return redirect('home')

    else:
        products = None
        categories = Category.objects.all()
        category_id = request.GET.get('category')
        if category_id:
            products = Product.objects.filter(category=category_id)
        else:
            products = Product.objects.filter(category=1)
        context = {'products': products, 'categories': categories}
        print("Your Email Address is: ", request.session.get('email'))
        return render(request, 'store/home.html', context)


Now lets check the application by clicking minus.

image.png

After removing all the items, we will get "Add to Cart" button under corresponding product.


Find the fifth part 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

Note: All the user in the dashboard has been saved with common password i.e. demo123 if you like to test the functionality of the project.