Many online shops give out coupons to customers that can be redeemed for discounts on their purchases. An online coupon usually consists of a code that is given to users, valid for a specific time frame. The code can be redeemed one or multiple times.
We are going to create a coupon system for our shop. Our coupons will be valid for clients that enter the coupon in a specific time frame. The coupons will not have any limitations in terms of the number of times they can be redeemed, and they will be applied to the total value of the shopping cart. For this functionality, we will need to create a model to store the coupon code, a valid time frame, and the discount to apply.
Edit the settings.py file of myshop and add the application to the INSTALLED_APPS setting as follows:
The new application is now active in our Django project.
Let's start by creating the Coupon model. Edit the models.py file of the coupons application and add the following code to it:
from django.db import models
from django.core.validators import MinValueValidator, \
MaxValueValidator
class Coupon(models.Model):
code = models.CharField(max_length=50,
unique=True)
valid_from = models.DateTimeField()
valid_to = models.DateTimeField()
discount = models.IntegerField(
validators=[MinValueValidator(0),
MaxValueValidator(100)])
active = models.BooleanField()
def __str__(self):
return self.code
This is the model that we are going to use to store coupons. The Coupon model contains the following fields:
- code: The code that users have to enter in order to apply the coupon to their purchase.
- valid_from: The datetime value that indicates when the coupon becomes valid.
- valid_to: The datetime value that indicates when the coupon becomes invalid.
- discount: The discount rate to apply (this is a percentage, so it takes values from 0 to 100). We use validators for this field to limit the minimum and maximum accepted values.
- active: A Boolean that indicates whether the coupon is active.
Run the following command to generate the initial migration for the coupons application:
python manage.py makemigrations
The output should include the following lines:
Migrations for 'coupons':
coupons/migrations/0001_initial.py:
- Create model Coupon
Then, we execute the next command to apply migrations:
python manage.py migrate
You should see an output that includes the following line:
Applying coupons.0001_initial... OK
The migrations are now applied in the database. Let's add the Coupon model to the administration site. Edit the admin.py file of the coupons application and add the following code to it:
from django.contrib import admin
from .models import Coupon
class CouponAdmin(admin.ModelAdmin):
list_display = ['code', 'valid_from', 'valid_to',
'discount', 'active']
list_filter = ['active', 'valid_from', 'valid_to']
search_fields = ['code']
admin.site.register(Coupon, CouponAdmin)
The Coupon model is now registered in the administration site. Ensure that your local server is running with the command python manage.py runserver. Open http://127.0.0.1:8000/admin/coupons/coupon/add/ in your browser. You should see the following form:
Fill in the form to create a new coupon that is valid for the current date and make sure that you check the Active checkbox and click the SAVE button.
We can store new coupons and make queries to retrieve existing coupons. Now we need a way for customers to apply coupons to their purchases. The functionality to apply a coupon would be as follows:
- The user adds products to the shopping cart.
- The user can enter a coupon code in a form displayed in the shopping cart detail page.
- When a user enters a coupon code and submits the form, we look for an existing coupon with the given code that is currently valid. We have to check that the coupon code matches the one entered by the user that the active attribute is True, and that the current datetime is between the valid_from and valid_to values.
- If a coupon is found, we save it in the user's session and display the cart, including the discount applied to it and the updated total amount.
- When the user places an order, we save the coupon to the given order.
Create a new file inside the coupons application directory and name it forms.py. Add the following code to it:
from django import forms
class CouponApplyForm(forms.Form):
code = forms.CharField()
This is the form that we are going to use for the user to enter a coupon code. Edit the views.py file inside the coupons application and add the following code to it:
from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import Coupon
from .forms import CouponApplyForm
@require_POST
def coupon_apply(request):
now = timezone.now()
form = CouponApplyForm(request.POST)
if form.is_valid():
code = form.cleaned_data['code']
try:
coupon = Coupon.objects.get(code__iexact=code,
valid_from__lte=now,
valid_to__gte=now,
active=True)
request.session['coupon_id'] = coupon.id
except Coupon.DoesNotExist:
request.session['coupon_id'] = None
return redirect('cart:cart_detail')
The coupon_apply view validates the coupon and stores it in the user's session. We apply the require_POST decorator to this view to restrict it to POST requests. In the view, we perform the following tasks:
- We instantiate the CouponApplyForm form using the posted data and we check that the form is valid.
- If the form is valid, we get the code entered by the user from the form's cleaned_data dictionary. We try to retrieve the Coupon object with the given code. We use the iexact field lookup to perform a case-insensitive exact match. The coupon has to be currently active (active=True) and valid for the current datetime. We use Django's timezone.now() function to get the current time zone-aware datetime and we compare it with the valid_from and valid_to fields performing lte (less than or equal to) and gte (greater than or equa...