Use Django To Create A Backend Application
Django is a popular Python framework developed to power your backend application. This tutorial will show you how to use Django to create a backend server to feed our Restaurant application.
Requirements
- Django 4.0 supports Python 3.8 or later. For older Python versions you should head to this link below for more information.
- You should have your favorite IDE already installed on your computer :)
Set-Up
The first thing you need to do is to create a virtual environment.
What is a virtual environment? This is a soft intro for you.
Imagine you have on your computer 2 Python projects A and B. A and B both use package P to realize their functionalities. However, A uses a different version of P other than B does. If you install a fixed version of P on your computer, you won’t be able to run both of A and B simultaneously. That’s why you need to start a virtual environment. In this environment, you will have all the necessary packages for the project. Virtual environments are independent of each other, so you can start a virtual environment for A and another for B and have both A and B run at the same time.
First off, create a directory where your source code should lie.
To create a virtual environment, simply open your command prompt and type:
python -m venv venv
Then type the following to start the virtual environment:
venv\Scripts\activate
If you do it correctly, a string (venv) will appear on the left of your path to the command prompt.
Next, you need to install Django. Type in the following command:
python -m pip install Django
In case you want to share your code with others, it’s best to define what packages are needed for your project. You can do it by creating a requirements.txt file.
pip freeze > requirements.txt
Other people can then start their own virtual environment and install dependencies via:
python -m pip install -r requirements.txt
Great! Now you are done with the setup and can move on to using Django to write our own Backend application.
Create Django Project
First thing first, create a Django project using this command:
django-admin startproject app
You will see a folder with the name app. On your command prompt type the following to change your current directory to the app:
cd app
you need to execute our first migration. Even though no data model has been implemented yet, this step is necessary for the first creation of a superuser. After this, you won’t have to run this code again if you want to create another superuser.
python manage.py migrate
Next thing is to create our first user account
python manage.py createsuperuser
The command prompt will guide you through the registration steps.
Finally, you can start the development server.
python manage.py runserver
Go to http://127.0.0.1:8000/admin/ and log in with your superuser account. If you get to the admin page, then Congratulations! You are now ready to take the next steps to create API endpoints for your backend server.
Create apps
Wait. Didn’t you just create a Django application up there?
Well no. It was a Django project. A project can contain multiple apps. Think of a project as a house. Now what we need are rooms for different purposes. It’s now a good time to explain what our application is about.
The application is called RestaurantApp. It should provide API endpoints, which in turn provide CRUD operations for restaurants, their dishes, and tags from a database. Moreover, different types of users are allowed to different sets of endpoints. Here is a class diagram to illustrate the idea more formally.
You need an app for restaurant-related information and an app for user-related information. Later, you can expand the application to different ideas like payment, security, advertisement, etc.
Make sure your command prompt is still in the outer app directory. If the development server is still running, use Control + C to terminate it. Type in the following to create an app:
python manage.py startapp restaurants
Your directory structure should look like this:
app
— app
— restaurants
Finally, you need to register restaurants to the Django project. Simply go to app/app/settings.py and add
'restaurants.apps.RestaurantsConfig'
to the INSTALLED_APPS array. The array should now look like this
INSTALLED_APPS = [
'restaurants.apps.RestaurantsConfig',
'accounts.apps.AccountsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
3 steps to create and register models
Step #1, go to the file models.py in folder restaurants and add:
from django.db import modelsclass Restaurant(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=100)
latitude = models.FloatField(default=0)
longitude = models.FloatField(default=0) def __str__(self):
return self.name
Let’s me explain what the code does. It created a data model to represent our model of a restaurant. We use models.CharField when our fields are of String type and models.FloatField when our fields are of Float type. Of course, there are other different field types that models can offer to suit your needs. The function __str__ allows restaurant objects to be represented by their name in the admin interface. We’ll get to that right away.
Here’s a rule of thumb: Whenever you make a change in models.py (create, update, or delete models), you need to make migration files and execute them. This allows updating the tables in your database.
python manage.py makemigrationspython manage.py migrate
Speaking of databases, at this moment, you are still using SQLite database. This comes by default with Django. However, you can still use other databases if you want. Simply head to this link to see the necessary configuration.
At this step, if you start your development server, you will see that the admin page still does not display information about restaurants. For that, you need to go to file admin.py in directory restaurants and manually register the model.
from django.contrib import admin
from .models import Restaurantclass RestaurantAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['name']}),
(None, {'fields': ['address']}),
(None, {'fields': ['latitude']}),
(None, {'fields': ['longitude']})
]
list_display = ('name', 'address', 'latitude', 'longitude')admin.site.register(Restaurant, RestaurantAdmin)
Step #2, refresh your admin page, and you will see a new entry for your restaurants on the column on the left-hand side. Click on that entry to enter the admin site for restaurants.
The purpose of this step is to identify which fields should be tracked and displayed.
Step #3, Input the information of your desired restaurant and click Save.
Create and register models for dishes and tags
However, you want more than just one model. You also want models for dishes and tags.
from django.db import modelsclass Restaurant(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=100)
latitude = models.FloatField(default=0)
longitude = models.FloatField(default=0) def __str__(self):
return self.nameclass Tag(models.Model):
name = models.CharField(max_length=30, unique=True)
restaurants = models.ManyToManyField(Restaurant)
def __str__(self):
return self.nameclass Dish(models.Model):
name = models.CharField(max_length=30)
description = models.CharField(max_length=100)
price = models.FloatField(default=0)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE) def __str__(self):
return self.name
For tags, note that one restaurant can have many tags (Italian, sandwiches, …) and one tag can be used for many restaurants. Thus, you can use models.ManyToManyField to represent this relationship.
Important: if you want to create a many-to-many relationship, creating as many fields in one model in the relationship is enough. That means you must not add this line in the class Restaurant.
tags = models.ManyToManyField(Tag)
For dishes, one restaurant has multiple dishes. Therefore, you can use models.ForeignKey for this purpose. The argument on_delete=models.CASCADE tells Django that if a restaurant gets deleted, its dishes should be deleted as well.
Just like before, you need to register an admin site for each of the new models
from django.contrib import admin
from .models import Restaurant, Tag, Dishclass DishInline(admin.TabularInline):
model = Dish
extra = 3class RestaurantTagInline(admin.TabularInline):
model = Tag.restaurants.through
extra = 3class RestaurantAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['name']}),
(None, {'fields': ['address']}),
(None, {'fields': ['latitude']}),
(None, {'fields': ['longitude']})
] inlines = [DishInline, RestaurantTagInline]
list_display = ('name', 'address', 'latitude', 'longitude')class TagAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['name']}),
]
inlines = [RestaurantTagInline]class DishAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['name']}),
(None, {'fields': ['description']}),
(None, {'fields': ['price']}),
(None, {'fields': ['restaurant']}),
] list_display = ('name', 'description', 'price', 'restaurant')admin.site.register(Restaurant, RestaurantAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Dish, DishAdmin)
You can use Inline class to display models in one-to-many or many-to-many relationships.
- First, you need to define the Inline classes.
- Then, you need to specify which models the classes should be responsible for. The extra property is for the number of objects that will be displayed in the admin site that uses the Inline class.
- Finally, classes on the other end of the relationships can use property inlines to specify which Inline classes to use.
CRUD API endpoints
You need to create a file named urls.py in directory restaurants. In that file, copy the following code snippet:
from django.urls import path
from . import viewsurlpatterns = [
path('restaurants/new/', views.create_a_new_restaurant, name='create_a_new_restaurant'), path('restaurants/', views.get_all_restaurants, name='get_all_restaurants'), path('restaurants/<int:restaurant_id>/', views.get_restaurant_by_id, name='get_restaurant_by_id'), path('restaurants/patch/<int:restaurant_id>/', views.update_restaurant_by_id, name='update_restaurant_by_id'), path('restaurants/delete/<int:restaurant_id>/', views.delete_restaurant_by_id, name='delete_restaurant_by_id'),
]
What it does is map URL patterns to handling methods defined in views.py in directory restaurants. The name argument is for reversing the URL, which we’ll go no further in this tutorial. We use <type:parameter_name> to define the parameter that may be appended to the URLs. Next, you need to register the URL patterns of the restaurant app in our Django project. Simply go to urls.py in the directory app in the project directory app. You can see that a URL for the admin site has already been registered. Now you need to do the same thing for the restaurants app.
from django.contrib import admin
from django.urls import include, pathurlpatterns = [
path('api/', include('restaurants.urls')),
path('admin/', admin.site.urls),
]
If you want to make an HTTP GET request to get a restaurant with id 1, you can use the URL:
http://127.0.0.1:8000/api/restaurants/1/
You will get an error because this URL is not declared yet. We will go through that in the steps below.
You are ready to implement the handling methods in restaurants/views.py.
Import modules
from django.http import HttpResponse
from django.core import serializers
from django.http import Http404, HttpResponseNotAllowed
from .models import Restaurant, Dish, Tag
import json
Create API endpoint
def create_a_new_restaurant(request):
if request.method == 'POST':
body = json.loads(request.body.decode('utf-8'))
new_name = body.get("name")
new_address = body.get("address")
new_latitude = body.get("latitude")
new_longitude = body.get("longitude")
try:
tag = Tag.objects.get(pk=body.get("tag_id"))
except Tag.DoesNotExist:
raise Http404("Tag does not exist")
new_restaurant = Restaurant(name=new_name, address=new_address, latitude=new_latitude, longitude=new_longitude)
new_restaurant.save()
tag.restaurants.add(new_restaurant)
tag.save()
return HttpResponse(status=200)
else:
raise HttpResponseNotAllowed("Method is not supported")
You need to extract the information from the request, instantiate an instance of Restaurant, and save it. You also need to get an already existing tag (Remember to create one at hand and use its id for your new restaurant) and add that restaurant to the tag’s restaurants.
Read API endpoint
def get_all_restaurants(request):
query_set = Restaurant.objects.all()
data = serializers.serialize("json", query_set)
return HttpResponse(data)def get_restaurant_by_id(request, restaurant_id):
try:
query_set = Restaurant.objects.filter(pk=restaurant_id)
except Restaurant.DoesNotExist:
raise Http404("Restaurant does not exist")
data = serializers.serialize("json", query_set)
return HttpResponse(data)
For the get-all method, you can query all the objects from the Restaurant model. Then you serialize the objects and return the data. For the get by id method, you can query for the restaurant based on the id given in the URL, serialize it and return the data.
Update API endpoint
def update_restaurant_by_id(request, restaurant_id):
if request.method == 'PUT':
body = json.loads(request.body.decode('utf-8'))
new_name = body.get("name")
new_address = body.get("address")
new_latitude = body.get("latitude")
new_longitude = body.get("longitude") try:
tag = Tag.objects.get(pk=body.get("tag_id"))
except Tag.DoesNotExist:
raise Http404("Tag does not exist") try:
restaurant = Restaurant.objects.get(pk=restaurant_id)
except Restaurant.DoesNotExist:
raise Http404("Restaurant does not exist") restaurant.name = new_name
restaurant.address = new_address
restaurant.latitude = new_latitude
restaurant.longitude = new_longitude
restaurant.save() tag.restaurants.add(restaurant)
tag.save() return HttpResponse(status=200) else:
raise HttpResponseNotAllowed("Method is not supported")
Similar to the Read API endpoint, you look for the tag and the restaurant by their ids. Then you change the properties of the found restaurant, found tag, and save them.
Delete API endpoint
def delete_restaurant_by_id(request, restaurant_id):
if request.method == 'DELETE':
try:
restaurant = Restaurant.objects.get(pk=restaurant_id)
except Restaurant.DoesNotExist:
raise Http404("Restaurant does not exist")
restaurant.delete()
return HttpResponse(status=200)
else:
raise HttpResponseNotAllowed("Method is not supported")
You get the restaurant that we want to delete by the id provided in the URL, then simply call the delete() method on that instance.
URLs and handling methods for Dish and Tag follow the same pattern. You can apply what you have learned to implement them.
Conclusion
So far, what you have done is just a fraction of what Django can offer. There are so many aspects that we can consider if you want to expand our application. For example, payment, security, authorization, etc.
If you want to learn more, this link makes great material. I wish you all the best on your journey of backend development!