Creating Django Rest Framework's Profile API
Updated: Dec 3, 2022
Planning:
1. Create profile API
Handles registration of new users
Validate profile Data
2. Listing Existing Profiles.
Search for profile and name
Email and Name
API URLS
127.0.0.1:8000/api/profiles
List all profile when HTTP GET method is called.
Create new Profile when HTTP POST method is called.
Implementation:
serializer.py
from rest_framework import serializers
from profiles_api import models
class HelloSerializers(serializers.Serializer):
name=serializers.CharField(max_length=10)
age = serializers.IntegerField()
class UserProfileSerializer(serializers.ModelSerializer):
"""Serialize a user profile object """
""" Note: Define a meta class , the way you work with ModelSerializer is to use a meta class to configure a serializer to point to a specific model in our project. Class Meta will point to UserProfile models . Here need to specify list of fields in our model that has to manage through serializer """
class Meta:
model = models.UserProfile
fields = ('id', 'email','name', 'password')
extra_kwargs = {
'password' : { 'write_only' : True, 'style' :{'input_type' :'password'}}
}
"""Now override the create function of DRF so as to save password in hash type not in simple readable format"""
"""Whenever we create a profile object with our serializer. It will validate the object and
fields, provided to serializer and then it will call the create function and passing it in validated_data"""
def create(self, validated_data):
"""create and return new user"""
user = models.UserProfile.objects.create_user(
email=validated_data['email'],
name=validated_data['name'],
password=validated_data['password']
)
return user
"""Note: it will override the create function and call our create_user function from models.py file."""
def update(self, instance, validated_data):
"""Handle update user account """
if 'password' in validated_data:
password=validated_data.pop('password')
instance.set_password(password)
return super().update(instance, validated_data)
"""The default update logic for DRF ModelSerializer will take whatever fields are
provided(in our case name, email and password) and pass them directly to the model.
This is fine with the fields like name and email, However the password field require some additional logic
to hash the pasword before saving the update. Now password will be save in hash or non readable form.
Therefore we override the update() method to add this logic to check the presence of password in validated_data
which is passed from DRF when updating an object.
If the fields exists we will pop the password from the validated_data dictionary and set it using set_password
which is save the password in Hash type.
Once that done we use super().update() to pass the values to the existing DRF update method,
to handle the remaining fields"""
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from profiles_api import serializers
from rest_framework import viewsets
from profiles_api import models
ss = serializers.HelloSerializers
def get(self, request, format=None):
an_api = [
"Uses Standard HTTP methods as function(get, post,put,delete)",
"It is similar to traditional Django view",
"Gives you control over ur application and logic",
"It mapped manually to URL's"
]
print("Log for ans_api",type(an_api))
return Response({"message":"hello world", "an_api" : an_api})
def post(self, request):
serializer = self.serializer_class(data=request.data)
print("Log for serializer", type(serializer))
if serializer.is_valid():
name=serializer.validated_data.get("name")
age=serializer.validated_data.get("age")
message = f"hello {name, age}"
return Response({'message' : message})
else:
return Response(serializer.errors,
status = status.HTTP_400_BAD_REQUEST)
def put(self, request, pk=None):
"""Handling update an object"""
return Response({'method':'PUT'})
def patch(self, request, pk=None):
"""Handling partial update of an object """
return Response({'method' : 'PATCH'})
def delete(self, request,pk=None):
"""Delete an Object """
return Response({'method' : 'DELETE'})
class HelloViewSet(viewsets.ViewSet):
"""Test API ViewSet """
serializer_class = serializers.HelloSerializers
def list(self, request):
"""Return a hello Message """
a_viewset = [
"uses action such as (list, create, retrive, update, partial_update)",
"Automatically map to URL's using routers",
"Provides more functionality with less code"
]
return Response({'message' : "Hello", 'a_viewset':a_viewset})
def create(self,request):
"""create a new hello world message"""
serializer=self.serializer_class(data=request.data)
if serializer.is_valid():
name=serializer.validated_data.get('name')
age=serializer.validated_data.get('age')
message=f'Hello{name, age}!'
return Response({'message' : message})
else:
return Response(serializer.errors,
status = status.HTTP_400_BAD_REQUEST)
def retrive(self, request, pk=None):
"""Handles getting an object by ID """
return Response({'http_method': 'GET'})
def update(self, request, pk=None):
"""Handles update an object"""
return Response({'http_method':'PUT'})
def partial_update(self, request, pk=None):
"""Handles partial updates"""
return Response({'http_method':'PATCH'})
def destroy(self, request, pk=None):
return Response({'http_method' : 'DELETE'})
class UserProfileViewSet(viewsets.ModelViewSet):
"""Handling creating and updating profiles """
serializer_class=serializers.UserProfileSerializer
queryset=models.UserProfile.objects.all()
urls.py
from django.urls import path, include
from profiles_api import views
from rest_framework.routers import DefaultRouter
router=DefaultRouter()
router.register('hello-viewset', views.HelloViewSet, base_name='hello_viewset')
"""Note: base_name is use to retrive the URL in our router """
router.register('profile', views.UserProfileViewSet)
"""Note: We dont need to give basename here because UserProfileViewSet has a queryset
that takes a name from models."""
urlpatterns = [
path('hello-view/', views.HelloApiVIew.as_view()),
path('', include(router.urls))
]
models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.models import BaseUserManager
class UserProfileManager(BaseUserManager):
def create_user(self, email, name, password=None):
if not email:
raise ValueError("User Email Is Important")
email=self.normalize_email(email)
user=self.model(email=email, name=name)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, name, password):
user=self.create_user(email, name, password)
user.is_superuser=True
user.save(using=self._db)
return user
class UserProfile(AbstractBaseUser, PermissionsMixin):
email=models.EmailField(max_length=255, unique=True)
name=models.CharField(max_length=255)
is_active=models.BooleanField(default=True)
is_staff=models.BooleanField(default=False)
objects=UserProfileManager()
USERNAME_FIELD="email"
REQUIRED_FIELDS=["name"]
def get_full_name(self):
return self.name
def short_name(self):
return self.name
def __str__(self):
return self.email
Note: Now you can delete, update and path anyones account try it before going forward
127.0.0.1:8000/api/profile/1 you will see all the option to edit and delete. However to control this and only authentic user can modify or delete its own profile we have to add authentications and permissions for that create a new file permissions.py in profiles_api
and write down the following code.
from rest_framework import permissions
class UpdateOwnProfile(permissions.BasePermission):
"""Allow user o edit their own permission"""
def has_object_permission(self, request, view, obj):
"""check user is trying to update their own profile"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.id==request.user.id
"""if request.method contain GET request it will show all the profiles
means readable mode. if request.method contain editable request like PUT,PATCH,DELETE etc then it will check request.user.id === obj.id if both are same it will return true else false means not allow to perform put, PATCH kind of operation. you will not see the option their."""