A Story about Django, React and a cafe restaurant webapp. Part 1 BackEnd

Christos Stathakis
11 min readNov 25, 2018

In this tutorial we will create a API with Django and Rest Framework, which will manage, save and store all the data React will send us

A live api can be found here → https://react-pos.herokuapp.com/api/

On this two part tutorial, our plan is to create two different technologies on each part.

  • Part 1. We will create a new django API server. We will manage with this the database, the requests anything need the project that includes data.
  • Part 2. We will create a React App. With this app will fetch the endpoints we created before to get or post the data we need

Each project we will create, can work separate and we on React part we will not use authentication or Redux to keep the simplicity of the project(and if we do that i will needed 4 or 5 parts). The libraries, frameworks and programming languages will we use is next.

Lets Create the BackEnd Server

First let’s explain how we handle the creation of the backend server

  • Step 1. Create a fresh django app and install the dependencies.
  • Step 2. Will Create The Product App
  • Step 3 . Will Create the Order App
  • Step 4. Create the Api for the Order App
  • Step 5. Create the Admin files

Step 1. Setup the project.

First we will install the dependencies of django so..

$ pip install django==2.0
$ pip install django-rest-framework
$ pip install corsheaders
$ pip install django_filters

After that let’s create our project.

$ django-admin startproject pos_react
then cd pos_react
$ python manage.py startapp frontend
$ python manage.py startapp products
$ python manage.py startapp orders

Thats its! Our main structure is ready. On this guide we will use the orders and product app. The frontend app we will use it later to store react there.

Now let’s do the changes needed!

On pos_react folder there is a settings.py file, it’s time to modify it. The full version is here.

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

'rest_framework',
'corsheaders',
'django_filters',

'frontend',
'products',
'orders',
]

We added here the third party libraries we installed before, rest_framework will create our api, corsheaders will do our job easier on cross domains requests and django_filters will its a plus on filtering our api. And we added the django apps we created

And on same file we add on bottom this

CURRENCY = '€'

CORS_ORIGIN_ALLOW_ALL=True

REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}

With Currency we specify… the currency!. With CORS_ORIGIN_ALLOW_ALL we allow anyone to do requests or POST in our api and the last is needed for filtering the API

Before moving on next part a fast look on urls.py.

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

from orders.api.views import (TableListAPIView, TableDetailAPIView,
OrderListAPIView, OrderDetailAPIView,
OrderItemListAPIView, OrderItemDetailAPIView,
ApiHomepage
)

from products.api.views import ProductListAPIView, ProductDetailAPIView, CategoryListApiView

urlpatterns = [
path('admin/', admin.site.urls),

path('api/', ApiHomepage),
path('api/table-list/', TableListAPIView.as_view(), name='table_list'),
path('api/table-detail/<int:pk>/', TableDetailAPIView.as_view(), name='table_detail'),
path('api/order-list/', OrderListAPIView.as_view(), name='order_list'),
path('api/order-detail/<int:pk>/', OrderDetailAPIView.as_view(), name='order_detail'),
path('api/order-item-list', OrderItemListAPIView.as_view(), name='order_item_list'),
path('api/order-item-detail/<int:pk>/', OrderItemDetailAPIView.as_view(), name='order_item_detail'),

path('api/product-list/', ProductListAPIView.as_view(), name='product_list'),
path('api/product-detail/<int:pk>/', ProductDetailAPIView.as_view(), name='product_detail'),
path('api/category-list/', CategoryListApiView.as_view(), name='category_list'),



]

Not much to explain here, when we will create the views, all with make sense!

Step one is done.

Step 2. Create The Product App.

Here we will create our database models that represents anything we need about products.

Our first step is to create the product model, so in the folder products we created before we will find the models.py and we add this .

from django.db import models
from django.core.exceptions import ValidationError
from django.conf import settings

CURRENCY = settings.CURRENCY

def validate_positive_number(value):
if value < 0 :
raise ValidationError('This number is lower than 0!')
return value


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

def __str__(self):
return self.title


class Product(models.Model):
active = models.BooleanField(default=True)
title = models.CharField(unique=True, max_length=150)
category = models.ForeignKey(Category, null=True, on_delete=models.SET_NULL)
value = models.DecimalField(decimal_places=2, max_digits=10, default=0, validators=[validate_positive_number,])

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

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

def tag_category(self):
return f'{self.category.title}'

Lets explain now. The validate_positive_number ensures that our products will have positive price. The reason we created the Category model is for later use of filtering the products. And when you see a function on model starts with tag_ , will always return a simply presentation string which is very helpfull later when we need to create json data etc.

Now is time for create the API.

On Products folder add a new folder called api, and inside this folder create two files, the serializers.py and views.py. On serializers.py we will add the serializers as you can figure and is a shortcut provided from rest framework that give us the ability to represent easy the data from models. Lets see them .

from rest_framework import serializers
from products.models import Product, Category


class ProductListSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='product_detail', read_only=True)

class Meta:
model = Product
fields = ['id', 'title', 'value', 'category', 'active', 'tag_category', 'tag_value', 'url']


class ProductDetailSerializer(serializers.ModelSerializer):

class Meta:
model = Product
fields = ['id', 'title', 'value', 'category', 'tag_category', 'tag_value']


class CategoryListSerializer(serializers.ModelSerializer):

class Meta:
model = Category
fields = ['id', 'title']

Let’s analyze the first one. The logic is the same , like django forms. First step our class inherited from ModelSerializer, like we do on django ModelForm, and then we use the meta class to specify the model and the fields. Here is the tag_ functions handly, we can represent any data we want with just calling them.

class ProductListSerializer(serializers.ModelSerializer):    class Meta:
model = Product
fields = ['id', 'title', 'value', 'category', 'tag_category', 'tag_value']

Or we can create the data here on serializer like next line

url = serializers.HyperlinkedIdentityField(view_name='product_detail', read_only=True)

What is this? If you go back on urls.py you will find a path connected on this view_name waiting. So here we add a link on our serializer!

Next step view.py on api folder.

from django_filters.rest_framework import  DjangoFilterBackend
from rest_framework import generics, permissions , filters
from products.models import Product, Category
from .serializers import ProductListSerializer, ProductDetailSerializer, CategoryListSerializer


class ProductListAPIView(generics.ListCreateAPIView):
serializer_class = ProductListSerializer
permission_classes = (permissions.AllowAny, )
queryset = Product.objects.filter(active=True)
filter_backends = (DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('category',)
search_fields = ['title', 'category__title']


class ProductDetailAPIView(generics.RetrieveAPIView):
serializer_class = ProductDetailSerializer
permission_classes = (permissions.AllowAny, )
queryset = Product.objects.filter(active=True)


class CategoryListApiView(generics.ListAPIView):
serializer_class = CategoryListSerializer
permission_classes = (permissions.AllowAny, )
queryset = Category.objects.filter(active=True)

After the initial imports lets explain the next lines

class ProductListAPIView(generics.ListCreateAPIView):    serializer_class = ProductListSerializer
permission_classes = (permissions.AllowAny, )
queryset = Product.objects.filter(active=True)
filter_backends = (DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('category',)
search_fields = ['title', 'category__title']

This code represent our API page responsible for the products.In fact is the page that collects all products together. We specify the initial products on queryset. The serializer_class which we add the serializer we created before represent the fields we want to show on each product. The permission class is controlling which user have access to our data, here anyone can fetch this page. The last three lines is a shortcut to filter the data. Will be explain on photo below

We do the same for the other views, but on ProductDetailAPIView because is product page to change it to RetrieveAPIView! The urls we created before will handle the views.

A note before closing this step. We used the class RetrieveAPIView, ListAPIView from generics because is only for the client to receive data. For putting data we will use the admin.

Step 3. Create the Orders app.

Lets move on orders folder.

Before we continue on models.py file again lets explain the logic here. On first model the Table we will store all the Tables of the restaurant. We will create a function that checks if there is a active order and if there is, will set the field is_free to false when a save is called. So in that way we will ensure that when a order is active then the table wouldn’t be free. On Order model we will store the total value of the order items. To avoid react to the calculations we will add some functionality on Order and OrderItem save method to make the hard job for us. Lets see it on action!

On models.py again let’s add the following code .

from django.db import models
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.db.models import Sum
from products.models import Product
from django.conf import settings

CURRENCY = settings.CURRENCY


class Table(models.Model):
active = models.BooleanField(default=True)
title = models.CharField(unique=True, max_length=150)
value = models.DecimalField(default=0, max_digits=10, decimal_places=2)
is_free = models.BooleanField(default=True)
active_order_id = models.PositiveIntegerField(blank=True, null=True)

def save(self, *args, **kwargs):
self.active_order_id = self.table_orders.filter(active=True).last().id\
if self.table_orders.filter(active=True).exists() else None
self.is_free = False if self.active_order_id else True
self.value = 0 if self.is_free else self.value
super(Table, self).save(*args, **kwargs)

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

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

def tag_active_order_id(self):
last_table = self.table_orders.filter(active=True).last()
return last_table.id if last_table else 'No Table'


class Order(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
title = models.CharField(blank=True, null=True, max_length=50)
value = models.DecimalField(default=0, max_digits=10, decimal_places=2)
table = models.ForeignKey(Table, null=True, on_delete=models.SET_NULL, related_name='table_orders')

class Meta:
ordering = ['-timestamp', ]

def __str__(self):
return f'Table {self.table.title}' if self.table else 'Table'

def save(self, *args, **kwargs):
self.value = self.order_items.all().aggregate(Sum('total_value'))['total_value__sum'] if \
self.order_items.all() else 0
self.value = self.value if self.value else 0
super(Order, self).save(*args, **kwargs)
self.table.value = self.value if self.value and self.active else 0
self.table.save()

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

def tag_table(self):
return f'{self.table.title}' if self.table else 'No table'

def tag_active(self):
return 'Closed' if not self.active else 'Active'

def tag_timestamp(self):
return self.timestamp.date()


@receiver(post_save, sender=Order)
def update_table_status(sender, instance, created, **kwargs):
if created:
instance.table.is_free = False
instance.table.save()


class OrderItem(models.Model):
product_related = models.ForeignKey(Product, on_delete=models.CASCADE)
order_related = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='order_items')
value = models.DecimalField(decimal_places=2, max_digits=10, default=0)
qty = models.PositiveIntegerField(default=1)
total_value = models.DecimalField(decimal_places=2, max_digits=10, default=0)

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

def save(self, *args, **kwargs):
self.value = self.product_related.value
self.total_value = self.value * self.qty
super(OrderItem, self).save(*args, **kwargs)
self.order_related.save()

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

def tag_total_value(self):
return f'{self.total_value} {CURRENCY}'

def tag_order_related(self):
return f'{self.order_related.__str__}'

def tag_product_related(self):
return f'{self.product_related.title}'


@receiver(post_delete, sender=OrderItem)
def update_order(sender, instance, *args, **kwargs):
instance.order_related.save()

Lets split up and explain it. First the Table model

class Table(models.Model):
active = models.BooleanField(default=True)
title = models.CharField(unique=True, max_length=150)
value = models.DecimalField(default=0, max_digits=10, decimal_places=2)
is_free = models.BooleanField(default=True)
active_order_id = models.PositiveIntegerField(blank=True, null=True)

First lets explain the Table model. The active will controls if the table will be available on frontend, the title is a fast description of the table, and now… The value, is_free and active_order_id calculate automatically on save method. Lets see it.

def save(self, *args, **kwargs):
self.active_order_id = self.table_orders.filter(active=True).last().id\
if self.table_orders.filter(active=True).exists() else None
self.is_free = False if self.active_order_id else True
self.value = 0 if self.is_free else self.value
super(Table, self).save(*args, **kwargs)

So first we check if there is a active order, if there is we return the id for easy redirect later on frontend and depends on this we update the current value and free status.

Now the next model Order.

class Order(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
title = models.CharField(blank=True, null=True, max_length=50)
value = models.DecimalField(default=0, max_digits=10, decimal_places=2)
table = models.ForeignKey(Table, null=True, on_delete=models.SET_NULL, related_name='table_orders')

The timestamp will be created automatically when a new Order created. The field active inform us that there are still customers on the Table, the title gets a default value from save method if you left it blank, the table inform us where the order is and the value is the calculated total cost from all items have been ordered. On class Meta we say to model to order it from newer to older instance. Works well on Report Page when we will create it.

class Meta:
ordering = ['-timestamp', ]

Next the save method. We calculate the value with the aggregate method direct from database. And then we call the save method on Table model too to update the changes.

def save(self, *args, **kwargs):
self.value = self.order_items.all().aggregate(Sum('total_value'))['total_value__sum'] if \
self.order_items.all() else 0
self.value = self.value if self.value else 0
super(Order, self).save(*args, **kwargs)
self.table.value = self.value if self.value and self.active else 0
self.table.save()

The tag_… is like the product model. Ready strings from presentation for the API.

And final the OrderItem Model.

product_related = models.ForeignKey(Product, on_delete=models.CASCADE)
order_related = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='order_items')
value = models.DecimalField(decimal_places=2, max_digits=10, default=0)
qty = models.PositiveIntegerField(default=1)
total_value = models.DecimalField(decimal_places=2, max_digits=10, default=0)

Here we save which product the customer got, for with order the value and the qty, total_value calculate fast on save as you can see.

def save(self, *args, **kwargs):
self.value = self.product_related.value
self.total_value = self.value * self.qty
super(OrderItem, self).save(*args, **kwargs)
self.order_related.save()

And after we calculate the order item we save it and call a save on Order to start the updates like domino!

Step 4 . Creating the API for Order Model

Same methodology, create a new file called api and inside the two files serializers and views.

On serializers as you can see here there is nothing so just copy and paste the code or better try to write alone, just use the same logic like on product serializers. But on views.py we have many new things. Lets see them!

from rest_framework import generics, permissions, filters
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.decorators import api_view
from django_filters.rest_framework import DjangoFilterBackend
from .serializers import (TableListSerializer, TableDetailSerializer,
OrderListSerializer, OrderDetailSerializer,
OrderItemListSerializer, OrderItemDetailSerializer
)

from ..models import Table, Order, OrderItem


@api_view(['GET'])
def ApiHomepage(request, format=None):
return Response({
'orders': reverse('order_list', request=request, format=format),
'order-items': reverse('order_item_list', request=request, format=format),
'tables': reverse('table_list', request=request, format=format),
'products': reverse('product_list', request=request, format=format),
'categories': reverse('category_list', request=request, format=format),

})



class TableListAPIView(generics.ListAPIView):
serializer_class = TableListSerializer
permission_classes = [permissions.AllowAny, ]
queryset = Table.objects.filter(active=True)


class TableDetailAPIView(generics.RetrieveUpdateAPIView):
serializer_class = TableDetailSerializer
permission_classes = (permissions.AllowAny,)
queryset = Table.objects.filter(active=True)


class OrderListAPIView(generics.ListCreateAPIView):
serializer_class = OrderListSerializer
permission_classes = (permissions.AllowAny, )
queryset = Order.objects.all()
filter_backends = (DjangoFilterBackend, )
filter_fields = ('table', )


class OrderDetailAPIView(generics.RetrieveUpdateAPIView):
serializer_class = OrderDetailSerializer
permission_classes = [permissions.AllowAny]
queryset = Order.objects.all()
lookup_field = 'id'
lookup_url_kwarg = 'pk'


class OrderItemListAPIView(generics.ListCreateAPIView):
serializer_class = OrderItemListSerializer
permission_classes = (permissions.AllowAny, )
queryset = OrderItem.objects.all()
filter_backends = [DjangoFilterBackend, filters.SearchFilter, ]
filter_fields = ['product_related', 'order_related', ]
search_fields = ['product_related__title', 'order_related__title']


class OrderItemDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = OrderItemDetailSerializer
permission_classes = (permissions.AllowAny, )
queryset = OrderItem.objects.all()

The first view you will see is the homepage of our api. We use the @api_view to inform rest_framework that is a API view and then we return the urls of our endpoints so the api will be browseble.

@api_view(['GET'])
def ApiHomepage(request, format=None):
return Response({
'orders': reverse('order_list', request=request, format=format),
'order-items': reverse('order_item_list', request=request, format=format),
'tables': reverse('table_list', request=request, format=format),
'products': reverse('product_list', request=request, format=format),
'categories': reverse('category_list', request=request, format=format),

})

The second view the ReportPage, is similar with the @api_view but here we write our one ‘view’. Don’t know if there a better way to do it, but this was a brutal and fast way. We just take all the orders we filtering with the table on request.GET and we return the stats so no need to do it on frontend. I like to keep the React we will use only for presentation, if the data can manipulate on backend i will do it here, i prefer python than javascript, no hard feelings.

The rest of the view you have already see it. So the backend server is ready!

Do the migrations, createsuperuser and runserver. Go to http://127.0.0.1:8000/api/ and explore it.

Step 5. Handing the Admin

Last part of this tutorial is to configure the admin page, so we can handle the

data. On every app we created we will go to admin.py file and fix it. So lets see the Product app first. Add this code on admin.py .

from django.contrib import admin
from .models import Product, Category


@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['title', 'category', 'tag_value', 'active']
list_filter = ['active', 'category', ]
readonly_fields = ['tag_value', ]
search_fields = ['title', 'category__title']


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_filter = ['active',]
list_display = ['id', 'title', 'active']

Here we import the models from models.py and then we register them to admin. After that we use next lines to configure the page like the image below

We do the same on the Order model admin.py file. This is the link.

from django.contrib import admin
from .models import Table, Order, OrderItem


@admin.register(Table)
class TableAdmin(admin.ModelAdmin):
list_display = ['title', 'is_free', 'active', 'tag_value']
list_filter = ['is_free', 'active']


@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ['table', 'tag_value', 'active']
list_filter = ['active', 'table']
search_fields = ['table__title', 'title']


@admin.register(OrderItem)
class OrderItemAdmin(admin.ModelAdmin):
list_display = ['product_related', 'order_related', 'qty', 'tag_value', 'tag_total_value']
list_filter = ['order_related__table']
search_fields = ['product_related__title', 'order_related__title']

And we are ready. Hit makemigrations, migrate, createsuperuser, startserver routine on manage.py and your backend Server is Ready!!

Now lets go On Part 2. Create the frontend with React.

--

--