Django HTTP Status Codes

Understanding server responses in Django applications

🌐 What are HTTP Status Codes?

HTTP status codes are three-digit numbers returned by servers to indicate the result of a request. Django uses these codes to communicate success, errors, or redirects to clients and browsers effectively.


# Basic Django view returning status codes
from django.http import HttpResponse

def my_view(request):
    return HttpResponse("Success!", status=200)
                                    

Response:

Status Code: 200 OK

Body: Success!

Status Code Categories

HTTP status codes are grouped into five categories, each representing different types of responses. Understanding these categories helps you handle requests appropriately and provide meaningful feedback to users and applications.

â„šī¸

1xx Informational

Request received, continuing process

100 Continue
101 Switching Protocols
✅

2xx Success

Request successfully received and processed

200 OK
201 Created
204 No Content
â†Ēī¸

3xx Redirection

Further action needed to complete request

301 Moved Permanently
302 Found
304 Not Modified
âš ī¸

4xx Client Errors

Request contains errors or is invalid

400 Bad Request
404 Not Found
403 Forbidden
❌

5xx Server Errors

Server failed to fulfill valid request

500 Internal Server Error
502 Bad Gateway
503 Service Unavailable

🔹 200 OK - Successful Request

The most common status code indicating the request was successful and the server returned the requested data.

from django.http import HttpResponse, JsonResponse

def success_view(request):
    # Return HTML response with 200
    return HttpResponse("Request successful!", status=200)

def api_success(request):
    # Return JSON response (200 is default)
    return JsonResponse({'message': 'Success', 'data': [1, 2, 3]})

def default_success(request):
    # 200 is default, no need to specify
    return HttpResponse("This returns 200 automatically")

When to Use:

  • GET request successfully retrieved data
  • POST request processed without creating resource
  • Any successful operation

🔹 201 Created - Resource Created

Indicates a new resource was successfully created, typically used after POST requests that create database records.

from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from .models import Article

@require_http_methods(["POST"])
def create_article(request):
    # Create new article
    article = Article.objects.create(
        title=request.POST.get('title'),
        content=request.POST.get('content')
    )
    
    return JsonResponse({
        'message': 'Article created successfully',
        'id': article.id
    }, status=201)

When to Use:

  • POST request created a new resource
  • User registration successful
  • New database record added

🔹 204 No Content - Success Without Body

Request succeeded but there's no content to return. Commonly used for DELETE operations or updates that don't need response data.

from django.http import HttpResponse
from django.views.decorators.http import require_http_methods
from .models import Article

@require_http_methods(["DELETE"])
def delete_article(request, article_id):
    try:
        article = Article.objects.get(id=article_id)
        article.delete()
        # Return 204 with no content
        return HttpResponse(status=204)
    except Article.DoesNotExist:
        return HttpResponse(status=404)

When to Use:

  • DELETE request successfully removed resource
  • PUT/PATCH update with no response needed
  • Action completed, no data to return

🔹 301 & 302 - Redirects

301 indicates permanent redirect, 302 indicates temporary redirect. Django provides shortcuts for both types of redirects.

from django.shortcuts import redirect
from django.http import HttpResponsePermanentRedirect

def temporary_redirect(request):
    # 302 Temporary redirect (default)
    return redirect('home')

def permanent_redirect(request):
    # 301 Permanent redirect
    return HttpResponsePermanentRedirect('/new-url/')

def conditional_redirect(request):
    if not request.user.is_authenticated:
        # Redirect to login
        return redirect('login')
    return redirect('dashboard')

When to Use:

  • 301: URL permanently changed, update bookmarks
  • 302: Temporary redirect, original URL still valid
  • After form submission (POST-Redirect-GET pattern)

🔹 400 Bad Request - Invalid Request

Client sent a malformed or invalid request. Use when request data is missing, invalid, or cannot be processed.

from django.http import JsonResponse, HttpResponseBadRequest

def create_user(request):
    if request.method != 'POST':
        return HttpResponseBadRequest("Only POST method allowed")
    
    username = request.POST.get('username')
    email = request.POST.get('email')
    
    # Validate required fields
    if not username or not email:
        return JsonResponse({
            'error': 'Username and email are required'
        }, status=400)
    
    # Validate email format
    if '@' not in email:
        return JsonResponse({
            'error': 'Invalid email format'
        }, status=400)
    
    # Process valid request
    return JsonResponse({'message': 'User created'}, status=201)

When to Use:

  • Missing required fields
  • Invalid data format
  • Validation errors

🔹 401 Unauthorized - Authentication Required

Request requires authentication. User must log in or provide valid credentials to access the resource.

from django.http import JsonResponse
from django.contrib.auth.decorators import login_required

@login_required
def protected_view(request):
    # Django automatically redirects to login if not authenticated
    return JsonResponse({'message': 'Protected data'})

def api_protected(request):
    # Manual authentication check for APIs
    if not request.user.is_authenticated:
        return JsonResponse({
            'error': 'Authentication required'
        }, status=401)
    
    return JsonResponse({'data': 'Secret information'})

When to Use:

  • User not logged in
  • Invalid or expired token
  • Missing authentication credentials

🔹 403 Forbidden - Access Denied

Server understood the request but refuses to authorize it. User is authenticated but lacks permission for the resource.

from django.http import HttpResponseForbidden, JsonResponse
from django.contrib.auth.decorators import user_passes_test

def is_admin(user):
    return user.is_staff

@user_passes_test(is_admin)
def admin_only_view(request):
    return JsonResponse({'message': 'Admin area'})

def check_permission(request, article_id):
    article = Article.objects.get(id=article_id)
    
    # Check if user owns the article
    if article.author != request.user:
        return JsonResponse({
            'error': 'You do not have permission to edit this article'
        }, status=403)
    
    # User has permission, proceed
    return JsonResponse({'message': 'Edit allowed'})

When to Use:

  • User authenticated but lacks permissions
  • Insufficient privileges for action
  • Access to resource denied by policy

🔹 404 Not Found - Resource Missing

The requested resource doesn't exist on the server. Most common error when URL or database record is not found.

from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from .models import Article

def article_detail(request, article_id):
    # Method 1: Using get_object_or_404 (recommended)
    article = get_object_or_404(Article, id=article_id)
    return JsonResponse({'title': article.title})

def article_detail_manual(request, article_id):
    # Method 2: Manual try-except
    try:
        article = Article.objects.get(id=article_id)
        return JsonResponse({'title': article.title})
    except Article.DoesNotExist:
        return JsonResponse({
            'error': 'Article not found'
        }, status=404)

def custom_404(request):
    # Method 3: Raise Http404
    raise Http404("Page does not exist")

When to Use:

  • Database record doesn't exist
  • Invalid URL or route
  • Resource has been deleted

🔹 405 Method Not Allowed - Wrong HTTP Method

Request method is not supported for the requested resource. For example, sending POST to a GET-only endpoint.

from django.views.decorators.http import require_http_methods
from django.http import JsonResponse

@require_http_methods(["GET"])
def get_only_view(request):
    # Only GET requests allowed
    return JsonResponse({'data': 'This is GET only'})

@require_http_methods(["POST", "PUT"])
def post_put_only(request):
    # Only POST and PUT allowed
    if request.method == 'POST':
        return JsonResponse({'message': 'Created'}, status=201)
    elif request.method == 'PUT':
        return JsonResponse({'message': 'Updated'})

def manual_method_check(request):
    if request.method != 'POST':
        return JsonResponse({
            'error': 'Only POST method is allowed'
        }, status=405)
    return JsonResponse({'message': 'Success'})

When to Use:

  • GET request to POST-only endpoint
  • POST request to GET-only endpoint
  • Unsupported HTTP method used

🔹 500 Internal Server Error - Server Failure

Server encountered an unexpected condition. Usually indicates a bug or unhandled exception in your Django code.

from django.http import JsonResponse
import logging

logger = logging.getLogger(__name__)

def risky_operation(request):
    try:
        # Potentially dangerous operation
        result = perform_complex_calculation()
        return JsonResponse({'result': result})
    
    except Exception as e:
        # Log the error for debugging
        logger.error(f"Error in risky_operation: {str(e)}")
        
        # Return 500 error
        return JsonResponse({
            'error': 'Internal server error occurred'
        }, status=500)

def divide_numbers(request):
    try:
        a = int(request.GET.get('a'))
        b = int(request.GET.get('b'))
        result = a / b
        return JsonResponse({'result': result})
    except ZeroDivisionError:
        return JsonResponse({'error': 'Cannot divide by zero'}, status=400)
    except Exception as e:
        logger.error(f"Unexpected error: {str(e)}")
        return JsonResponse({'error': 'Server error'}, status=500)

When to Use:

  • Unhandled exceptions in code
  • Database connection failures
  • Unexpected server-side errors

🔹 Using Django's Built-in Status Code Classes

Django provides convenient classes for common HTTP responses, making your code more readable and maintainable.

from django.http import (
    HttpResponse,
    HttpResponseRedirect,
    HttpResponsePermanentRedirect,
    HttpResponseNotFound,
    HttpResponseBadRequest,
    HttpResponseForbidden,
    HttpResponseNotAllowed,
    HttpResponseServerError,
    JsonResponse
)

def example_responses(request):
    # Various response types
    
    # 200 OK
    response_ok = HttpResponse("Success")
    
    # 400 Bad Request
    response_bad = HttpResponseBadRequest("Invalid data")
    
    # 403 Forbidden
    response_forbidden = HttpResponseForbidden("Access denied")
    
    # 404 Not Found
    response_not_found = HttpResponseNotFound("Page not found")
    
    # 405 Method Not Allowed
    response_not_allowed = HttpResponseNotAllowed(['GET', 'POST'])
    
    # 500 Server Error
    response_error = HttpResponseServerError("Server error")
    
    # 302 Redirect
    response_redirect = HttpResponseRedirect('/new-location/')
    
    # 301 Permanent Redirect
    response_permanent = HttpResponsePermanentRedirect('/permanent-location/')
    
    return response_ok

Benefits:

  • More readable code
  • Type safety and IDE autocomplete
  • Consistent status code usage
  • Less prone to typos

🔹 REST API Status Code Best Practices

Follow these conventions when building REST APIs to ensure consistent and predictable behavior for API consumers.

from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from .models import Product

# GET - Retrieve resources (200, 404)
@require_http_methods(["GET"])
def get_product(request, product_id):
    try:
        product = Product.objects.get(id=product_id)
        return JsonResponse({'id': product.id, 'name': product.name})
    except Product.DoesNotExist:
        return JsonResponse({'error': 'Product not found'}, status=404)

# POST - Create resource (201, 400)
@require_http_methods(["POST"])
def create_product(request):
    name = request.POST.get('name')
    if not name:
        return JsonResponse({'error': 'Name required'}, status=400)
    
    product = Product.objects.create(name=name)
    return JsonResponse({'id': product.id, 'name': product.name}, status=201)

# PUT - Update resource (200, 404)
@require_http_methods(["PUT"])
def update_product(request, product_id):
    try:
        product = Product.objects.get(id=product_id)
        product.name = request.POST.get('name')
        product.save()
        return JsonResponse({'id': product.id, 'name': product.name})
    except Product.DoesNotExist:
        return JsonResponse({'error': 'Product not found'}, status=404)

# DELETE - Remove resource (204, 404)
@require_http_methods(["DELETE"])
def delete_product(request, product_id):
    try:
        product = Product.objects.get(id=product_id)
        product.delete()
        return HttpResponse(status=204)
    except Product.DoesNotExist:
        return JsonResponse({'error': 'Product not found'}, status=404)

REST API Status Code Guide:

  • GET: 200 (success), 404 (not found)
  • POST: 201 (created), 400 (bad request)
  • PUT/PATCH: 200 (updated), 404 (not found)
  • DELETE: 204 (deleted), 404 (not found)

🧠 Test Your Knowledge

Which status code should you return after successfully creating a new resource?