0% found this document useful (0 votes)
54 views13 pages

DRF API Setup for Blog Application

Uploaded by

mukesh.khanijo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
54 views13 pages

DRF API Setup for Blog Application

Uploaded by

mukesh.khanijo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

================================================================================

DRF BASICS & REST PRINCIPLES - MID-LEVEL DEVELOPER INTERVIEW QUESTIONS


================================================================================

Q1. REST API PRINCIPLES


-----------------------
Question: Explain the key principles of REST APIs and how they apply to web
services.

Solution:
REST (Representational State Transfer) principles:

1. **Stateless**: Each request contains all information needed to process it


2. **Client-Server**: Separation of concerns between client and server
3. **Cacheable**: Responses must be cacheable
4. **Uniform Interface**: Consistent interface for all resources
5. **Layered System**: Client can't tell if connected directly to server
6. **Code on Demand**: Servers can extend client functionality

HTTP Methods in REST:


- GET: Retrieve data
- POST: Create new resource
- PUT: Update entire resource
- PATCH: Partial update
- DELETE: Remove resource

Example RESTful endpoints:


```
GET /api/posts/ # List all posts
POST /api/posts/ # Create new post
GET /api/posts/1/ # Get specific post
PUT /api/posts/1/ # Update entire post
PATCH /api/posts/1/ # Partial update
DELETE /api/posts/1/ # Delete post
```

Q2. BASIC DRF SETUP


-------------------
Question: Set up a basic Django REST Framework API for a blog application.

Solution:
```python
# [Link] - Add to INSTALLED_APPS
INSTALLED_APPS = [
# ... existing apps
'rest_framework',
]

# Configure DRF settings


REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.[Link]',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.[Link]',
'PAGE_SIZE': 10,
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.[Link]',
'rest_framework.[Link]',
],
}

# [Link]
from [Link] import models
from [Link] import User

class Post([Link]):
title = [Link](max_length=200)
content = [Link]()
author = [Link](User, on_delete=[Link])
created_at = [Link](auto_now_add=True)
updated_at = [Link](auto_now=True)
is_published = [Link](default=False)

def __str__(self):
return [Link]

# [Link]
from rest_framework import serializers
from .models import Post
from [Link] import User

class UserSerializer([Link]):
class Meta:
model = User
fields = ['id', 'username', 'email']

class PostSerializer([Link]):
author = UserSerializer(read_only=True)

class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at',
'is_published']
read_only_fields = ['created_at', 'updated_at']

def create(self, validated_data):


validated_data['author'] = [Link]['request'].user
return super().create(validated_data)

# [Link]
from rest_framework import generics
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Post
from .serializers import PostSerializer

class PostListCreateView([Link]):
queryset = [Link](is_published=True)
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]

def get_queryset(self):
return [Link](is_published=True).order_by('-created_at')

class PostDetailView([Link]):
queryset = [Link]()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# [Link]
from [Link] import path
from . import views

urlpatterns = [
path('posts/', [Link].as_view(), name='post-list-create'),
path('posts/<int:pk>/', [Link].as_view(), name='post-detail'),
]
```

Q3. SERIALIZERS AND VALIDATION


------------------------------
Question: Create serializers with custom validation for a blog post API.

Solution:
```python
# [Link]
from rest_framework import serializers
from .models import Post, Category
from [Link] import User

class CategorySerializer([Link]):
class Meta:
model = Category
fields = ['id', 'name']

class PostSerializer([Link]):
author = [Link](source='[Link]')
category_name = [Link](source='[Link]', read_only=True)

class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'category', 'category_name',
'created_at', 'is_published']
read_only_fields = ['created_at', 'author']

def validate_title(self, value):


"""Custom validation for title field"""
if len(value) < 5:
raise [Link]("Title must be at least 5 characters
long.")

# Check for duplicate titles by same author


user = [Link]['request'].user
if [Link](title=value, author=user).exists():
raise [Link]("You already have a post with this
title.")

return value

def validate_content(self, value):


"""Custom validation for content field"""
if len(value) < 50:
raise [Link]("Content must be at least 50
characters long.")
return value

def validate(self, data):


"""Object-level validation"""
if [Link]('is_published') and len([Link]('content', '')) < 100:
raise [Link](
"Posts must have at least 100 characters to be published."
)
return data

# Advanced serializer with nested data


class PostDetailSerializer(PostSerializer):
category = CategorySerializer(read_only=True)
author_posts_count = [Link]()

class Meta([Link]):
fields = [Link] + ['author_posts_count']

def get_author_posts_count(self, obj):


return [Link](author=[Link]).count()

# Usage in views
class PostDetailView([Link]):
queryset = [Link]()
permission_classes = [IsAuthenticatedOrReadOnly]

def get_serializer_class(self):
if [Link] == 'GET':
return PostDetailSerializer
return PostSerializer
```

Q4. VIEWSETS AND ROUTERS


------------------------
Question: Implement ViewSets and use DRF routers for automatic URL generation.

Solution:
```python
# [Link]
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from .models import Post, Category
from .serializers import PostSerializer, CategorySerializer

class PostViewSet([Link]):
queryset = [Link]()
serializer_class = PostSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['is_published', 'author']
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'title']
ordering = ['-created_at']

def get_queryset(self):
"""Filter queryset based on user permissions"""
if [Link].is_staff:
return [Link]()
return [Link](is_published=True)
def perform_create(self, serializer):
"""Set the author when creating a post"""
[Link](author=[Link])

@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
"""Custom action to publish a post"""
post = self.get_object()
post.is_published = True
[Link]()
return Response({'status': 'post published'})

@action(detail=False, methods=['get'])
def my_posts(self, request):
"""Custom action to get current user's posts"""
posts = [Link](author=[Link])
serializer = self.get_serializer(posts, many=True)
return Response([Link])

@action(detail=False, methods=['get'])
def published(self, request):
"""Custom action to get only published posts"""
posts = [Link](is_published=True)
serializer = self.get_serializer(posts, many=True)
return Response([Link])

class CategoryViewSet([Link]):
queryset = [Link]()
serializer_class = CategorySerializer

@action(detail=True, methods=['get'])
def posts(self, request, pk=None):
"""Get all posts in a category"""
category = self.get_object()
posts = [Link](category=category, is_published=True)
serializer = PostSerializer(posts, many=True)
return Response([Link])

# [Link]
from [Link] import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
[Link](r'posts', [Link])
[Link](r'categories', [Link])

urlpatterns = [
path('api/', include([Link])),
]

# Generated URLs:
# GET /api/posts/ # List posts
# POST /api/posts/ # Create post
# GET /api/posts/{id}/ # Retrieve post
# PUT /api/posts/{id}/ # Update post
# DELETE /api/posts/{id}/ # Delete post
# POST /api/posts/{id}/publish/ # Custom action
# GET /api/posts/my_posts/ # Custom action
# GET /api/posts/published/ # Custom action
```

Q5. AUTHENTICATION AND PERMISSIONS


----------------------------------
Question: Implement different authentication methods and custom permissions.

Solution:
```python
# [Link]
from rest_framework import permissions

class IsAuthorOrReadOnly([Link]):
"""Custom permission to only allow authors to edit their posts."""

def has_object_permission(self, request, view, obj):


# Read permissions for any request
if [Link] in permissions.SAFE_METHODS:
return True

# Write permissions only to the author


return [Link] == [Link]

class IsPublishedOrAuthor([Link]):
"""Allow access to published posts or author only."""

def has_object_permission(self, request, view, obj):


# Allow access to published posts
if obj.is_published:
return True

# Allow access to author


return [Link] == [Link]

# [Link]
from rest_framework.authentication import TokenAuthentication,
SessionAuthentication
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from .permissions import IsAuthorOrReadOnly, IsPublishedOrAuthor

class PostViewSet([Link]):
queryset = [Link]()
serializer_class = PostSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]

def get_permissions(self):
"""Different permissions for different actions"""
if [Link] == 'create':
permission_classes = [IsAuthenticated]
elif [Link] in ['update', 'partial_update', 'destroy']:
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
else:
permission_classes = [IsAuthenticatedOrReadOnly]
return [permission() for permission in permission_classes]

# [Link] - Add token authentication


INSTALLED_APPS = [
# ... existing apps
'rest_framework.authtoken',
]

# [Link] - Add authentication endpoints


from rest_framework.[Link] import obtain_auth_token

urlpatterns = [
# ... existing urls
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
]

# Custom authentication view


from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status
from [Link] import authenticate
from rest_framework.[Link] import Token

@api_view(['POST'])
@permission_classes([AllowAny])
def login_view(request):
username = [Link]('username')
password = [Link]('password')

if username and password:


user = authenticate(username=username, password=password)
if user:
token, created = [Link].get_or_create(user=user)
return Response({
'token': [Link],
'user_id': [Link],
'username': [Link]
})
else:
return Response({
'error': 'Invalid credentials'
}, status=status.HTTP_401_UNAUTHORIZED)
else:
return Response({
'error': 'Please provide username and password'
}, status=status.HTTP_400_BAD_REQUEST)
```

Q6. PAGINATION AND FILTERING


----------------------------
Question: Implement pagination and filtering for the blog API.

Solution:
```python
# [Link]
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

class PostPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
page_query_param = 'page'
class CustomLimitOffsetPagination(LimitOffsetPagination):
default_limit = 10
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 100

# [Link]
import django_filters
from .models import Post

class PostFilter(django_filters.FilterSet):
title = django_filters.CharFilter(lookup_expr='icontains')
content = django_filters.CharFilter(lookup_expr='icontains')
author = django_filters.CharFilter(field_name='author__username',
lookup_expr='icontains')
created_after = django_filters.DateTimeFilter(field_name='created_at',
lookup_expr='gte')
created_before = django_filters.DateTimeFilter(field_name='created_at',
lookup_expr='lte')

class Meta:
model = Post
fields = ['is_published', 'author', 'category']

# [Link]
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
from .pagination import PostPagination
from .filters import PostFilter

class PostViewSet([Link]):
queryset = [Link]()
serializer_class = PostSerializer
pagination_class = PostPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_class = PostFilter
search_fields = ['title', 'content', 'author__username']
ordering_fields = ['created_at', 'title', 'author__username']
ordering = ['-created_at']

# [Link]
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.[Link]',
'PAGE_SIZE': 10,
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend',
'rest_framework.[Link]',
'rest_framework.[Link]',
],
}

# Example API calls:


# GET /api/posts/?page=2&page_size=5
# GET /api/posts/?search=django
# GET /api/posts/?ordering=-created_at
# GET /api/posts/?is_published=true&author=john
# GET /api/posts/?created_after=2024-01-01
```
Q7. API DOCUMENTATION
---------------------
Question: Set up API documentation using drf-spectacular or drf-yasg.

Solution:
```python
# [Link]
INSTALLED_APPS = [
# ... existing apps
'drf_spectacular',
]

REST_FRAMEWORK = {
# ... existing settings
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.[Link]',
}

SPECTACULAR_SETTINGS = {
'TITLE': 'Blog API',
'DESCRIPTION': 'A simple blog API built with Django REST Framework',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}

# [Link]
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView,
SpectacularRedocView

urlpatterns = [
# ... existing urls
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'),
name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'),
name='redoc'),
]

# [Link] - Add documentation


from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes

class PostViewSet([Link]):
@extend_schema(
summary="List posts",
description="Get a list of all published posts",
parameters=[
OpenApiParameter(
name="search",
type=[Link],
location=[Link],
description="Search in title and content"
),
OpenApiParameter(
name="is_published",
type=[Link],
location=[Link],
description="Filter by publication status"
),
],
responses={200: PostSerializer(many=True)}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)

@extend_schema(
summary="Create post",
description="Create a new blog post",
request=PostSerializer,
responses={201: PostSerializer}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)

@extend_schema(
summary="Publish post",
description="Publish an unpublished post",
responses={200: {"type": "object", "properties": {"status": {"type":
"string"}}}}
)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
post = self.get_object()
post.is_published = True
[Link]()
return Response({'status': 'post published'})
```

Q8. TESTING DRF APIs


--------------------
Question: Write tests for your DRF API endpoints.

Solution:
```python
# [Link]
from [Link] import TestCase
from [Link] import User
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from [Link] import reverse
from .models import Post, Category

class PostAPITestCase(APITestCase):
def setUp(self):
[Link] = APIClient()
[Link] = [Link].create_user(
username='testuser',
password='testpass123'
)
[Link] = [Link](name='Technology')
[Link] = [Link](
title='Test Post',
content='This is a test post content.',
author=[Link],
category=[Link],
is_published=True
)
def test_list_posts(self):
"""Test getting list of posts"""
url = reverse('post-list')
response = [Link](url)
[Link](response.status_code, status.HTTP_200_OK)
[Link](len([Link]['results']), 1)

def test_create_post_authenticated(self):
"""Test creating a post when authenticated"""
[Link].force_authenticate(user=[Link])
url = reverse('post-list')
data = {
'title': 'New Post',
'content': 'This is a new post content.',
'category': [Link],
'is_published': False
}
response = [Link](url, data)
[Link](response.status_code, status.HTTP_201_CREATED)
[Link]([Link](), 2)
[Link]([Link]['author'], [Link])

def test_create_post_unauthenticated(self):
"""Test creating a post when not authenticated"""
url = reverse('post-list')
data = {
'title': 'New Post',
'content': 'This is a new post content.',
'category': [Link]
}
response = [Link](url, data)
[Link](response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_update_post_author(self):
"""Test updating a post by its author"""
[Link].force_authenticate(user=[Link])
url = reverse('post-detail', args=[[Link]])
data = {'title': 'Updated Title'}
response = [Link](url, data)
[Link](response.status_code, status.HTTP_200_OK)
[Link]([Link]['title'], 'Updated Title')

def test_update_post_non_author(self):
"""Test updating a post by non-author"""
other_user = [Link].create_user(
username='otheruser',
password='testpass123'
)
[Link].force_authenticate(user=other_user)
url = reverse('post-detail', args=[[Link]])
data = {'title': 'Updated Title'}
response = [Link](url, data)
[Link](response.status_code, status.HTTP_403_FORBIDDEN)

def test_publish_action(self):
"""Test custom publish action"""
[Link].is_published = False
[Link]()
[Link].force_authenticate(user=[Link])
url = reverse('post-publish', args=[[Link]])
response = [Link](url)
[Link](response.status_code, status.HTTP_200_OK)

[Link].refresh_from_db()
[Link]([Link].is_published)

def test_search_posts(self):
"""Test searching posts"""
url = reverse('post-list')
response = [Link](url, {'search': 'test'})
[Link](response.status_code, status.HTTP_200_OK)
[Link](len([Link]['results']), 1)

def test_filter_posts(self):
"""Test filtering posts"""
url = reverse('post-list')
response = [Link](url, {'is_published': 'true'})
[Link](response.status_code, status.HTTP_200_OK)
[Link](len([Link]['results']), 1)

# Running tests
# python [Link] test [Link]
```

PRACTICAL EXERCISES:
====================

Exercise 1: Add Comments API


---------------------------
```python
# [Link]
class Comment([Link]):
post = [Link](Post, on_delete=[Link],
related_name='comments')
author_name = [Link](max_length=100)
content = [Link]()
created_at = [Link](auto_now_add=True)
is_approved = [Link](default=False)

# [Link]
class CommentSerializer([Link]):
class Meta:
model = Comment
fields = ['id', 'author_name', 'content', 'created_at', 'is_approved']
read_only_fields = ['created_at', 'is_approved']

# [Link]
class CommentViewSet([Link]):
queryset = [Link](is_approved=True)
serializer_class = CommentSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
```

Exercise 2: Add Rate Limiting


----------------------------
```python
# [Link]
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.[Link]',
'rest_framework.[Link]'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}
```

KEY CONCEPTS TO REMEMBER:


=========================

1. **REST Principles**: Stateless, client-server, cacheable, uniform interface


2. **HTTP Methods**: GET, POST, PUT, PATCH, DELETE
3. **Serializers**: Convert complex data types to/from JSON
4. **ViewSets**: Combine common operations into single class
5. **Routers**: Automatically generate URL patterns
6. **Authentication**: Token, Session, or custom authentication
7. **Permissions**: Control access to API endpoints
8. **Pagination**: Handle large datasets efficiently

BEST PRACTICES:
===============

1. Use appropriate HTTP status codes


2. Implement proper error handling
3. Use serializers for data validation
4. Implement authentication and permissions
5. Use ViewSets for CRUD operations
6. Add comprehensive API documentation
7. Write tests for all endpoints
8. Use pagination for large datasets

You might also like