Django REST Framework with MongoDB: A Comprehensive Tutorial

Introduction

If you’re looking to enhance your Django REST Framework project with the power of MongoDB Atlas and PyMongo, you’re in the right place. In this blog post, we’ll guide you through the process of setting up MongoDB Atlas, connecting it to Django, and incorporating the flexibility of NoSQL data handling in your Django apps development.

MongoDB is a widely used NoSQL database that stores data in a flexible, JSON-like format. To interact with MongoDB visually, we’ll be using MongoDB Compass, a user-friendly graphical interface. Before we delve into the Django integration, let’s take a moment to understand MongoDB and set it up for Python queries.

Related read: Django REST Framework with MongoDB: A Comprehensive Tutorial

Setting Up MongoDB Atlas

Step 1: Create an Account on MongoDB Atlas

Head over to the MongoDB Atlas website here and hit the “Start Free” button. Fill in your details, agree to the terms, and click “Get Started Free” to kick off the account creation.

Step 2: Create a Cluster

Once your account is set up, log in to MongoDB Atlas. Click on “Build a Cluster,” choose your preferences, and click “Create Cluster.” Wait a few minutes for the cluster to be deployed.

Congratulations! MongoDB Atlas is now set up.

Step 3: Setting up MongoDB Compass

MongoDB Compass provides a visual representation of your MongoDB data, allowing easy exploration and interaction with databases.

🔸 Install MongoDB Compass from the official website.
🔸 Open MongoDB Compass, Click “New Connection.”
🔸 Copy the connection string from MongoDB Atlas (found on the cluster overview page) and paste it into MongoDB Compass.
🔸 Click “Connect” to establish a connection to your MongoDB Atlas cluster.

You’ve successfully set up MongoDB Atlas and connected to your cluster using MongoDB Compass. This opens up possibilities for viewing, adding, editing, and deleting documents directly in MongoDB.

Exploring MongoDB with Python: A Simple Example

Now that we’ve successfully set up MongoDB Atlas and connected to our cluster using MongoDB Compass, let’s take the next step and explore MongoDB with Python. In this section, we’ll walk through a simple example using the PyMongo library to connect to MongoDB and perform basic operations.

Prerequisites

Before we dive into the example, ensure you have the PyMongo library installed. If not, you can easily install it using the following command:

pip install pymongo

Connecting to MongoDB

To interact with MongoDB in Python, we’ll utilize the PyMongo library. Below is a straightforward script that establishes a connection to MongoDB Compass. Remember to replace <username> and <password> with your MongoDB Atlas cluster credentials.

from pymongo import MongoClient

# MongoDB connection URI (replace with your own connection string)
connection_uri = "mongodb+srv://<username>:<password>@cluster0.mongodb.net/test?retryWrites=true&w=majority"

# Connect to MongoDB
client = MongoClient(connection_uri)

Selecting a Database and Collection

Once connected, we can specify the database and collection we want to work with. In our example, let’s assume we have a database named ‘blog_db’ and a collection named ‘posts’:

# Select the database
db = client.blog_db

# Select the collection
collection = db.posts

Replace ‘blog_db’ and ‘posts’ with your actual database and collection names.

Querying the Collection

Now, let’s perform a simple query on the collection. Suppose we want to find all posts written by a specific author. We can define a filter for the query and execute it as follows:

# Define a filter for the query
filter_criteria = {"author": "John Doe"}

# Query the collection with the filter
result = collection.find(filter_criteria)

Replace “author”: “John Doe” with your actual filter criteria.

Displaying Results

Finally, we can iterate over the query results and print them:

# Print the results
for document in result:
print(document)

This script will print each document (or record) that matches the specified filter criteria. It’s a simple yet powerful way to retrieve and display data from your MongoDB collection.

In the next part of our blog, we’ll continue building on this example, showcasing more advanced MongoDB operations in conjunction with Django. Stay tuned for a deeper dive into the world of MongoDB and Python integration!

Building a Django REST Framework Project with MongoDB

Now that we’ve explored connecting to MongoDB using PyMongo, it’s time to step into the world of Django REST Framework (DRF). In this part of our blog series, I’ll guide you through setting up a full-fledged Django REST Framework project with MongoDB as the backend. We’ve chosen PyJWT for token-based authentication, aligning with the flexibility of MongoDB.

Why PyJWT over SimpleJWT?

In our Django REST Framework project, the choice between PyJWT and SimpleJWT is deliberate. While SimpleJWT is tailored for SQL databases, PyJWT’s adaptability fits seamlessly with our MongoDB backend.

Setting Up the Django Project

Step 1: Create a Virtual Environment

In your terminal, navigate to your desired directory and activate a virtual environment:

Navigate to the desired directory.

cd path/to/your/directory

Activate virtual environment.

source venv/bin/activate # For Unix/Linux
venv\Scripts\activate # For Windows

Step 2: Install Django and Required Packages

With the virtual environment activated, install Django, Django REST Framework, and PyJWT:

Install Django.

pip install django

Install Django REST Framework.

pip install djangorestframework

Install PyJWT.

pip install pyjwt

Step 3: Create a Django Project

Now that we have the necessary packages installed, create a new Django project named “django_with_mongo”:

Create a New Django Project.

django-admin startproject django_with_mongo

Navigate into the project directory.

cd django_with_mongo

Create the ‘customuser’ and ‘project’ apps:

Create ‘customuser’ app.

python manage.py startapp customuser

Create ‘project’ app.

python manage.py startapp project

Step 4: Configure Settings.py

Update the INSTALLED_APPS in the settings.py file to include the necessary apps:

# settings.py

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

'rest_framework', # <- Django REST Framework
'customuser', # <- 'customuser' app
'project', # <- 'project' app

Remove the DATABASE settings from settings.py, as MongoDB does not require it for configuration.

With these steps, you’ve successfully set up the foundation for your Django REST Framework project with MongoDB. In the next part, we’ll delve into creating API’s, handling user registration, and incorporating PyJWT for authentication. Stay tuned for more exciting developments in our Django and MongoDB journey!

Enhancing Authentication and Permissions in Django with MongoDB

Till now, we have set up the foundation for our Django REST Framework project with MongoDB. Now, let’s take a closer look at the authentication and permission classes that will empower our application.

Custom Authentication with MongoDB

MongoDB doesn’t provide built-in authentication classes, so we’ve crafted our own custom classes using the PyJWT library. Below is an explanation of the MongoDBAuthentication class:

# user/permissions.py

import os
from pymongo import MongoClient
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication

client = MongoClient(os.getenv("CONNECTION_STRING"))
database = client[os.getenv("DATABASE")]
customuser_collection = database.customuser


def get_user_by_id(self, user_id):
return customuser_collection.find_one({"_id": ObjectId(user_id)})


class MongoDBAuthentication(BaseAuthentication):
"""
Class to authenticate incoming requests.
"""

def authenticate_user(self, token):
try:
# checking if token is blacklisted
black_list_collection.find(
{"token": token}
)
if black_list_collection is not None:
raise ExpiredSignatureError

# decoding token to get user_id from token payload
user_id = jwt.decode(token, os.getenv("JWT_PUBLIC_KEY"), algorithms=["RS256"])

if user_id and user_id["token_type"] == "access":
# get user object from CustomUser collection and return
user_obj = get_user_by_id(user_id["id"])
user_obj.pop("password")

return user_obj
else:
raise AuthenticationFailed(
_("Given token not valid for any token type."),
)
except InvalidSignatureError:
raise AuthenticationFailed(
_("Given token not valid for any token type."),
)

except ExpiredSignatureError:
raise AuthenticationFailed(
_("Token is invalid or expired."),
)

def authenticate(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION')
if auth_header:
key, token = auth_header.split(' ')


if key == 'Bearer':
user_data = self.authenticate_user(token)
if user_data:
user_data["is_authenticated"] = True
return parse_json(user_data), None
else:
return None
else:
raise AuthenticationFailed(
_("Authorization header must contain two space-delimited values."),
)

return None

Explanation:

🔸 Authenticates incoming requests using JWT tokens.
🔸 Examines the Authorization header in the request.
🔸 Extracts the token and attempts to decode it using the JWT public key.
🔸 Checks for revoked tokens in the black_list_collection.
🔸 Retrieves user information from the custom_user collection if the token is valid.
🔸 Returns the user data with an is_authenticated flag set to True.
🔸 Raises appropriate exceptions for invalid or expired tokens.
🔸 Parse_json converts MongoDB objects to json, find more about it in next part of the blog.

Key Points:

🔸 Stores revoked tokens in MongoDB for efficient invalidation.
🔸 Uses the RS256 algorithm for secure token signing.
🔸 Retrieves user information from a custom model, allowing flexibility in user management.

Custom Permissions with MongoDB

I’ve also defined custom permission classes to control API access based on user roles. Let’s explore these classes:

class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""

def has_permission(self, request, view):
return bool(request.user and request.user["is_authenticated"])

Explanation:

🔸 Allows access only to authenticated users.
🔸 Checks for the presence of a valid user object in the request.

class IsManager(BasePermission):
"""
Allows access only to PM.
"""

def has_permission(self, request, view):
return bool(request.user and request.user["role"] == "PM")

Explanation:

🔸 Allows access only to users with the “PM” role.
🔸 Verifies the user’s role from their user data.

class IsAdmin(BasePermission):
"""
Allows access only to admin.
"""

def has_permission(self, request, view):
return bool(request.user and request.user["role"] == "ADMIN")

Explanation:

🔸 Allows access only to users with the “ADMIN” role.
🔸 Similarly verifies the user’s role.

Key Points:

🔸 Custom permissions allow fine-grained control over API access based on user roles.
🔸 Clear separation of concerns between authentication and authorization.

In our next blog installment, we’ll integrate these custom classes into our Django REST Framework project, showcasing a seamless flow of authentication and authorization with MongoDB.
Stay tuned for a deeper dive into the world of Django, MongoDB, and secure API access!

Developers Skilled in Django REST Framework and MongoDB are Available for Seamless Integration. Hire Now to Elevate!

Parsing MongoDB Resultsets

Now let us see a method that converts data received from MongoDB to Json, we will be using this in our API’s.

def convert_object_ids_to_str(doc):
for key, value in doc.items():
if isinstance(value, ObjectId):
doc[key] = str(value)
elif isinstance(value, datetime):
doc[key] = value.isoformat()
elif isinstance(value, dict):
convert_object_ids_to_str(value)
elif isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, dict):
convert_object_ids_to_str(item)

Explanation:

Purpose: Recursively converts MongoDB-specific ObjectIds and datetime objects within a document to their string representations, ensuring compatibility with JSON serialization.

Steps:

🔸 Iterates through each key-value pair in the document.
🔸 If the value is an ObjectId, converts it to a string using str(value).
🔸 If the value is a datetime object, converts it to ISO 8601 format using value.isoformat().
🔸 If the value is a dictionary or list, recursively calls itself to handle nested data structures.

def parse_json(data):
if isinstance(data, Cursor) | isinstance(data, CommandCursor):
parsed_data_list = list()
for obj in data:
convert_object_ids_to_str(obj)
parsed_data = json.loads(json_util.dumps(obj))
parsed_data_list.append(parsed_data)
else:
convert_object_ids_to_str(data)
parsed_data = json.loads(json_util.dumps(data))
return parsed_data

return parsed_data_list

convert_object_ids_to_str(data)
parsed_data = json.loads(json_util.dumps(data))

return parsed_data

Purpose: Converts data retrieved from MongoDB (cursors or individual documents) to valid JSON format.

Steps:

🔸 If data is a Cursor or CommandCursor:
🔸 Initializes an empty list parsed_data_list to store parsed results.
🔸 Iterates through each document in the cursor:
🔸 Calls convert_object_ids_to_str to handle ObjectIds and datetimes within the document.
🔸 Converts the document to JSON using json_util.dumps and loads it back into a Python dictionary for proper parsing.
🔸 Appends the parsed document to parsed_data_list.
🔸 Returns the list of parsed documents.
🔸 If data is a single document:
🔸 Calls convert_object_ids_to_str to handle ObjectIds and datetimes.
🔸 Converts the document to JSON using json_util.dumps and loads it back into a Python dictionary.
🔸 Returns the parsed document.

Unlocking User Authentication APIs in Django with MongoDB

In our Django REST Framework project, seamless user authentication is a cornerstone, and I’ve meticulously designed a suite of APIs to manage user registration, login, and more. Let’s delve into the functionality of each API, unveiling the MongoDB intricacies woven into the fabric of our authentication system.

1. Signup API (SignupAPIView)

Endpoint: /api/user/signup/
Method: POST

Description: This API empowers users to register by providing essential details such as first name, last name, email, role, and a password. Upon a successful registration, it returns the user’s unique identifier (id) accompanied by a 201 Created status.

MongoDB Aspect:

The user’s registration data is intelligently stored in MongoDB, capitalizing on its flexibility to adeptly handle diverse data structures.

# customuser/views.py

def create_user(self, user_data):
try:
customuser_collection.create_index([('email', 1)], unique=True)

user_data['password'] = make_password(user_data['password'])
user_data["created_at"] = datetime.now(timezone.utc)
user_data["updated_at"] = datetime.now(timezone.utc)
result = customuser_collection.insert_one(user_data)
return result.inserted_id

except DuplicateKeyError as e:
raise CustomException("User already exists.")

class SignupAPIView(CreateAPIView):
"""
Class to create register new user.
"""
permission_classes = ()
authentication_classes = ()
serializer_class = UserProfileSerializer

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(SignupAPIView, self).__init__(**kwargs)

def post(self, request, *args, **kwargs):
"""
Method to create and register a new user.
"""
user_serializer = self.get_serializer(data=request.data)
if user_serializer.is_valid(raise_exception=True):
user_data = user_serializer.validated_data
user_id = create_user(user_data)
if user_id:
self.response_format["status_code"] = status.HTTP_201_CREATED
self.response_format["data"] = {"id": json.loads(json_util.dumps(user_id))["$oid"]}
self.response_format["error"] = None
self.response_format["message"] = [messages.CREATED.format("User")]
else:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "User"
self.response_format["message"] = [messages.UNEXPECTED_ERROR]

return Response(self.response_format)

Explanation:

🔸 The SignupAPIView class inherits from CreateAPIView, streamlining the creation of a new user.
🔸 MongoDB effortlessly handles the diverse user data structures, ensuring a seamless registration process.
🔸 The create_user method integrates MongoDB to store user information securely.
🔸 The response is carefully formatted to include the user’s unique identifier and relevant status codes.

2. Login API (LoginAPIView)

Endpoint: /api/user/login/
Method: POST

Description: Users can log in using their credentials through this API. Upon a successful login, it returns a JSON Web Token (JWT) containing user information, facilitating secure authentication for subsequent requests.

MongoDB Aspect:

User credentials are verified against MongoDB, ensuring a seamless integration between the Django project and the MongoDB database.

# customuser/views.py
def get_user_by_email(self, email):
return customuser_collection.find_one({'email': email})


class LoginAPIView(CreateAPIView):
"""
Class to log in user.
"""
permission_classes = ()
authentication_classes = ()
serializer_class = UserLoginSerializer

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(LoginAPIView, self).__init__(**kwargs)

def post(self, request, *args, **kwargs):
"""
Method to login user and return jwt tokens.
"""
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(raise_exception=True):
user = get_user_by_email(serializer.validated_data['username'])
if user and check_password(serializer.validated_data['password'], user['password']):
user = parse_json(user)

jwt_token = get_tokens_for_user(user)

response_data = {
"first_name": user["first_name"],
"last_name": user["last_name"],
"email_name": user["email"],
"role": user["role"],
"token": jwt_token,

}
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["data"] = response_data
self.response_format["error"] = None
self.response_format["message"] = [messages.LOGIN_SUCCESS]

else:
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["data"] = None
self.response_format["error"] = "User"
self.response_format["message"] = [messages.INVALID_CREDENTIALS]

return Response(self.response_format)

3. Logout API (LogoutAPIView)

Endpoint: /api/user/logout/
Method: POST

Description: Users can log out through this API, which not only invalidates the access token but also blacklists it for enhanced security. The API requires an authentication header with the access token.

MongoDB Aspect:

The blacklisted tokens are stored in a MongoDB collection, providing a centralized mechanism to track and manage token revocation.

# customuser/views.py

class LogoutAPIView(CreateAPIView):
"""
Class for creating API view for user logout.
"""
permission_classes = (IsAuthenticated,)
authentication_classes = (MongoDBAuthentication,)

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(LogoutAPIView, self).__init__(**kwargs)

def post(self, request, *args, **kwargs):
"""
POST Method for logging out the user and blacklisting the access token used.
"""
auth_header = request.META.get('HTTP_AUTHORIZATION')
if auth_header:
key, access_token = auth_header.split(' ')
refresh_token = request.data.get("refresh")

black_list_collection.insert_many(
[{"token": access_token, "timestamp": datetime.now(timezone.utc)},
{"token": refresh_token, "timestamp": datetime.now(timezone.utc)}]
)
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["data"] = None
self.response_format["error"] = None
self.response_format["message"] = [messages.LOGOUT_SUCCESS]
return Response(self.response_format)

4. Get User API (GetUser)

Endpoint: /api/user/user/
Method: GET

Description: Retrieves information about the user currently logged in. Requires a valid access token for authentication.

MongoDB Aspect:

User information is retrieved from MongoDB based on the authenticated user’s data.

# customuser/views.py

class GetUser(RetrieveAPIView):
permission_classes = (IsAuthenticated,)
authentication_classes = (MongoDBAuthentication,)

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(GetUser, self).__init__(**kwargs)

def get(self, request, *args, **kwargs):
self.response_format["data"] = request.user
self.response_format["error"] = None
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["message"] = [messages.SUCCESS]
return Response(self.response_format)

We’re not done yet! There’s more to discover about building awesome features using Django REST Framework and MongoDB. So, stick around to learn even more cool stuff!

CRUD Operations in Django REST Framework with MongoDB

Let’s dive into how we can create, read, update, and delete data (CRUD operations) in our Django REST Framework project, using MongoDB as our database!

Certainly! In our example, imagine you’re building a system for managing clients and projects. Clients are like the companies you work with, and each client can have multiple projects.

Related read: Automating Database Translation in Django REST Framework Using gTranslate Library

1. Create Client API (CreateClientAPIView)

Method: POST
Endpoint: /project/createClient/

Description: Allows the creation of a new client.

Permissions: Only authenticated users with admin privileges can create clients.

MongoDB Aspect:

The client data is serialized and stored in the client_collection MongoDB collection. The created_by field is set to the ObjectId of the user creating the client.

# project/views.py

def create_client(self, client_data):
client_data["created_at"] = datetime.now(timezone.utc)
client_data["updated_at"] = datetime.now(timezone.utc)
result = client_collection.insert_one(client_data)
return result.inserted_id

def get_client_by_id(self, client_id):
return client_collection.find_one({"_id": ObjectId(client_id)})


class CreateClientAPIView(CreateAPIView):
"""
Class to create client.
"""
permission_classes = (IsAuthenticated, IsAdmin)
authentication_classes = (MongoDBAuthentication,)
serializer_class = ClientSerializer

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(CreateClientAPIView, self).__init__(**kwargs)

def post(self, request, *args, **kwargs):
"""
Post method to create client.
"""
client_serializer = self.get_serializer(data=request.data)
if client_serializer.is_valid(raise_exception=True):
request_data = client_serializer.data
request_data["created_by"] = ObjectId(request.user["_id"])
client_id = create_client(request_data)

client = get_client_by_id(client_id)

self.response_format["status_code"] = status.HTTP_201_CREATED
self.response_format["data"] = parse_json(client)
self.response_format["error"] = None
self.response_format["message"] = [messages.CREATED.format("Client")]
return Response(self.response_format)

2. Update Client API (UpdateClientAPIView)

Method: PATCH
Endpoint: /project/updateClient/<client_id>/

Description: Update details of a specific client.

Permissions: Only authenticated admin can update client details.

MongoDB Aspect:

Updates client details in the client_collection based on the provided client_id.

# project/views.py

class UpdateClientAPIView(UpdateAPIView):
"""
Class to create API for updating client.
"""
authentication_classes = (MongoDBAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
serializer_class = ClientSerializer

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(UpdateClientAPIView, self).__init__(**kwargs)

def patch(self, request, *args, **kwargs):
"""
Method to update client.
"""
client_id = self.kwargs["pk"]

client_serializer = self.get_serializer(data=request.data)
if client_serializer.is_valid(raise_exception=True):

filter_condition = {"_id": ObjectId(client_id)}
update_fields = {
"$set": {
"first_name": client_serializer.validated_data["first_name"],
"last_name": client_serializer.validated_data["last_name"],
"company_name": client_serializer.validated_data["company_name"],
"company_address": client_serializer.validated_data["company_address"],
"description": client_serializer.validated_data["description"],
"updated_at": datetime.now(timezone.utc),

}
}

result = client_collection.update_one(filter_condition, update_fields)
if result.matched_count == 0:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Client"
self.response_format["message"] = [messages.DOES_NOT_EXIST.format("Client")]

elif result.matched_count == result.modified_count:
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["data"] = None
self.response_format["error"] = None
self.response_format["message"] = [messages.UPDATED.format("Client")]

else:
self.response_format["status_code"] = status.HTTP_500_INTERNAL_SERVER_ERROR
self.response_format["data"] = None
self.response_format["error"] = "Unexpected error"
self.response_format["message"] = [messages.UNEXPECTED_ERROR]

return Response(self.response_format)

5.Delete Client API (DeleteClientAPIView)

Method: DELETE
Endpoint: /deleteClient/<client_id>/

Description: Delete a specific client and associated projects.

Permissions: Only authenticated admin can delete client details.

MongoDB Aspect:

Deletes the client and its associated projects from client_collection and project_collection, respectively.

# project/views.py

class DeleteClientAPIView(DestroyAPIView):
"""
Class to create API to delete client.
"""
authentication_classes = (MongoDBAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(DeleteClientAPIView, self).__init__(**kwargs)

def delete(self, request, *args, **kwargs):
"""
Method to delete client.
"""
try:
client_id = self.kwargs["pk"]

# If you want to keep project details and delete only client, set foreignkey to null for client to be deleted.
# projects = project_collection.update_many(
# {"client_id": ObjectId(client_id)},
# {"$set": {"client_id": None}}
# )

# Delete client and all related projects.
projects = project_collection.delete_many({"client_id": ObjectId(client_id)})

client = client_collection.delete_one({"_id": ObjectId(client_id)})
if client.deleted_count > 0:
self.response_format["status_code"] = status.HTTP_204_NO_CONTENT
self.response_format["data"] = None
self.response_format["error"] = None
self.response_format["message"] = [messages.DELETED.format("Client")]
else:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Client"
self.response_format["message"] = [messages.DOES_NOT_EXIST.format("Client")]
except InvalidId:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Client"
self.response_format["message"] = [messages.INVALID_ID.format("Client")]

return Response(self.response_format)

These snippets provide a glimpse into the elegance of combining Django REST Framework with MongoDB, creating a robust foundation for handling client-related operations. In the next segment, we will explore project-related APIs, unraveling the interplay between Django and MongoDB for seamless project management. Stay tuned for more!

Project APIs

1.Create Project API (CreateProjectAPIView)

Method: POST
Endpoint: /createProject/

Description: Create a new Project.

Permissions: Only authenticated admin can create Project.

MongoDB Aspect:

Serializes and stores project data in project_collection. Associates the project with an existing client based on client_id.

# project/views.py

def create_project(self, project_data):
project_data["created_at"] = datetime.now(timezone.utc)
project_data["updated_at"] = datetime.now(timezone.utc)
result = project_collection.insert_one(project_data)
return result.inserted_id

def get_project_by_id(self, project_id):
return project_collection.find_one({"_id": ObjectId(project_id)})


class CreateProjectAPIView(CreateAPIView):
"""
Class to create Project.
"""
authentication_classes = (MongoDBAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
serializer_class = ProjectSerializer

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(CreateProjectAPIView, self).__init__(**kwargs)

def post(self, request, *args, **kwargs):
"""
Post method to create client.
"""
project_serializer = self.get_serializer(data=request.data)
if project_serializer.is_valid(raise_exception=True):
request_data = project_serializer.data
request_data["created_by"] = ObjectId(request.user["_id"])
request_data["client_id"] = ObjectId(request_data["client_id"])
project_id = create_project(request_data)

project = get_project_by_id(project_id)

self.response_format["status_code"] = status.HTTP_201_CREATED
self.response_format["data"] = parse_json(project)
self.response_format["error"] = None
self.response_format["message"] = [messages.CREATED.format("Client")]
return Response(self.response_format)

2.Get Project API (GetProjectAPIView)

Method: GET
Endpoint: /getProjectDetails/<project_id>/

Description: Retrieve details of a specific Project.

Permissions: Only authenticated users(admin or manager) can get project details.

MongoDB Aspect:

Retrieves project details from project_collection with client details embedded via a $lookup.

# project/views.py

class GetProjectAPIView(RetrieveAPIView):
"""
Class to create API to get project.
"""
authentication_classes = (MongoDBAuthentication, )
permission_classes = (IsAuthenticated, (IsManager | IsAdmin))

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(GetProjectAPIView, self).__init__(**kwargs)

def get_project(self, project_id):
project = project_collection.aggregate(
[
{
"$match": {"_id": ObjectId(project_id)}
},
{
"$lookup": {
"from": "client",
"localField": "client_id",
"foreignField": "_id",
"as": "client"
}
},
{
"$project": {
"_id": 1,
"name": 1,
"cost": 1,
"description": 1,
"created_at": 1,
"updated_at": 1,
"client": {
"first_name": {"$arrayElemAt": ["$client.first_name", 0]},
"last_name": {"$arrayElemAt": ["$client.last_name", 0]}
}
}
}
]
)
return next(project, None)

def get(self, request, *arg, **kwargs):
"""
Method to get Project details.
"""
project_id = self.kwargs["pk"]
try:
project = self.get_project(project_id)
if project:
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["data"] = parse_json(project)
self.response_format["error"] = None
self.response_format["message"] = [messages.SUCCESS]
else:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Client"
self.response_format["message"] = [messages.DOES_NOT_EXIST.format("Client")]
except InvalidId:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Client"
self.response_format["message"] = [messages.INVALID_ID.format("Client")]
return Response(self.response_format)

3.Update Project API (UpdateProjectAPIView)

Method: PATCH
Endpoint: /updateProject/<project_id>/

Description: Update details of a specific Project.

Permissions: Only authenticated users(admin or manager) can update Project details.

MongoDB Aspect:

Updates project details in project_collection based on the provided project_id.

# project/views.py

class UpdateProjectAPIView(UpdateAPIView):
"""
Class to create API for updating project.
"""
authentication_classes = (MongoDBAuthentication,)
permission_classes = (IsAuthenticated, (IsManager | IsAdmin))
serializer_class = ProjectSerializer

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(UpdateProjectAPIView, self).__init__(**kwargs)

def patch(self, request, *args, **kwargs):
"""
Method to update project.
"""
project_id = self.kwargs["pk"]
try:
project_serializer = self.get_serializer(data=request.data)
if project_serializer.is_valid(raise_exception=True):

filter_condition = {"_id": ObjectId(project_id)}
update_fields = {
"$set": {
"name": project_serializer.validated_data["name"],
"cost": project_serializer.validated_data["cost"],
"client_id": project_serializer.validated_data["client_id"],
"description": project_serializer.validated_data["description"],
"updated_at": datetime.now(timezone.utc),
}
}

result = project_collection.update_one(filter_condition, update_fields)
if result.matched_count == 0:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Client"
self.response_format["message"] = [messages.DOES_NOT_EXIST.format("Project")]

elif result.matched_count == result.modified_count:
self.response_format["status_code"] = status.HTTP_200_OK
self.response_format["data"] = None
self.response_format["error"] = None
self.response_format["message"] = [messages.UPDATED.format("Project")]

else:
self.response_format["status_code"] = status.HTTP_500_INTERNAL_SERVER_ERROR
self.response_format["data"] = None
self.response_format["error"] = "Unexpected error"
self.response_format["message"] = [messages.UNEXPECTED_ERROR]
except InvalidId:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Project"
self.response_format["message"] = [messages.INVALID_ID.format("Project")]
return Response(self.response_format)

4.Delete Project API (DeleteProjectAPIView)

Method: DELETE
Endpoint: /deleteProject/<project_id>/

Description: Delete a specific Project.

Permissions: Only authenticated admin can delete Project details.

MongoDB Aspect:

Deletes the project from project_collection. Associated projects with the client are deleted as well.

# project/views.py

class DeleteProjectAPIView(DestroyAPIView):
"""
Class to create API to delete project.
"""
authentication_classes = (MongoDBAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)

def __init__(self, **kwargs):
"""
Constructor function for formatting the web response to return.
"""
self.response_format = ResponseInfo().response
super(DeleteProjectAPIView, self).__init__(**kwargs)

def delete(self, request, *args, **kwargs):
"""
Method to delete project.
"""
try:
project_id = self.kwargs["pk"]

client = project_collection.delete_one({"_id": ObjectId(project_id)})
if client.deleted_count > 0:
self.response_format["status_code"] = status.HTTP_204_NO_CONTENT
self.response_format["data"] = None
self.response_format["error"] = None
self.response_format["message"] = [messages.DELETED.format("Project")]
else:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Project"
self.response_format["message"] = [messages.DOES_NOT_EXIST.format("Project")]
except InvalidId:
self.response_format["status_code"] = status.HTTP_400_BAD_REQUEST
self.response_format["data"] = None
self.response_format["error"] = "Project"
self.response_format["message"] = [messages.INVALID_ID.format("Project")]

return Response(self.response_format)
coma

Conclusion

We built a cool app using Django and MongoDB! We made sure only authorized users can access it and added features to manage clients and projects. Everything works smoothly and handles any hiccups. Basically, Django and MongoDB work great together!

Keep Reading

Keep Reading

Struggling with EHR integration? Learn about next-gen solutions in our upcoming webinar on Mar 6, at 11 AM EST.

Register Now

Let's create something together!