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)