Install the following required packages/modules using pip
pip install django djangorestframework
After the packages installing successfully, use the below command to create project
django-admin startproject drf_api_project_folder .
The above code will create a project folder named drf_api_project_folder
. I am going to create two applications 1. mainApp
and 2. helloWorld
by using the below command
python manage.py startapp mainApp
python manage.py startapp helloWorld
Okay, so let's move into the next section, after successfully create apps, need to add into INSTALLED_APPS
on settings.py
file in the project folder.
#settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'helloWorld',
'mainApp'
]
mainApp
will navigate the user about the end-points and other operations
helloWorld
app just an application to demonstrate the basic api operations
On the day 02.04.2024
on hello world app we create a sample model and other sample views to display data on JSON format
- Changing Dash Board View
Changing the admin dashboard view from this
to this
by adding the code block on admin.py
on your app folder, adding a new class in admin.py
file like below
class SampleAdmin(admin.ModelAdmin):
list_display = ("id", "name", "age", "job", "created_at") #these names are the column names of our model
After adding the class you need to register on your admin site like below
admin.site.register(Sample) #previous one only with model
the above code needs to change to like below
admin.site.register(Sample, SampleAdmin)
- Adding Links
Now you can view the data from dashboard and if you click the id
it will navigate to the edit page, but if we need to do the same when we click other field like name
to do that we are going to add an another attribute named list_display_links
inside the class like below
class SampleAdmin(admin.ModelAdmin):
list_display = ("id", "name", "age", "job", "created_at")
list_display_links = ("id", "name")
- Search Links
We can add search field using the same method, add a line of code like below
class SampleAdmin(admin.ModelAdmin):
list_display = ("id", "name", "age", "job", "created_at")
list_display_links = ("id", "name")
search_fields = ("name", "job")
Always remember you need to provide column names on tuple always
- Pagination
We can add pagination feature on our dashboard you can limit the table list per page, by adding the below line of code
class SampleAdmin(admin.ModelAdmin):
list_display = ("id", "name", "age", "job", "created_at")
list_display_links = ("id", "name")
search_fields = ("name", "job")
list_per_page = 10
- Editing
We can implement the editing feature (without navigate to detail view) also, before that i am going to add an another boolean field on our model like below
class Sample(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField(default=0)
job = models.CharField(max_length=100, null=True)
is_active = models.BooleanField(default=True) #<--- Added field
created_at = models.DateTimeField(default=datetime.now())
After the column added we need to run the scripts for makemigrations
and migrate
on our command prompt. After the migration we are going to add the is_active
column for editable columns, like below
class SampleAdmin(admin.ModelAdmin):
list_display = ("id", "name", "age", "job", "is_active", "created_at")
list_display_links = ("id", "name")
search_fields = ("name", "job")
list_per_page = 10
list_editable = ('is_active',)
- Filter
Next we're going to add the filter option by using list_filter
like below
class SampleAdmin(admin.ModelAdmin):
list_display = ("id", "name", "age", "job", "is_active", "created_at")
list_display_links = ("id", "name")
search_fields = ("name", "job")
list_per_page = 10
list_editable = ('is_active',)
list_filter = ("id", "name", "created_at")
After all these changes your admin dashboard will look like this
In the above part we just created a simple API using django's default json concept. But this is not the proper way to create an API we need some architecture or rules to follow for creating an API that's where the djangorestframework
aka drf
is going to use for that we are going to create a new app named employeeDetails
for doing some CRUD operations on employee field using REST API, so first we're going to create an app using below command on terminal
python manage.py startapp employeeDetails
After successfully created i had done the admin panel styling parts like above and created models you can check that out, that's all for today we will start to create REST API from tomorrow:
Refer employeeDetails/README.md
for the code and explanation of the topics that covered on the app.
Topics:
- Serializers
- Function-Based Views - GET, POST, PUT, and DELETE
- Status Codes
For demonstrating ModelSerializer
we're going to create another app called StudentDetails
For the code and explanation goto StudentDetails/README.md
For that we're going to create an app called TodoApp
and this app views will be completely using the class based views
For the code and explanation refer TodoApp/README.md
Topics:
- Class Based Views - GET. PUT, POST, and DELETE
Next we're going to see how to validate the serialized fields for that we're going to create an application named votersDetails
, and we are going to use class based views and model serailizers for the application.
For code and explanation refer votersDetails/README.md
.
Topics:
- Field-level Validation
- Object-level Validation
- Serializer Method Field
For this topic we're going to create an new application called spotifyApp
with 2 tables Tracks
and Albums
here, i will show you the Foregin-key
concept also and other configurations all are same.
For code and explanation refer spotifyApp/README.md
Topics:
- Foreign Key
- Nested Serializer
For this topic we're going to create a new Application named BlogApp
, because this topic covers various concept like serialization on StringRelatedField
, HyperLink
, SlugField
, etc so, we're going to create a new app and going to add lots of columns in our models, like below
import random
import string
from django.db import models
from django.template.defaultfilters import slugify
# Create your models here.
class Category(models.Model):
category_name = models.CharField(max_length=50)
def __str__(self):
return f"Category: {self.category_name}"
class Blog(models.Model):
blog_title = models.CharField(max_length=100)
blog_description = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category")
post_date = models.DateTimeField(auto_now_add=True)
is_public = models.BooleanField(default=True)
slug = models.CharField(max_length=1000, null=True, blank=True)
def __str__(self):
return self.blog_title
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(self.blog_title+" "+self.category.category_name)
self.slug = base_slug+''.join(random.choice(string.ascii_letters+string.digits) for _ in range(5))
return super().save(*args, **kwargs)
Then as usual add views, urls, serializers like we before did.
For code / explanatons refer BlogApp/README.md
.
Topics:
- API Reference
We saw how the Class based views works in our previous parts. From today onwards we're going to look into how the Generic View
works! Comparing to other views Generic view is easy and it's only need a short code we dont need junk line of codes. For this Generic View
initialization we're going to create a new app called booksApp
so we will continue this topic after all the other configuration of app's finish! I will be right back after sometime!! Happy Coding!!
For code and explanation refer booksApp/README.md
Topics:
- Generics
- Mixins
Today we're going to discuss about the ConcreteViewClasses
for this! I am going to create a new application
called MovieReviewApp
, by using the above methods
For code and explanation refer MovieReviewApp/README.md
.
Topics:
- generics views
The next topic is view sets for this topic i am going to create a new application called MusicAPI
so we can use the application for reference.
For code and explanation refer MusicAPI/README.md
Topics:
- Viewsets
For this tutorial i am going to create a new app called RecipeApp
.
For code and explanation refer RecipeApp/README.md
Topics:
- generics views
There two level of permission in django-rest-framework
- Global Level Permission
- Object Level Permission
And also there's 4 type of permissions in drf
- AllowAny
- IsAuthenticated
- IsAdminUser
- IsAuthenticatedOrReadOnly
And also you can set your CustomPermissions
also.
First, we're going to look into IsAuthenticated
as global level for that you just need to add the permission on your projects settings.py
file it will then it will affect all your applications as well.
Actually, IsAuthenticated
means you need to login when you need to access anything so if you're not then not able to do anything.
For the above IsAuthenticated
activate as global level in you're project first you need to open settings.py
file and scroll down to the bottom of the file the copy paste the below code in your file
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
The above provided code will help you globally your project to login if you want to do some operations. So, if you're not login the result will look like below
And you need to provide the credentials to access the contents of your applications in your entire project.
So, next we're going to create object-level permission
before that we're going to comment down the global-level permission
otherwise it will override that's not a good practise!
if you're using class-based views on your applications you need to specify the permission_classes
on your class. Or, if you're using function-based views you need to import the decorators from your rest_framework.permissions
module. Actually, here we're using class based views so we're going to use the permission_classes
on our class, but before that you need to import some classes from rest_framework.permissions
module. like below
from rest_framework.response import Response
from rest_framework import status
from rest_framework import generics
from .models import *
from .serializer import *
from rest_framework.permissions import IsAuthenticated
class RecipeListCreateView(generics.ListCreateAPIView):
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
permission_classes = [IsAuthenticated]
After i made the above change in my RecipeApp
i can access every other applications views without login. But for RecipeApp
i need to Authorise before trying to do any changes!!
This is how the IsAuthenticated
method will work in Class Based Views
, We will check out how this is going to work in Function Based Views
for that we're going to select the function-based application employeeDetails
that we create in previous section. For function based views we're going to use the decorator like below.
from .models import Employee
from .serializer import EmployeeSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
# Create your views here.
@api_view(['GET', 'POST']) #this decorator only used when we use function based views
@permission_classes([IsAuthenticated])
def employee_list(request):
if request.method == "POST":
serializer = EmployeeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
all_emp = Employee.objects.all()
#converting to json using serializer
serialized_data = EmployeeSerializer(all_emp, many=True) #if we are fetching more than one data we need to add many=True
return Response(serialized_data.data, status=status.HTTP_200_OK) #status code 200
In the above code first we import the decorator permission_classes
from the rest_frmework.decorators
module and then we import the IsAuthenticated
from rest_framework.permissions
module. This how the Authenticating permission implement in Function Based Views
Next we're going to look into IsAdminUser
this method is all about the permissions only allowed for Admin
users if you give this in your permission_classes
the view will only allowed to the Admin Users
first we will implement this in a Class Based Views
for that we're going to use the application RecipeApp
from rest_framework.response import Response
from rest_framework import status
from rest_framework import generics
from .models import *
from .serializer import *
from rest_framework.permissions import IsAuthenticated, IsAdminUser
class RecipeListCreateView(generics.ListCreateAPIView):
queryset = Recipe.objects.all()
serializer_class = RecipeSerializer
#permission_classes = [IsAuthenticated]
permission_classes = [IsAdminUser]
After the implementation of IsAdminUser
if you try to access the content using other login credentials the result will be look like below
And you're only able to access the contents if you logged in as a admin!!
Let's look into Function Based Views
also for that we're going to take the employeeDetails
application!
from .models import Employee
from .serializer import EmployeeSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, IsAdminUser
# Create your views here.
@api_view(['GET', 'POST']) #this decorator only used when we use function based views
@permission_classes([IsAdminUser])
def employee_list(request):
if request.method == "POST":
serializer = EmployeeSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
all_emp = Employee.objects.all()
#converting to json using serializer
serialized_data = EmployeeSerializer(all_emp, many=True) #if we are fetching more than one data we need to add many=True
return Response(serialized_data.data, status=status.HTTP_200_OK) #status code 200
Next we're going to look into IsAuthenticatedOrReadOnly
permissions. This permission will allow users to view (GET)
the content without login but if they try to write (POST)
something they need to login. For this method we're going to use the booksApp
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
from .models import Author, Category, Book
from .serializer import AuthorSerializer, CategorySerializer, BookSerializer
from rest_framework import mixins, generics
from rest_framework.permissions import IsAuthenticatedOrReadOnly
# Create your views here.
#Generic Views
class BookListGenericView(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
#get method
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
#post method
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
The above code you can view the contents without login but you need to login when you want to do the post method.
Our next topic is Custom Permissions
we're going to create our own custom permissions, for that we're going to build an app called LbraryApp
here librarian will be the admin and the user is who comes to read the books and we're going to build this app as Function Based View
app.
For code and explanation refer LbraryApp/README.md
This section is mainly focused for the advanced methods that developers used in their backend projects and we will discuss the function based and class based views here. Then also we will cover the advanced methods in serializers and models also. The application to demonstrate this section is advancedMethods
.
For code and explanations refer advancedMethods/README.md
Topics:
- Login Form
- Decorators
- Uploading a File
- Downloading a File
- Sending Email
- Added Decorators to class based views
- Decorators for a specific method
-
Changing Timezones Cron Jobs & Worker
-
Adding a cron job using "django-crontab" To install the package use the below command:
pip install django-crontab
You can find the documentation about usage goto
advancedMethods
README.md
file.
Configuring celery and redis for add worker & cron jobs
-
Adding celery for asychronous tasks and scheduling jobs
To install the package use the below command:
pip install celery
-
Adding django-celery-beta to store the periodic task in database
To install the package use the below command:
pip install django-celery-beat
Note
I am using ubuntu 24.04 and used snap for installing redis and redisinsight. If you're using MacOS, windows, or other linux distro please read the documentation on redis website
-
Configuring redis with django
To install the package use the below command:
pip install django-redis
- Single serializer for multiple data from multiple column
- For the cases like accessing elements from multiple tables using
ORM
query - Uploading multiple images
SerializerMethodField
and saving binary data to the database without saving to Disk
- Adding
JSONField()
data to database
- for testing purpose
perform_create
method for generic viewsperform_update
for update an instance
- Adding cron job using celery
-
SERVER HEALTH CHECK Application using django rest-framework
Created an django rest api for check the health of server and database. It will help you to analyze the cpu, memory usage for your system and database it connected or not. For this i created an app named server_health_check and after url routeing and added to
INSTALLED_APPS
i created aAPIView
like below to check the server and db status:
from rest_framework.response import Response
from rest_framework.views import APIView
# Create your views here.
class HealthCheckView(APIView):
def get(self, request):
data = {
"status":"OK",
"Database":self.check_database(),
"Server":self.check_server()
}
return Response(data)
def check_database(self):
try:
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
result = cursor.fetchone()
connection.close()
if result == (1,):
return "Database is working fine!"
else:
return "Database connection failed!"
except Exception as e:
return "Checking connection db is failed:"+str(e)
def check_server(self):
import psutil
cpu_percent =psutil.cpu_percent(interval=1)
memory = psutil.virtual_memory()
disk = psutil.disk_usage("/")
return {
"CPU Count":psutil.cpu_count(),
"CPU Usage":"{:.1f}%".format(cpu_percent),
"Memory Usage":"{:.1f}%".format(memory.percent),
"Disk Usage":"{:.1f}%".format(disk.percent),
"Current Running Processes Count":len(list(psutil.pids())),
"Running Processes": list(set([proc.name() for proc in psutil.process_iter()]))
}
After creating views you have to add the views to the urls.py
like below:
from django.urls import path
from server_health_check.views import *
urlpatterns = [
path("health-check/", HealthCheckView.as_view(), name="server-health-check")
]
Important
Install psutil
before using package
To install psutil
pip install psutil
The result will look like below:
Configuring sentry on django, for that first we need to install create an account in sentry to create an account Click Here
After creating account install the sentry-sdk
like below
pip install --upgrade 'sentry-sdk[django]'
After installing sentry-sdk
you have to configure the sentry
on settings.py
like below:
import sentry_sdk
sentry_sdk.init(
dsn=os.getenv("SENTRY_DSN"),
)
After the configuration create an custom error to check sentry configure or not like below on your projects urls.py
file
"""
URL configuration for drf_api_project_folder project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
#To check the sentry debug mode
def trigger_error(request):
division_by_zero = 1 / 0
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')), #this from drf official website
path('', include('mainApp.urls')),
path('api/hello-world-api/', include('helloWorld.urls')),
path('api/employee-details-api/', include('employeeDetails.urls')),
path('api/student-details-api/', include('StudentDetails.urls')),
path('api/todo-api/', include('TodoApp.urls')),
path('api/voters-details-api/', include('votersDetails.urls')),
path('api/spotify-api/', include('spotifyApp.urls')),
path('api/blog-api/', include('BlogApp.urls')),
path('api/books-api/', include('booksApp.urls')),
path("api/movie-review-api/", include('MovieReviewApp.urls')),
path('api/music-api/', include('MusicAPI.urls')),
path('api/recipe-api/', include('RecipeApp.urls')),
path('api/library-api/', include('LbraryApp.urls')),
path('api/advanced-methods-api/', include('advancedMethods.urls')),
path("api/server/", include("server_health_check.urls")),
path('sentry-debug/', trigger_error), #to check the sentry debug mode
]
After that navigate to the sentry-debug/
endpoint you will receive an error regarding your project.
For the custom errors if your add to try
, exception
block you have to do like this below: for that I am using my last server-health-check
application and I am going to write the custom views like below:
class TestAPIView(APIView):
def get(self, request):
try:
result = 1/0
return Response({"result":result})
except Exception as e:
sentry_sdk.capture_exception(e)
return Response({"message":"Something went wrong!!"})
Adding documentation using swagger-ui
. For, that first you need to install drf-yasg
use the below command to install the django restframework swagger extension
pip install drf-yasg
After installing the module you have to add the module in your settings.py
like below
INSTALLED_APPS = [
#other apps
'drf_yasg',
]
After adding the module to the INSTALLED_APPS
configure the urls for swagger like below
from django.contrib import admin
from django.urls import path, include
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from django.conf import settings
from django.conf.urls.static import static
schema_view = get_schema_view(
openapi.Info(
title="DRF Tutorial",
default_version='v1',
description="DRF Tutorial",
),
public=True,
)
urlpatterns = [
#other endpoints
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name="schema-swagger-ui"),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
path('swagger.json', schema_view.without_ui(cache_timeout=0), name='schema-json'),
]
Then run the command python manage.py runserver
and navigate to 127.0.0.1:8000/swagger/
You can find your documentation's there
Writing some test cases to the API here we're going to use the advancedMethods
api to test. First we need to create a folder called tests
inside the advancedMethods
app folder.
Inside the tests
folder adding different test cases for serializers
, models
, and endpoints
like below
test_models.py
test_serializers.py
test_views.py
And always remember to add test in front of the test case like below
#test_serializers.py
from rest_framework import serializers
from django.test import TestCase
from advancedMethods.serializer import *
from advancedMethods.models import *
class TestSerializersTest(TestCase):
def test_valid_data(self):
data = {
"role_name":"admin",
"permissions":{
"Create":True,
"Read":True
}
}
serializer = userRoleSerializer(data=data)
self.assertTrue(serializer.is_valid())
def test_invalid_data(self):
data = {
"role_name":"",
"permissions":{
"Create":True,
"Read":True
}
}
serializer = userRoleSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIn("role_name", serializer.errors)
#test_models.py
from django.test import TestCase
from advancedMethods.models import *
class TestProcessModel(TestCase):
def test_model_fields(self):
model = Process.objects.create(
title="test",
days_activation=1
)
self.assertEqual(model.title, "test")
self.assertEqual(model.days_activation, 1)
# test_views.py
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from unittest.mock import patch
from advancedMethods.models import *
from django.test import TestCase, override_settings
from pathlib import Path
class AdvancedMethodsTestCase(APITestCase):
def setup(self):
pass
def test_get_endpoint(self):
response = self.client.get("/api/advanced-methods-api/test-table-1")
self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertIn("id", response.json())
def test_post_endpoint(self):
data = {
"col_1": "Hello",
"col_2": "Hello 1",
"col_3": "Hello 2"
}
response = self.client.post("/api/advanced-methods-api/test-table-1", data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
#testing with decorator function
class LocationListAPIViewDecoratorTest(APITestCase):
def setUp(self):
self.client = APIClient()
self.location = Location.objects.create(name="Test Location")
Products.objects.create(name="Test Product", location_id=self.location, price=44)
def test_get_endpoint(self):
response = self.client.get("/api/advanced-methods-api/location-list-api-view/")
self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertIn("id", response.json())