Django is an open-source Python web framework. It follows the model-template-views architectural pattern. If you are familiar with some other framework like Laravel you would find this tutorial simple enough to follow. It is important to note that Django views are similar in function to Laravel controller and Django templates will be similar to Laravel blade files. It goes without saying that these are just rough comparisons for the sake of understanding only.
The tutorial is divided broadly into three sections. If you want you can simply click and skip to the required section.
- Section A: Setting up a server for Django development
- Section B: Build Django Model, Register with Admin panel and Perform CRUD Operations
- Section C: Integrate Rest API with OAuth 2.0
In this tutorial, we will learn how to set up and host a Django project and then build Restful API using Django and securing them with OAuth 2.0. We are using it because OAuth 2.0 is the modern standard for securing access to APIs.
Section A: Setting up a server for Django development
We will set up Django in Ubuntu 20.04. This section of the tutorial is kept minimal. If you are interested in an in-depth tutorial you can click here.
We will use Django with Python 3 so let us install the following packages. Open up your terminal and run the following commands. This will install all the necessary files for your project to run.
$ sudo apt-get update
$ sudo apt-get install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx
Next, Start PostgreSQL in your terminal and create a new Database called “djangorest” for your Django project in PostgreSQL (Note: All password are kept simple for this tutorial. Make sure to create a strong password in a production environment.)
$ sudo -u postgres psql
postgres=# CREATE DATABASE djangorest;
postgres=# CREATE USER djangorestuser WITH PASSWORD 'password';
postgres=# ALTER ROLE djangorestuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE djangorestuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE djangorestuser SET timezone TO 'UTC';
postgres=# GRANT ALL PRIVILEGES ON DATABASE djangorest TO djangorestuser;
postgres=# \q
We will now install Python virtual environment called virtualenv. Virtual environments are a great way to isolate and maintain your own projects without interfering with any other projects in the server. In your terminal run the following commands.
$ sudo -H pip3 install --upgrade pip
$ sudo -H pip3 install virtualenv
Let us make a new project directory called “djangorest” and enter it.
$ mkdir ~/djangorest
$ cd ~/djangorest
Inside the project directory create a Python virtual environment called “djangorestenv”
$ virtualenv djangorestenv
Now, activate the virtual environment.
$ source djangorestenv/bin/activate
Your prompt will change to indicate that you are now operating within a Python virtual environment. It will look similar to this: (djangorestenv)user@host:~/djangorest$
With your virtual environment active, install Django, Gunicorn, and the psycopg2(PostgreSQL adaptor) with the local instance of pip. Inside any virtual environment irrespective of the Python version, we always use pip.
(djangorestenv) $ pip install django gunicorn psycopg2
Now, let us create a new Django project called “djangorest” and a Django app called “restapi”.
(djangorestenv) $ django-admin.py startproject djangorest ~/djangorest
(djangorestenv) $ cd ~/djangorest
(djangorestenv) $ django-admin.py startapp restapi
We need to adjust our project settings now.
(djangorestenv) $ nano ~/djangorest/djangorest/settings.py
In ALLOWED_HOSTS add your server IP address or the domain of the application. In my case my IP was “192.168.198.140”
# ~/djangorest/djangorest/settings.py
ALLOWED_HOSTS = ['your_server_domain_or_IP', 'second_domain_or_IP', . . .]
Add import os on top of the file. We will need it for using our static files.
# ~/djangorest/djangorest/settings.py
import os
from pathlib import Path
In DATABASES keep the following config.
# ~/djangorest/djangorest/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'djangorest',
'USER': 'djangorestuser',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '',
}
}
Add a STATIC_ROOT for static files. We added import os before for this step.
# ~/djangorest/djangorest/settings.py
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
If you were editing the setting with the nano editor. Save the file by pressing ctrl + o and exit the file by pressing ctrl + x.
Let us now migrate the tables to our database.
(djangorestenv) $ ~/djangorest/manage.py makemigrations
(djangorestenv) $ ~/djangorest/manage.py migrate
Next, create an administrator (Super User) for your Django project. This user can log into the admin panel of your project. You will have to select a username, provide an email address, and choose and confirm a password.
(djangorestenv) $ ~/djangorest/manage.py createsuperuser
We can collect all of the static content like the css and js files into the directory location we configured in settings.py by typing:
(djangorestenv) $ ~/djangorest/manage.py collectstatic
Finally, exit the virtual environment by typing:
(djangorestenv) $ deactivate
We will now use Gunicorn as our web server gateway. The Gunicorn “Green Unicorn” is a Python Web Server Gateway Interface HTTP server. Create a Gunicorn systemd service file (You will need sudo privilege for this operation) with the command mentioned below. Our application is named “djangorest-gunicorn”.
$ sudo nano /etc/systemd/system/djangorest-gunicorn.service
Copy & Paste the content of this file from below. Feel free to make changes according to your system.
[Unit]
Description=djangorest-gunicorn daemon
After=network.target
[Service]
User=saif
Group=www-data
WorkingDirectory=/home/saif/djangorest
ExecStart=/home/saif/djangorest/djangorestenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/saif/djangorest/djangorest.sock djangorest.wsgi:application
[Install]
WantedBy=multi-user.target
Start the “djangorest-gunicorn” service and enable it for auto start on boot ups.
$ sudo systemctl start djangorest-gunicorn
$ sudo systemctl enable djangorest-gunicorn
Next, check the status of your “djangorest-gunicorn” service with the command:
$ sudo systemctl status djangorest-gunicorn
If you face any issue with your “djangorest-gunicorn” service make sure check the process logs by typing:
$ sudo journalctl -u djangorest-gunicorn
Important: Whenever you make changes in your project make sure to restart djangorest-gunicorn everytime
$ sudo systemctl restart djangorest-gunicorn
Configure Nginx to proxy_pass to “djangorest-gunicorn”. We are basically reverse proxying all request to the server from Nginx to “djangorest-gunicorn“
$ sudo nano /etc/nginx/sites-available/djangorest
server {
listen 80;
server_name server_domain_or_IP;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/saif/djangorest;
}
location / {
include proxy_params;
proxy_pass http://unix:/home/saif/djangorest/djangorest.sock;
}
}
Save and close the file when you are finished. Now, we can enable the file by linking it to the sites-enabled
directory:
$ sudo ln -s /etc/nginx/sites-available/djangorest /etc/nginx/sites-enabled
Test your Nginx for any error:
$ sudo nginx -t
Make sure to add a new rule in ufw to allow Nginx by typing:
$ sudo ufw allow 'Nginx Full'
Finally, If everything is fine. Simply restart your Nginx
$ sudo systemctl restart nginx
That’s it. Your server is now successfully configured to run Django projects. In my Nginx setting, I am listening to port 8005 and my Ip is 192.168.198.140. Therefore to view my Django project in the browser I need to type http://192.168.198.140:8005.
Section B: Build Django Model, Register with Admin panel and Perform CRUD Operations
Now that our initial setup is done and we have also created an app called “restapi“. Let us start building our Rest API.
Inside your project folder open “djangorest” and modify admin/ to accounts/. Your ~/djangorest/djangorest/urls.py should look like this:
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('accounts/', admin.site.urls),
]
Register your “restapi” app in ~/djangorest/djangorest/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'restapi'
]
Create a new model called Quote in ~/djangorest/restapi/models.py
# models.py
from django.db import models
# Create your models here.
class Quote(models.Model):
author = models.CharField(max_length=50)
quote = models.CharField(max_length=200)
def __str__(self):
return self.author
Register Quote with the admin site in ~/djangorest/restapi/admin.py
from django.contrib import admin
from .models import Quote
# Register your models here.
@admin.register(Quote)
class QuoteAdmin(admin.ModelAdmin):
list_display = ("author", "quote")
Migrate the Quote table (after activating virtual environment)
(djangorestenv) $ ~/djangorest/manage.py makemigrations
(djangorestenv) $ ~/djangorest/manage.py migrate
Now, insert some quote in the admin panel. Navigate to your domain/accounts (eg: http://192.168.198.140/accounts) and log in with your given credentials. Add some data in the Quote table as shown in Fig 1.
Section C: Integrate Rest API with OAuth 2.0
Now that our model is created and we can perform CRUD(Create, Retrieve, Update & Delete) operations on it from the admin panel. We can now install two new packages for generating Rest API and protecting it with OAuth and then migrate the tables. (Inside Virtual Environment)
(djangorestenv) $ pip install django-oauth-toolkit djangorestframework
(djangorestenv) $ ~/djangorest/manage.py makemigrations
(djangorestenv) $ ~/djangorest/manage.py migrate
Update ~/djangorest/djangorest/settings.py to modify INSTALLED_APPS. Add the two new packages.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'restapi',
'oauth2_provider',
'rest_framework',
]
Also add the following lines at the bottom of ~/djangorest/djangorest/settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
)
}
Create a new file serializers.py inside “restapi” and open and edit ~/djangorest/restapi/serializers.py
from rest_framework import serializers
from .models import Quote
class QuoteSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Quote
fields = ('author', 'quote')
In ~/djangorest/restapi/views.py add a new class as shown below:
from rest_framework import viewsets,generics, permissions, serializers
from restapi.models import *
from .serializers import *
from django.contrib import admin
admin.autodiscover()
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope
class QuoteViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = Quote.objects.all().order_by('id')
serializer_class = QuoteSerializer
In ~/djangorest/djangorest/urls.py add a new paths and also import include from django.urls. New urls.py should look like this
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('accounts/', admin.site.urls),
path('', include('restapi.urls')),
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
]
Now, create a new file urls.py in “restapi” and open ~/djangorest/restapi/urls.py to edit its content as shown below:
from django.urls import include, path
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'quotes', views.QuoteViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Next, we will check if our API is accessible. (Note: I am hosting my site in 192.168.198.140 and my port is 8005)
Let us now open Postman to view the API by typing the URL http://192.168.198.140:8005/quotes/ we get a 401 Unauthorized response as shown in Fig 2. This is good because it means that our API is protected from unauthorized access.
To get a proper response we need to first generate a token. We can do it easily from the admin panel. First, create an application under the section “Django OAuth Toolkit”. Here is my config of the first application.
Client ID: wqmlu2vtNFYTv9motTkbmfZDaWfRx4tdDwIWQumy
User: 1
Client Type: Confidential
Authorization Grant Type: Resource owner password-based
Client Secret: wXHmsiXff4CXAH8YRiojeJMMWRQe3CwIR1ztcUzLN2UctcUUrIJFDO0dhAGvtaV9JfFDHd2rdf0ltzs9yd2KFd0VQxrH11VovC5yBWENKD8aZnQjrPus4nJc8UL4ASBA
Name: My App
Let us create an access token now. Fig 3 shows how the access token was configured. The token has both read and write permission. 12345 is a bearer token. In production it’s important to have a refresh token also; a refresh token is used to generate a new token after the expiry of the current token.
Let us now access the API again with this new bearer token. The bearer token is set in Authorization as shown in Fig 4 with 200 OK response code.
Similarly, we can access other the rest of the APIs. The structure is shared below in Python requests format.
############################################
##############GET All Quotes################
import requests
url = "http://192.168.198.140:8005/quotes/"
payload={}
headers = {
'Authorization': 'Bearer 12345'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
############################################
##############GET Single Quote##############
import requests
url = "http://192.168.198.140:8005/quotes/2"
payload={}
headers = {
'Authorization': 'Bearer 12345'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
############################################
##############UPDATE Quote##################
import requests
url = "http://192.168.198.140:8005/quotes/2/"
payload='quote=test1&author=test2'
headers = {
'Authorization': 'Bearer 12345',
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.request("PUT", url, headers=headers, data=payload)
print(response.text)
############################################
##############DELETE Quote##################
import requests
url = "http://192.168.198.140:8005/quotes/2/"
payload={}
headers = {
'Authorization': 'Bearer 12345'
}
response = requests.request("DELETE", url, headers=headers, data=payload)
print(response.text)
############################################
##############CREATE QUOTE##################
import requests
url = "http://192.168.198.140:8005/quotes/"
payload='quote=New%20Quote&author=New%20Author'
headers = {
'Authorization': 'Bearer 12345',
'Content-Type': 'application/x-www-form-urlencoded'
}
############################################
With this our tutorials is complete. Now, you can build Rest API in Django with OAuth. If you want to read more about the OAuth package you can click here. I have also uploaded the source code in Github. Feel free to explore it.
Click anywhere to get to my Github page https://github.com/sa1if3/djangorest
Md. Saifur Rahman is a Full Stack Django and Laravel Developer. Additionally, he loves to spend time learning and researching about the Internet of Things (IoT). He loves to share his work and contribute to helping fellow developers.
everything ok! but man… consider windows users for the first steps. I’ve had a pretty hard time figuring out how to setting up on windows machine. 😡
Hey,
I use Windows too. But, my servers are virtual machines running in VmWare. Hope that helps !
Hey,
I totally understand how you feel. I use windows too but prefer to keep my work servers separate as virtual machines in VmWare. Hope that helps!