Create a Order Manager with Django.

Christos Stathakis
25 min readJun 7, 2019

With the help of python, django and ajax. In this tutorial we will explore how we can connect django and ajax requests to front-end and create a simply Order Manager.

What we will do?

We will create a web app, which will be split on two django apps. The first one, we will manage our products, categories and anything we need to store and the second app will manage the order and how to sell the products.

What we are gonna learn?

Some basics info how we create a Retail Order, a simply connection with the warehouse, how will make asynchronous request to server with ajax, and how the server response to that and send a update.

Sources

Part 1.1 Create the Project

First move, let’s add django! I assume you have already installed the python 3.6 or 3.7 if not, grab it from the links above. After you have installed python you can add django. So open cmd to desktop or any folder you want and type this

$ pip install django
$ pip install django-tables2

And then

$ django-admin startproject blog_pos

After we created the django project lets create the apps. So now…

$ cd blog_pos
$ python manage.py startapp product
$ python manage.py startapp order

After that let’s register the apps we created to settings.py with the django_tables and add CURRENCY too.

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

'product',
'order',

'django_tables2',
]
CURRENCY = '€'

The urls.py we will complete it later when our views are done.

Part 1.2 Create the Product

On this part we will work on product app. On models.py which is on product folder let’s add this code.

from django.db import models
from django.conf import settings
from .managers import ProductManager

CURRENCY = settings.CURRENCY


class Category(models.Model):
title = models.CharField(max_length=150, unique=True)

class Meta:
verbose_name_plural = 'Categories'

def __str__(self):
return self.title

First let’s explain the imports. Depends on your country you are, you have a different currency so let’s import the variable CURRENCY from settings. The others import is the models and settings from django and the ProductManager which we will create in a while. With models django creates the tables and row for the database. Now the class Meta, with the verbose_name_plural we change the name we want to see on the admin page and the function __str__ is to return a value when the Product is called.

Now lets add our Product Model.

class Product(models.Model):
active = models.BooleanField(default=True)
title = models.CharField(max_length=150, unique=True)
category = models.ForeignKey(Category, null=True, on_delete=models.SET_NULL)
value = models.DecimalField(default=0.00, decimal_places=2, max_digits=10)
discount_value = models.DecimalField(default=0.00, decimal_places=2, max_digits=10)
final_value = models.DecimalField(default=0.00, decimal_places=2, max_digits=10)
qty = models.PositiveIntegerField(default=0)

objects = models.Manager()
broswer = ProductManager()

class Meta:
verbose_name_plural = 'Products'

def save(self, *args, **kwargs):
self.final_value = self.discount_value if self.discount_value > 0 else self.value
super().save(*args, **kwargs)

def __str__(self):
return self.title

def tag_final_value(self):
return f'{self.final_value} {CURRENCY}'
tag_final_value.short_description = 'Value'

Lets see the fields here. With active field we control the status if we want to sell it or not,then title and category not much to tell. The value is the initial price and discount price is the new price if we want to do a discount. We can just change the value here, but I wanted to do like this to make an example how you can handle the discount if you want. The final price calculates automatically when the object is saved, and is equal to discount_value if we use a discount else to value. This field will help us a lot in the Order and OrderItems models when we will create later.

Now lets create the managers.py. On product folder add the new file managers.py and inside it add this code.

from django.db import models


class ProductManager(models.Manager):

def active(self):
return self.filter(active=True)

def have_qty(self):
return self.active().filter(qty__gte=1)

With the manager we create our custom queries, which we will be easier to call. I use them as an example because is handy on bigger projects. So in this example we create the active query to get only the products we want to show and next with have_qty we filter them to check if we have qty in warehouse. We will not use the second query just added as example if you want to sell things that you actually have.

Last step the admin.py. With the following code we control how our admin will look. Django ships a default admin so we will use it! Let’s add this code..

from django.contrib import admin

from .models import Category, Product


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
search_fields = ['title']


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['title', 'category', 'tag_final_value', 'qty', 'active']
list_select_related = ['category']
list_filter = ['active', 'category']
search_fields = ['title']
list_per_page = 50
fields = ['active', 'title', 'category', 'qty', 'value', 'discount_value', 'tag_final_value']
autocomplete_fields = ['category']
readonly_fields = ['tag_final_value']

First the CategoryAdmin, which i added the search_fields so the autocomplete on ProductAdmin will work. Another tip here is the list_selected_related, which does less queries on the database. So if you have a ForeignKey and you want to show it on admin, it a good move to add it here too. And i added the tag_final_price on read_only because it’s a function not a field.

So part 1 its Done!

Part 2. Order Model

The movement. Source code is all the files i download from the site offered the template.

Before we create the models, let’s explain a little about the folders static and templates. Like a said before, we used a ready template theme so we need to create a way for our django server to serve the templates and the static files.So the first step was to create the folders i needed like the image above, and i transferred there all the static files from the source code. You can download all the static from my github repo or from the source.

Now the templates folder.

This time we only need to transfer the index file. I always prefer to break the big templates to smaller parts, so i created the folder include which contains the next files. To css.html i transfer all the files was on <head> tag and on js.html all the files with the <script> near the end of the body. To inform the index where is the transferred code, i used the {% include …. %} and with the help of {% static %} template tag, i created the connection with the static folder. One final part i did after the changes needed i added the {% block content %} …… {% endblock %} to create a dynamic part on my template so i can do easy the changes i want.

I strongly recommend to copy all templates and static file from repo, because i will not post all the code here.

Now let’s create our models for the order. Inside the folder order we can find a file called model.py, the usual suspect. Let’s add the first part of code.

This will be the final version when all done
from django.db import models
from django.db.models import Sum
from django.conf import settings
from django.urls import reverse
from django.dispatch import receiver
from django.db.models.signals import post_delete
import datetime
from product.models import Product

from decimal import Decimal
CURRENCY = settings.CURRENCY


class OrderManager(models.Manager):

def active(self):
return self.filter(active=True)

First part i did all the imports needed, and i created here the manager for the Order.

Next step the order model.

class Order(models.Model):
date = models.DateField(default=datetime.datetime.now())
title = models.CharField(blank=True, max_length=150)
timestamp = models.DateField(auto_now_add=True)
value = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)
discount = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)
final_value = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)
is_paid = models.BooleanField(default=True)
objects = models.Manager()
browser = OrderManager()

class Meta:
ordering = ['-date']

def save(self, *args, **kwargs):
order_items = self.order_items.all()
self.value = order_items.aggregate(Sum('total_price'))['total_price__sum'] if order_items.exists() else 0.00
self.final_value = Decimal(self.value) - Decimal(self.discount)
super().save(*args, **kwargs)

def __str__(self):
return self.title if self.title else 'New Order'

def get_edit_url(self):
return reverse('update_order', kwargs={'pk': self.id})

def get_delete_url(self):
return reverse('delete_order', kwargs={'pk': self.id})

def tag_final_value(self):
return f'{self.final_value} {CURRENCY}'

def tag_discount(self):
return f'{self.discount} {CURRENCY}'

def tag_value(self):
return f'{self.value} {CURRENCY}'

@staticmethod
def filter_data(request, queryset):
search_name = request.GET.get('search_name', None)
date_start = request.GET.get('date_start', None)
date_end = request.GET.get('date_end', None)
queryset = queryset.filter(title__contains=search_name) if search_name else queryset
if date_end and date_start and date_end >= date_start:
date_start = datetime.datetime.strptime(date_start, '%m/%d/%Y').strftime('%Y-%m-%d')
date_end = datetime.datetime.strptime(date_end, '%m/%d/%Y').strftime('%Y-%m-%d')
print(date_start, date_end)
queryset = queryset.filter(date__range=[date_start, date_end])
return queryset

A lot things happen here. Let’s explain the fields first. With date we save the date we want the order to be, the title is the notes, name of the order, timestamp save automatically the date and time the order is created, value will store the total amount of value from the order items we gonna sell, discount is here if we want to do a discount, final value id the value of our order if we remove the discount, with is_paid w check if the costumer have paid and finally browser is the connection with the custom manager we created before.

Lets see the rest.

class Meta:
ordering = ['-date']

def save(self, *args, **kwargs):
order_items = self.order_items.all()
self.value = order_items.aggregate(Sum('total_price'))['total_price__sum'] if order_items.exists() else 0.00
self.final_value = Decimal(self.value) - Decimal(self.discount)
super().save(*args, **kwargs)

With ordering on class Meta we create the ordering of the orders, now the save method. First line the order_items, here we create a easy query with the help of related_name we will see on OrderItem model and we get all order items the order have. Next step is the use of some django tools to do the calculations we need on database level. The tools we gonna use is aggregate and Sum, we will use them like the example above.

After we done with self.value, we update self.final_value. The reason we used Decimal here, is to ensure both values are on same format, we can’t add for example float and decimal we get us a error.

A little explain about aggregate and Sum. We always could calculate the value with loop, but using the method django provides us we can do the same on database level which i think is always faster and easier if you get used to it. So here after we get the queryset with the order_items, and we check if queryset.exists(), because if the queryset is empty and we try to do a calculate will always return a error and our app will broke, so if that happens we ensure to return 0.

def __str__(self):
return self.title if self.title else 'New Order'

def get_edit_url(self):
return reverse('update_order', kwargs={'pk': self.id})

def get_delete_url(self):
return reverse('delete_order', kwargs={'pk': self.id})

def tag_final_value(self):
return f'{self.final_value} {CURRENCY}'

def tag_discount(self):
return f'{self.discount} {CURRENCY}'

def tag_value(self):
return f'{self.value} {CURRENCY}'

This part on order model we create methods for the front end , we will explain later. Same with filters_data.

Now the next model the OrderItem.

class OrderItem(models.Model):
product = models.ForeignKey(Product, on_delete=models.PROTECT)
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='order_items')
qty = models.PositiveIntegerField(default=1)
price = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)
discount_price = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)
final_price = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)
total_price = models.DecimalField(default=0.00, decimal_places=2, max_digits=20)

def __str__(self):
return f'{self.product.title}'

def save(self, *args, **kwargs):
self.final_price = self.discount_price if self.discount_price > 0 else self.price
self.total_price = Decimal(self.qty) * Decimal(self.final_price)
super().save(*args, **kwargs)
self.order.save()

def tag_final_price(self):
return f'{self.final_price} {CURRENCY}'

def tag_discount(self):
return f'{self.discount_price} {CURRENCY}'

def tag_price(self):
return f'{self.price} {CURRENCY}'


@receiver(post_delete, sender=OrderItem)
def delete_order_item(sender, instance, **kwargs):
product = instance.product
product.qty += instance.qty
product.save()
instance.order.save()

Lets see the fields. With the use of ForeignKey we make a connect to product and order for the first two fields. The only difference is on the on_delete. On product, we use PROTECT so when we try to delete a product will raise an error if the product is used on order item, but in order, we use CASCADE because when we delete an order we want all order items get deleted too. Next is a city which we will store how many, if the product we going to sell, the price is how much cost per unit, discount _price if there is a discount, final_price is how much cost in the end and final total_price is the total.

def save(self,  *args, **kwargs):
self.final_price = self.discount_price if self.discount_price > 0 else self.price
self.total_price = Decimal(self.qty) * Decimal(self.final_price)
super().save(*args, **kwargs)
self.order.save()

Lets see the logic behind all that price values before explaining the save method. First the price and discount_price will be autocomplete when we create a new order item, details later. After that when that item is saved, we want to check if there is a discount price , if there is we make the final_value to be equal to discount_value else to be equal to the value. After that on total_value we multiply the qty with that save and we save the object. After the object is saved, we save the related order too, to update his fields like we said on order model.

The rest from the model is for the front-end will be explain later.

Now the signals. Django provides us that, which a fast explain is, its a code which run everywhere when we need to run. For example here we use the post_delete.


@receiver(post_delete, sender=OrderItem)
def delete_order_item(sender, instance, **kwargs):
product = instance.product
product.qty += instance.qty
product.save()

The first signal occurs when we delete a order, so we tell to the system before you delete the order find all order items and delete them. The second signal occurs we delete a order item, just before the delete we return the qty to warehouse.

Now lets create two more files, the tables.py and forms.py.

  • tables.py
import django_tables2 as tables

from product.models import Product
from .models import OrderItem, Order


import django_tables2 as tables

from product.models import Product
from .models import OrderItem, Order


class OrderTable(tables.Table):
tag_final_value = tables.Column(orderable=False, verbose_name='Value')
action = tables.TemplateColumn(
'<a href="{{ record.get_edit_url }}" class="btn btn-info"><i class="fa fa-edit"></i></a>', orderable=False)

class Meta:
model = Order
template_name = 'django_tables2/bootstrap.html'
fields = ['date', 'title', 'tag_final_value']


class ProductTable(tables.Table):
tag_final_value = tables.Column(orderable=False, verbose_name='Price')
action = tables.TemplateColumn(
'<button class="btn btn-info add_button" data-href="{% url "ajax_add" instance.id record.id %}">Add!</a>',
orderable=False
)

class Meta:
model = Product
template_name = 'django_tables2/bootstrap.html'
fields = ['title', 'category', 'tag_final_value']


class OrderItemTable(tables.Table):
tag_final_price = tables.Column(orderable=False, verbose_name='Price')
action = tables.TemplateColumn('''
<button data-href="{% url "ajax_modify" record.id "add" %}" class="btn btn-success edit_button"><i class="fa fa-arrow-up"></i></button>
<button data-href="{% url "ajax_modify" record.id "remove" %}" class="btn btn-warning edit_button"><i class="fa fa-arrow-down"></i></button>
<button data-href="{% url "ajax_modify" record.id "delete" %}" class="btn btn-danger edit_button"><i class="fa fa-trash"></i></button>
''', orderable=False)

class Meta:
model = OrderItem
template_name = 'django_tables2/bootstrap.html'
fields = ['product', 'qty', 'tag_final_price']

Here we going to benefit from the third party app django-tables. What does? is a fast way to create tables for front-end. Lets see it in action.

class OrderTable(tables.Table):
tag_final_value = tables.Column(orderable=False, verbose_name='Value')
action = tables.TemplateColumn(
'<a href="{{ record.get_edit_url }}" class="btn btn-info"><i class="fa fa-edit"></i></a>', orderable=False)

class Meta:
model = Order
template_name = 'django_tables2/bootstrap.html'
fields = ['date', 'title', 'tag_final_value']

First we create our table class which is inherited from Table. Works similar with django forms, on class Meta we register our model, we inform with template_name that we will use bootstrap, and on fields we choose what we want to show. Before the class Meta we declare two additional fields, the first we return a string with the final_value, and the second we create a new button which when user press it redirect to order page. We will explain the key features later

  • form.py
from django import forms

from .models import Order


class BaseForm(forms.Form):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'


class OrderCreateForm(BaseForm, forms.ModelForm):
date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))

class Meta:
model = Order
fields = ['date', 'title' ]


class OrderEditForm(BaseForm, forms.ModelForm):

class Meta:
model = Order
fields = ['date', 'title', 'discount', 'is_paid']

Because we use bootstrap on frontend,we create a BaseForm that informs all the fields get the class form-control. There are plenty ways to do this, but i prefer it to keep it simple here. Then the forms we want to create inherits from both BaseForm and forms.ModelForm. Not much here, on class Meta we inform the model and the fields!

Part 3.1 Front-End Part.

We will control all the front-end from the views.py on order model. But before do anything lets create our templates and static if you didn’t copy them already from github repo. You still can copy the templates and static folder and just read the explain.

So on folder templates, lets create all the files we needed to look as below.

The source file contains all the templates from the source.

Let’s see the css.html and js.html we spoke earlier which is to folder include.

css.html

{% load static %}

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">

<!-- Favicon icon -->
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'assets/images/favicon.png' %}">
<title>Matrix Template - The Ultimate Multipurpose admin template</title>
<!-- Custom CSS -->
<link href="{% static 'assets/libs/fullcalendar/dist/fullcalendar.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/extra-libs/calendar/calendar.css' %}" rel="stylesheet" />
<link href="{% static 'dist/css/style.min.css' %}" rel="stylesheet">

js.html

{% load static %}


<script src="{% static 'assets/libs/jquery/dist/jquery.min.js' %}"></script>
<script src="{% static 'dist/js/jquery.ui.touch-punch-improved.js' %}"></script>
<script src="{% static 'dist/js/jquery-ui.min.js' %}"></script>
<!-- Bootstrap tether Core JavaScript -->
<script src="{% static 'assets/libs/popper.js/dist/umd/popper.min.js' %}"></script>
<script src="{% static 'assets/libs/bootstrap/dist/js/bootstrap.min.js' %}"></script>
<!-- slimscrollbar scrollbar JavaScript -->
<script src="{% static 'assets/libs/perfect-scrollbar/dist/perfect-scrollbar.jquery.min.js' %}"></script>
<script src="{% static 'assets/extra-libs/sparkline/sparkline.js' %}"></script>
<!--Wave Effects -->
<script src="{% static 'dist/js/waves.js' %}"></script>
<!--Menu sidebar -->
<script src="{% static 'dist/js/sidebarmenu.js' %}"></script>
<!--Custom JavaScript -->
<script src="{% static 'dist/js/custom.min.js' %}"></script>
<!-- this page js -->
<script src="{% static 'assets/libs/moment/min/moment.min.js' %}"></script>
<script src="{% static 'assets/libs/fullcalendar/dist/fullcalendar.min.js' %}"></script>
<script src="{% static 'dist/js/pages/calendar/cal-init.js' %}"></script>

Not much here, we transfer all the css from the index.html here, and then with the help of {% static %} we make a connection to our static files. Sames goes for the js.

After that we moved all the static files from the source to static folder like below, so django can find them.

After we are done, lets analyse the index.html. First, we load the template tags we will need.

<!DOCTYPE html> {% load render_table from django_tables2 %} {% load static %}

Then we will use the include tag to add the css.html to our file.

<head>
{% include 'include/css.html' %}
</head>

Because is a big file, just get it from here → https://github.com/Zefarak/blog_pos/blob/master/order/templates/index.html

The next interest point is..

{% block content %}

which means inside here, we can change dynamically anything we want.

Now lets see how django communicate with the html files.

3.2 Views.py

First the imports.

from django.views.generic import ListView, CreateView, UpdateView
from django.utils.decorators import method_decorator
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import get_object_or_404, redirect, reverse
from django.contrib import messages
from django.template.loader import render_to_string
from django.http import JsonResponse
from django.db.models import Sum
from django_tables2 import RequestConfig
from .models import Order, OrderItem, CURRENCY
from .forms import OrderCreateForm, OrderEditForm
from product.models import Product
from .tables import ProductTable, OrderItemTable, OrderTable

import datetime

and then lets create the homepage view which we control the page contains the index.html.

@method_decorator(staff_member_required, name='dispatch')
class HomepageView(ListView):
template_name = 'index.html'
model = Order
queryset = Order.objects.all()[:10]

We want a smart way to ensure that only the staff have access to our views, so with method_decorator and staff_member_required before every class view we ensure this. Then we have to add the follow template_name, model and optional queryset. With template_name we do a connect with the template we will use and with model we inform the model we will use. Finally, because is our first page, we want only max 10 order to be show and with queryset we handle it.

Now lets add the get_context_data.

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
orders = Order.objects.all()
total_sales = orders.aggregate(Sum('final_value'))['final_value__sum'] if orders.exists() else 0
paid_value = orders.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum']\
if orders.filter(is_paid=True).exists() else 0
remaining = total_sales - paid_value
diviner = total_sales if total_sales > 0 else 1
paid_percent, remain_percent = round((paid_value/diviner)*100, 1), round((remaining/diviner)*100, 1)
total_sales = f'{total_sales} {CURRENCY}'
paid_value = f'{paid_value} {CURRENCY}'
remaining = f'{remaining} {CURRENCY}'
orders = OrderTable(orders)
RequestConfig(self.request).configure(orders)
context.update(locals())
return context

Here is all the magic. We control any action or data goes to front end. So let’s explain first the image below.

After we get again all orders, we can calculate the total sales, paid_value and the remaining value with the help of Sum and aggregate. Then we need to create the percent for the bar and to avoid our diviner = 0 we do this…

diviner = total_sales if total_sales > 0 else 1

And then we create the % and the strings.

paid_percent, remain_percent = round((paid_value/diviner)*100, 1), round((remaining/diviner)*100, 1)
total_sales = f'{total_sales} {CURRENCY}'
paid_value = f'{paid_value} {CURRENCY}'
remaining = f'{remaining} {CURRENCY}'

Before we show the strings we created let’s pass the table to front end.

orders = OrderTable(orders)
RequestConfig(self.request).configure(orders)

On index.html we just add the follow code and django_tables create the table for us.

{% render_table orders %}

And last we use the template tags we created before…

<div>
<div class="d-flex no-block align-items-center m-t-25">
<span>{{ paid_percent }}% Paid Value</span>
<div class="ml-auto">
<span>{{ paid_value }}</span>
</div>
</div>
<div class="progress">
<div class="progress-bar progress-bar-striped bg-success" role="progressbar" style="width: {{ paid_percent }}%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>

we add the paid_value on <span> because is string and the paid_percent because is number we pass it to width.

And done.

Next view is…

@staff_member_required
def auto_create_order_view(request):
new_order = Order.objects.create(
title='Order 66',
date=datetime.datetime.now()

)
new_order.title = f'Order - {new_order.id}'
new_order.save()
return redirect(new_order.get_edit_url())

When the user press the Create Auto button, we use this view to create a new order with the title order 66 and date the current date, after that we give a serious name to order and we redirect to order page.

Next part is…

@method_decorator(staff_member_required, name='dispatch')
class OrderListView(ListView):
template_name = 'list.html'
model = Order
paginate_by = 50

def get_queryset(self):
qs = Order.objects.all()
if self.request.GET:
qs = Order.filter_data(self.request, qs)
return qs

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
orders = OrderTable(self.object_list)
RequestConfig(self.request).configure(orders)
context.update(locals())
return context

Like the homepage view we use the ListView and we add our model and template name, but now because here we will handle all orders, we need to add a pagination to ensure that not all orders load every time make the user use the page or refresh. Another difference is the filters.

Filters
def get_queryset(self):
qs = Order.objects.all()
if self.request.GET:
qs = Order.filter_data(self.request, qs)
return qs

Because this time we have some filters to manage, first we get again all objects of Order and we check if there is a request.GET. If there is with this line we add the filters to our database, lets see how.

Lets head back now to folder Order and inside let’s open the models.py and recover the filters_data.

@staticmethod
def filter_data(request, queryset):
search_name = request.GET.get('search_name', None)
date_start = request.GET.get('date_start', None)
date_end = request.GET.get('date_end', None)
queryset = queryset.filter(title__contains=search_name) if search_name else queryset
if date_end and date_start and date_end >= date_start:
date_start = datetime.datetime.strptime(date_start, '%m/%d/%Y').strftime('%Y-%m-%d')
date_end = datetime.datetime.strptime(date_end, '%m/%d/%Y').strftime('%Y-%m-%d')
print(date_start, date_end)
queryset = queryset.filter(date__range=[date_start, date_end])
return queryset

First the staticmethod we used here is because we didn’t want to pass the self argument, but the function is about Order model. Then we used here 2 arguments, the queryset and request. First we passed the request from view to get the filters user used and then we passed the queryset so we can filter it. And when filtering is done we return the result.

On dates because django ORM need a specific type to understand them we use this to give date the format we want.

datetime.datetime.strptime(date_start, '%m/%d/%Y').strftime('%Y-%m-%d')

And of course because we left on front end the user to add any date he want before filtering we check this.

if date_end and date_start and date_end >= date_start:

And last let’s see this.

When button is pressed we use ajax request to load the results

First on our template list.html the buttons is like this…

<div class="card-header">
<button data-href="{% url 'ajax_calculate_result' %}" class="btn btn-info result_button">Order Analysis</button>
<button data-href="{% url 'ajax_category_result' %}" class="btn btn-info result_button">Category Analysis</button>
</div>

So when the user press one button the next script get in action.

<script>
$('.result_button').click(function (event) {
event.preventDefault();
const btn = $(this);
const href = btn.attr('data-href');
const params = window.location.search.substr(1);
const url = href + '?' + params;
$.ajax({
method: 'GET',
dataType: 'json',
url: url,

success: function (data) {
$('#result_container').html(data.result)
}
})
})
</script>

Lets explain, when someone hit a button with the class result_button i want to prevent any default action which will take movement, then i will bind this to const btn and we will get the url with the help of data-href and params. Then we will have all the data to do a call with ajax and then get a response.

When the call happens this view responses..

@staff_member_required
def ajax_calculate_results_view(request):
orders = Order.filter_data(request, Order.objects.all())
total_value, total_paid_value, remaining_value, data = 0, 0, 0, dict()
if orders.exists():
total_value = orders.aggregate(Sum('final_value'))['final_value__sum']
total_paid_value = orders.filter(is_paid=True).aggregate(Sum('final_value'))['final_value__sum'] if\
orders.filter(is_paid=True) else 0
remaining_value = total_value - total_paid_value
total_value, total_paid_value, remaining_value = f'{total_value} {CURRENCY}',\
f'{total_paid_value} {CURRENCY}', f'{remaining_value} {CURRENCY}'
data['result'] = render_to_string(template_name='include/result_container.html',
request=request,
context=locals())
return JsonResponse(data)

So here, from the ajax we get the params, i mean the GET params and we filter the orders. When filtering is done we calculate the total value, the remaining value etc and we pass them to a dictionary with the key ‘result’. To result_container.html we have created a table that will contains our data. After that we create the JsonResponse with the data.

Now back to our view which we have made the ajax request we have this line.

success: function (data) {
$('#result_container').html(data.result)
}

So when we do the ajax request and the server respond with success, we want to inject the data to the div with id result_html, and done!

If the user press the category button then this view trigger.

@staff_member_required
def ajax_calculate_category_view(request):
orders = Order.filter_data(request, Order.objects.all())
order_items = OrderItem.objects.filter(order__in=orders)
category_analysis = order_items.values_list('product__category__title').annotate(qty=Sum('qty'),
total_incomes=Sum('total_price')
)
data = dict()
category, currency = True, CURRENCY
data['result'] = render_to_string(template_name='include/result_container.html',
request=request,
context=locals()
)
return JsonResponse(data)

Next part is the CreateOrderView.

@method_decorator(staff_member_required, name='dispatch')
class CreateOrderView(CreateView):
template_name = 'form.html'
form_class = OrderCreateForm
model = Order

def get_success_url(self):
self.new_object.refresh_from_db()
return reverse('update_order', kwargs={'pk': self.new_object.id})

def form_valid(self, form):
object = form.save()
object.refresh_from_db()
self.new_object = object
return super().form_valid(form)

Here we connect the form we created before to create a new order with manual data. When the new order is created,we save it to self.new_object so we can use it on get_success_url and redirect on new order edit page.

Now the OrderUpdateView.

@method_decorator(staff_member_required, name='dispatch')
class OrderUpdateView(UpdateView):
model = Order
template_name = 'order_update.html'
form_class = OrderEditForm


def get_success_url(self):
return reverse('update_order', kwargs={'pk': self.object.id})

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
instance = self.object
qs_p = Product.objects.filter(active=True)[:12]
products = ProductTable(qs_p)
order_items = OrderItemTable(instance.order_items.all())
RequestConfig(self.request).configure(products)
RequestConfig(self.request).configure(order_items)
context.update(locals())
return context

The view that the whole article is wrote. Let see what we did.

@method_decorator(staff_member_required, name='dispatch')
class OrderUpdateView(UpdateView):
model = Order
template_name = 'order_update.html'
form_class = OrderEditForm

With the UpdateView we need a form, which can be found to form_class, success_url when the form is saved, ofcourse the template name and the model.

def get_success_url(self):
return reverse('update_order', kwargs={'pk': self.object.id})

We will override the success url with this function, and we want when the order is saved to return us on same page

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
instance = self.object
qs_p = Product.objects.filter(active=True)[:12]
products = ProductTable(qs_p)
order_items = OrderItemTable(instance.order_items.all())
RequestConfig(self.request).configure(products)
RequestConfig(self.request).configure(order_items)
context.update(locals())
return context

First because i like more the variable instance, i saved the object to instance , then we we had to call the products, but because i don’t want to load all and make the view response slow, i just called the last new 12 products and i passed them to django_tables, then go to same about the order items. So the page is like bellow.

Left screen is the ProductTable and on right OrderItemTable

Now we will use the search on left screen to update our products, if we need a different product than one of the last 12. So lets create a new file..

Create a new file called ajax_calls.html in folder include.

ajax_calls.html

<script type="text/javascript">
$('.add_button').click(function (evt) {
evt.preventDefault();
const btn = $(this);
const url = btn.attr('data-href');
$.ajax({
method: 'GET',
dataType: 'json',
url: url,
success : function (data) {
$('#order_item_container').html(data.result)
}
})
});

$('.edit_button').click(function (evt) {
evt.preventDefault();
const btn = $(this);
const url = btn.attr('data-href');
$.ajax({
method: 'GET',
url: url,
dataType: 'json',

success: function (data) {
$('#order_item_container').html(data.result)
}
})
});

$('.search_button').keyup(function (evt) {
evt.preventDefault();
const btn = $(this);
const val = btn.val();
const url = btn.attr('data-href');
$.ajax({
method:'GET',
dataType: 'json',
url: url + '?q='+val,

success: function (data) {
$('#product_container').html(data.products)
}
})
})
</script>

Lets analyse the search first.

$('.search_button').keyup(function (evt) {
evt.preventDefault();
const btn = $(this);
const val = btn.val();
const url = btn.attr('data-href');

Here when the user types something, when he releases the button we do the next. First we prevent the default behaviour, we don’t want some unexpected results, then we bind the button info to btn, then we get the val, which is what the user have typed and the url which is the endpoint on our server.

$.ajax({
method:'GET',
dataType: 'json',
url: url + '?q='+val,

success: function (data) {
$('#product_container').html(data.products)
}
})

After that we do the calling with ajax. The method and dataType is always needed, now on url because we want the params we add them manually so the url ends up like this https://testurl/search-me/?q=test_search. And if all goes all right on success, we gonna replace whole product container with the response.

product_container

Now lets see how we handle it on our server.

On view.py again we have a function called ajax_search_products.

@staff_member_required
def ajax_search_products(request, pk):
instance = get_object_or_404(Order, id=pk)
q = request.GET.get('q', None)
products = Product.broswer.active().filter(title__startswith=q) if q else Product.broswer.active()
products = products[:12]
products = ProductTable(products)
RequestConfig(request).configure(products)
data = dict()
data['products'] = render_to_string(template_name='include/product_container.html',
request=request,
context={
'products': products,
'instance': instance
})
return JsonResponse(data)

First, because we need the order instance we pass it on function with the pk. Then we get the q parameter that we added manually before and we filter the products with startswith if the param q exists, else we just return the active products. After that we minimize our queryset to 12 items and pass it to table. And finally we create the asynchronous response with the help of render_to_string and JsonResponse.

That’s how we update the products without needing to refresh whole page!

Now the add button. When we press the add button on product list we trigger this.

<script type="text/javascript">
$('.add_button').click(function (evt) {
evt.preventDefault();
const btn = $(this);
const url = btn.attr('data-href');
$.ajax({
method: 'GET',
dataType: 'json',
url: url,
success : function (data) {
$('#order_item_container').html(data.result)
}
})
});

Which is the same like before with 2 changes, first we don’t do a search now so no need to add params, and now we modify the order_item_container.

order_item_container

On server side the view handle the request is this.

@staff_member_required
def ajax_add_product(request, pk, dk):
instance = get_object_or_404(Order, id=pk)
product = get_object_or_404(Product, id=dk)
order_item, created = OrderItem.objects.get_or_create(order=instance, product=product)
if created:
order_item.qty = 1
order_item.price = product.value
order_item.discount_price = product.discount_value
else:
order_item.qty += 1
order_item.save()
product.qty -= 1
product.save()
instance.refresh_from_db()
order_items = OrderItemTable(instance.order_items.all())
RequestConfig(request).configure(order_items)
data = dict()
data['result'] = render_to_string(template_name='include/order_container.html',
request=request,
context={'instance': instance,
'order_items': order_items
}
)
return JsonResponse(data)

So here we get the order and the product which we need to add and then we check if there is a product item with this data with this nice line.

order_item, created = OrderItem.objects.get_or_create(..........)

If created is True, we need to add 1 qty and the prices from product price and if the product already exists we just add 1 more qty. Now we save the order _item , and the order get saved too because that way works on the model. And because of that we need to do a refresh to order to get the correct data.

instance.refresh_from_db()

After that, we recreate the order item table , we pass it to render_to string and send it in frontend with JsonResponse.

Now lets see how works the buttons on order_container. When a button is pressed will trigger the next script.

$('.edit_button').click(function (evt) {
evt.preventDefault();
const btn = $(this);
const url = btn.attr('data-href');
$.ajax({
method: 'GET',
url: url,
dataType: 'json',

success: function (data) {
$('#order_item_container').html(data.result)
}
})
});

and the server view which will handle the request is…

@staff_member_required
def ajax_modify_order_item(request, pk, action):
order_item = get_object_or_404(OrderItem, id=pk)
product = order_item.product
instance = order_item.order
if action == 'remove':
order_item.qty -= 1
product.qty += 1
if order_item.qty < 1: order_item.qty = 1
if action == 'add':
order_item.qty += 1
product.qty -= 1
product.save()
order_item.save()
if action == 'delete':
order_item.delete()
data = dict()
instance.refresh_from_db()
order_items = OrderItemTable(instance.order_items.all())
RequestConfig(request).configure(order_items)
data['result'] = render_to_string(template_name='include/order_container.html',
request=request,
context={
'instance': instance,
'order_items': order_items
}
)
return JsonResponse(data)

On this view we pass two parameters the pk and action. With pk we get the order item we want and with action we choose what to do with it. So after we get the order item and the order, we ask the action what to do, if is remove we remove 1 from qty, but because we don’t want to hit 0, we check it here to always return 1 qty at least.

if order_item.qty < 1: order_item.qty = 1

If action is add we just add 1 qty and if action is delete we delete the order item. After we done with action we save the object if needed and refresh again the order and we do the same as before.

The last two views left is…

@staff_member_required
def delete_order(request, pk):
instance = get_object_or_404(Order, id=pk)
instance.delete()
messages.warning(request, 'The order is deleted!')
return redirect(reverse('homepage'))


@staff_member_required
def done_order_view(request, pk):
instance = get_object_or_404(Order, id=pk)
instance.is_paid = True
instance.save()
return redirect(reverse('homepage'))

The first, we use it when we want to delete a order, and because we used

order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='order_items')

on OrderItem model, all order items is related to this order is deleted too.And with done_order_view we ensure that the order is paid.

Now its time to see the urls.py like we said before. So…

from django.contrib import admin
from django.urls import path

from order.views import (HomepageView, OrderUpdateView, CreateOrderView, delete_order,
OrderListView, done_order_view, auto_create_order_view,
ajax_add_product, ajax_modify_order_item, ajax_search_products, ajax_calculate_results_view,
order_action_view, ajax_calculate_category_view
)

urlpatterns = [
path('admin/', admin.site.urls),
path('', HomepageView.as_view(), name='homepage'),
path('order-list/', OrderListView.as_view(), name='order_list'),
path('create/', CreateOrderView.as_view(), name='create-order'),
path('create-auto/', auto_create_order_view, name='create_auto'),
path('update/<int:pk>/', OrderUpdateView.as_view(), name='update_order'),
path('done/<int:pk>/', done_order_view, name='done_order'),
path('delete/<int:pk>/', delete_order, name='delete_order'),
path('action/<int:pk>/<slug:action>/', order_action_view, name='order_action'),


# ajax_calls
path('ajax/search-products/<int:pk>/', ajax_search_products, name='ajax-search'),
path('ajax/add-product/<int:pk>/<int:dk>/', ajax_add_product, name='ajax_add'),
path('ajax/modify-product/<int:pk>/<slug:action>', ajax_modify_order_item, name='ajax_modify'),
path('ajax/calculate-results/', ajax_calculate_results_view, name='ajax_calculate_result'),
path('ajax/calculate-category-results/', ajax_calculate_category_view, name='ajax_category_result'),

]

So here we import any view we created and with the help of path, we give it a dynamic or not url. When our view needed a pk we ensure that’s the arg will be a int, and for arg action we used slug. for example this line

path('ajax/modify-product/<int:pk>/<slug:action>', ajax_modify_order_item, name='ajax_modify'),

Which on tables.py we used this line to make it work

action = tables.TemplateColumn('''
<button data-href="{% url "ajax_modify" record.id "add" %}" class="btn btn-success edit_button"><i class="fa fa-arrow-up"></i></button>
<button data-href="{% url "ajax_modify" record.id "remove" %}" class="btn btn-warning edit_button"><i class="fa fa-arrow-down"></i></button>
<button data-href="{% url "ajax_modify" record.id "delete" %}" class="btn btn-danger edit_button"><i class="fa fa-trash"></i></button>
''', orderable=False)

So as you can see the name ajax_modify is what we use on url, the record.id is what django_table understands about looping so we get here the id, and depends what we want to do we pass the add, remove or delete as the slug argument.

action = tables.TemplateColumn('''
<button data-href="{% url "ajax_modify" record.id "add" %}" class="btn btn-success edit_button"><i class="fa fa-arrow-up"></i></button>
<button data-href="{% url "ajax_modify" record.id "remove" %}" class="btn btn-warning edit_button"><i class="fa fa-arrow-down"></i></button>
<button data-href="{% url "ajax_modify" record.id "delete" %}" class="btn btn-danger edit_button"><i class="fa fa-trash"></i></button>
''', orderable=False)

How to make it work.

Now write the next command on cmd.

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver

And have fun!

--

--