Building a Food Delivery App With Django and Python 3: Part 2 Building the Ordering System Part 1

LegionScript
6 min readOct 20, 2020

--

Video Tutorial

Code on Github

In this tutorial we are going to begin to build the ordering system for our food delivery web application. This will involve a lot of different details to get it functional so we will break it up into 2 different tutorials. In this tutorial we will set up the form to select menu items and submit them. After submitting, we will show a order summary with a total price. That will give us a basic version working that we can build off of next time.

Before we get into building all of this, we need to set up our media settings so we can store image files. First, we need to install pillow:

pip install Pillow

We also need to add a line at the end of our urls.py:

urlpatterns = []+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Finally we need to add some lines to our settings.py:

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

Finally, let’s create a path of /media/menu_items. This is where our images will be stored.

Now with that set up, let’s get started by setting up the data structures of what we will be saving in the database.

In our customer/models.py:

from django.db import modelsclass MenuItem(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
image = models.ImageField(upload_to='menu_images/')
price = models.DecimalField(max_digits=5, decimal_places=2)
category = models.ManyToManyField('Category', related_name='item')
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class OrderModel(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
price = models.DecimalField(max_digits=7, decimal_places=2)
items = models.ManyToManyField('MenuItem', related_name='order', blank=True)
def __str__(self):
return f'Order: {self.created_on.strftime("%b %d %Y %I:%M %p")}'

We are creating three different objects here. The first one is the different menu item options we will give our customers to choose from. The next is a category that we will assign to each menu item. We will link these together with the category ManyToMany field on our MenuItem. The final object we are creating is the actual order. Once we submit an order, this will be crated and we will link all of the products that were selected to it. This will be used on the restaurant side later on.

Before continuing you will need to go into the admin panel and add some menu items and categories.

Now that we have some menu items added, let’s create our view for this in our customer/views.py:

class Order(View):    def get(self, request, *args, **kwargs):
appetizers = MenuItem.objects.filter(category__name__contains='Appetizer')
entres = MenuItem.objects.filter(category__name__contains='Entre')
desserts = MenuItem.objects.filter(category__name__contains='Dessert')
drinks = MenuItem.objects.filter(category__name__contains='Drink')
context = {
'appetizers': appetizers,
'entres': entres,
'desserts': desserts,
'drinks': drinks,
}
return render(request, 'customer/order.html', context)
def post(self, request, *args, **kwargs): order_items = {
'items': []
}
items = request.POST.getlist('items[]')
for item in items:
menu_item = MenuItem.objects.get(pk__contains=int(item))
item_data = {
'id': menu_item.pk,
'name': menu_item.name,
'price': menu_item.price,
}
order_items['items'].append(item_data)

price = 0
item_ids = []
for item in order_items['items']:
price += item['price']
item_ids.append(item['id'])

order = OrderModel.objects.create(price=price)
order.items.add(*item_ids)
context = {
'items': order_items['items'],
'price': price
}
return render(request, 'customer/order_confirmation.html', context)

This is a complicated view, let’s start with the get method. First we are grabbing all of the menu items for each category that we created. We are then passing those items into our template.

For the post method, we need to handle the form submission. We are creating a dictionary with an items list. This is where we will store our selected items. We can then use request.POST.getlist(‘items[]’). This will grab all of the selected items with a name of ‘items[]’ which we will give to each of the items in our template. So this will grab every checked item. We can then go and loop through this list, get each item from the database and get all of the needed information from it. Then we can append it to our list in the dictionary.

Next we need to get each price and id. We need each price to calculate a total price and we need each id to add it as a relationship to our order object that we are about to create. So we loop thorugh our items and get those two pieces of information. We create an order with OrderModel.objects.create(price=price). The order.items.add(*items) line will go through each item id in the list and add it to the order that we just made. Finally we pass all of this information into the context and render the template.

Now let’s create our HTML templates. First let’s build the order.html template.

{% extends 'customer/base.html' %}{% block content %}
<div class="container mb-5">
<div class="row justify-content-center mt-1">
<div class="col-md-12 col-sm-12 p-4">
<form method="POST">
{% csrf_token %}
<div class="pt-5">
{% for app in appetizers %}
<div class="row">
<div class="col-md-2 col-sm-12">
<img class="rounded" src="{{ app.image.url }}" width="150" height="100"/>
</div>
<div class="col-md-8 col-sm-12">
<div class="d-flex flex-row">
<div class="form-group form-check">
<input type="checkbox" name="items[]" class="form-check-input" value="{{ app.pk }}">
<label class="form-check-label">{{ app.name }}</label>
</div>
<p class="font-weight-bold pl-5">{{ app.price }}</p>
</div>
<p>{{ app.description }}</p>
</div>
</div>
<hr />
{% endfor %}
</div>
<div class="pt-5">
{% for entre in entres %}
<div class="row mt-4">
<div class="col-md-2 col-sm-12">
<img class="rounded" src="{{ entre.image.url }}" width="150" height="100"/>
</div>
<div class="col-md-8 col-sm-12">
<div class="d-flex flex-row">
<div class="form-group form-check">
<input type="checkbox" name="items[]" class="form-check-input" value="{{ entre.pk }}">
<label class="form-check-label">{{ entre.name }}</label>
</div>
<p class="font-weight-bold pl-5">{{ entre.price }}</p>
</div>
<p>{{ entre.description }}</p>
</div>
</div>
<hr />
{% endfor %}
</div>
<div class="pt-5">
{% for dessert in desserts %}
<div class="row mt-4">
<div class="col-md-2 col-sm-12">
<img class="rounded" src="{{ dessert.image.url }}" width="150" height="100"/>
</div>
<div class="col-md-8 col-sm-12">
<div class="d-flex flex-row">
<div class="form-group form-check">
<input type="checkbox" name="items[]" class="form-check-input" value="{{ dessert.pk }}">
<label class="form-check-label">{{ dessert.name }}</label>
</div>
<p class="font-weight-bold pl-5">{{ dessert.price }}</p>
</div>
<p>{{ dessert.description }}</p>
</div>
</div>
<hr />
{% endfor %}
</div>
<div class="pt-5">
{% for drink in drinks %}
<div class="row mt-4">
<div class="col-md-2 col-sm-12">
<img class="rounded" src="{{ drink.image.url }}" width="150" height="100"/>
</div>
<div class="col-md-8 col-sm-12">
<div class="d-flex flex-row">
<div class="form-group form-check">
<input type="checkbox" name="items[]" class="form-check-input" value="{{ drink.pk }}">
<label class="form-check-label">{{ drink.name }}</label>
</div>
<p class="font-weight-bold pl-5">{{ drink.price }}</p>
</div>
<p>{{ drink.description }}</p>
</div>
</div>
<hr />
{% endfor %}
</div>
<button class="btn btn-dark mt-5">Place Order!</button>
</form>
</div>
</div>
</div>
{% endblock content %}

In this file, we are looping through each category and listing out each menu item in the database. We are creating a checkbox to allow the user to select the items they want to purchase. In each checkbox there are two important additions. There is a name set as “items[]” for each of the items there is also a value that is set to that item’s primary key. When we grab the selected we are going to find them by using the name using the getlist function in our views and it will return a list of the values of the selected items, which in this case is the primary keys. Finally, there is a button at the bottom that submits the form as a post request.

Now let’s build the order confirmation after they place an order.

{% extends 'customer/base.html' %}{% block content %}
<div class="container mb-5">
<div class="row justify-content-center mt-1">
<div class="col-md-5 col-sm-12 p-4 text-center">
<h1>Order Submitted!</h1>
<p>You should receive a confirmation email soon.</p>
<a href="{% url 'index' %}">Go to the homepage</a>
</div>
</div>
<div class="row justify-content-center mt-5">
<div class="col-md-5 col-sm-12 text-center">
<h3 class="pb-3">Order Summary:</h3>
{% for item in items %}
<p>{{ item.name }} <span class="pl-3">{{ item.price }}</span></p>
{% endfor %}
<p class="font-weight-bold pt-4">Total: {{ price }}</p>
</div>
</div>
</div>
{% endblock content %}

This is just going to display a link back to the homepage and a summary this will be used during the post method in our view.

Now that we have our views and HTML templates created the only thing left is to set up our urls.py.

from django.contrib import admin
from django.urls import path, include
from customer.views import Index, About, Order
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', Index.as_view(), name='index'),
path('about/', About.as_view(), name='about'),
path('order/', Order.as_view(), name='order'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

We added a url pattern for our order view that we just made and used .as_view() like we will need to for every class based view.

That’s all we should need for this basic version to work. Obviously, there is still a lot that will be needed to be added for this to have the features that are expected from an ordering system, we will add the rest of those features next. This is a good point to stop for now, we will come back and finish it next time.

--

--