Django views must return HttpResponse objects or subclasses. Understanding different response types and rendering techniques is crucial for building flexible, maintainable web applications.
from django.http import (
HttpResponse, JsonResponse, HttpResponseRedirect,
HttpResponsePermanentRedirect, HttpResponseNotFound,
HttpResponseBadRequest, HttpResponseForbidden,
HttpResponseServerError, StreamingHttpResponse,
FileResponse, HttpResponseNotModified
)
from django.shortcuts import render, redirect
import json
import csv
import io
def basic_html_response(request):
"""Return basic HTML response"""
html_content = """
<!DOCTYPE html>
<html>
<head><title>Basic Response</title></head>
<body>
<h1>Hello, World!</h1>
<p>Current time: {}</p>
</body>
</html>
""".format(timezone.now())
return HttpResponse(html_content, content_type='text/html')
def plain_text_response(request):
"""Return plain text response"""
content = "This is a plain text response.\nLine 2\nLine 3"
return HttpResponse(content, content_type='text/plain')
def json_response_basic(request):
"""Return JSON response"""
data = {
'message': 'Hello, JSON!',
'timestamp': timezone.now().isoformat(),
'user_id': request.user.id if request.user.is_authenticated else None,
'items': ['item1', 'item2', 'item3']
}
return JsonResponse(data)
def json_list_response(request):
"""Return JSON list (requires safe=False)"""
posts = Post.objects.values('id', 'title', 'created_at')
posts_list = list(posts)
# Convert datetime objects to strings
for post in posts_list:
post['created_at'] = post['created_at'].isoformat()
return JsonResponse(posts_list, safe=False)
def custom_status_responses(request):
"""Examples of different HTTP status codes"""
action = request.GET.get('action')
if action == 'not_found':
return HttpResponseNotFound('<h1>Page Not Found</h1>')
elif action == 'bad_request':
return HttpResponseBadRequest('<h1>Bad Request</h1>')
elif action == 'forbidden':
return HttpResponseForbidden('<h1>Access Denied</h1>')
elif action == 'server_error':
return HttpResponseServerError('<h1>Internal Server Error</h1>')
elif action == 'not_modified':
return HttpResponseNotModified()
else:
return HttpResponse('<h1>Choose an action</h1>')
from django.shortcuts import render, render_to_response
from django.template import loader
from django.template.response import TemplateResponse
from django.http import HttpResponse
def render_with_shortcut(request):
"""Using render() shortcut function"""
posts = Post.objects.select_related('author').all()
categories = Category.objects.all()
context = {
'posts': posts,
'categories': categories,
'page_title': 'Blog Posts',
'current_user': request.user,
}
return render(request, 'blog/post_list.html', context)
def render_with_loader(request):
"""Using template loader directly"""
template = loader.get_template('blog/post_list.html')
context = {
'posts': Post.objects.all(),
'categories': Category.objects.all(),
}
html = template.render(context, request)
return HttpResponse(html)
def template_response_view(request):
"""Using TemplateResponse for lazy rendering"""
context = {
'posts': Post.objects.all(),
'categories': Category.objects.all(),
}
# TemplateResponse allows middleware to modify context before rendering
return TemplateResponse(request, 'blog/post_list.html', context)
def conditional_template_rendering(request):
"""Choose template based on conditions"""
user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
# Mobile detection (simplified)
is_mobile = any(device in user_agent for device in ['mobile', 'android', 'iphone'])
template_name = 'blog/post_list_mobile.html' if is_mobile else 'blog/post_list.html'
context = {
'posts': Post.objects.all(),
'is_mobile': is_mobile,
}
return render(request, template_name, context)
def render_with_custom_context_processor(request):
"""Demonstrate custom context processing"""
from django.template.context import RequestContext
# Custom context data
extra_context = {
'site_name': 'My Blog',
'current_year': timezone.now().year,
'debug_info': {
'request_path': request.path,
'request_method': request.method,
'user_authenticated': request.user.is_authenticated,
}
}
context = RequestContext(request, extra_context)
template = loader.get_template('blog/custom_context.html')
return HttpResponse(template.render(context))
from django.http import HttpResponse
import mimetypes
import os
def custom_content_type_response(request):
"""Response with custom content type and headers"""
content = "Custom content with special headers"
response = HttpResponse(content, content_type='text/plain; charset=utf-8')
# Add custom headers
response['X-Custom-Header'] = 'Custom Value'
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
return response
def xml_response(request):
"""Return XML response"""
posts = Post.objects.all()[:10]
xml_content = '<?xml version="1.0" encoding="UTF-8"?>\n<posts>\n'
for post in posts:
xml_content += f'''
<post id="{post.id}">
<title><![CDATA[{post.title}]]></title>
<author>{post.author.username}</author>
<created_at>{post.created_at.isoformat()}</created_at>
</post>
'''
xml_content += '</posts>'
return HttpResponse(xml_content, content_type='application/xml')
def rss_feed_response(request):
"""Generate RSS feed"""
posts = Post.objects.filter(status='published').order_by('-created_at')[:20]
rss_content = '''<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>My Blog</title>
<link>https://example.com</link>
<description>Latest blog posts</description>
'''
for post in posts:
rss_content += f'''
<item>
<title><![CDATA[{post.title}]]></title>
<link>https://example.com{post.get_absolute_url()}</link>
<description><![CDATA[{post.excerpt}]]></description>
<pubDate>{post.created_at.strftime('%a, %d %b %Y %H:%M:%S %z')}</pubDate>
<guid>https://example.com{post.get_absolute_url()}</guid>
</item>
'''
rss_content += '''
</channel>
</rss>
'''
return HttpResponse(rss_content, content_type='application/rss+xml')
def csv_export_response(request):
"""Export data as CSV"""
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="posts_export.csv"'
writer = csv.writer(response)
# Write header
writer.writerow(['ID', 'Title', 'Author', 'Category', 'Created', 'Status'])
# Write data
posts = Post.objects.select_related('author', 'category').all()
for post in posts:
writer.writerow([
post.id,
post.title,
post.author.username,
post.category.name if post.category else '',
post.created_at.strftime('%Y-%m-%d %H:%M:%S'),
post.status
])
return response
from django.http import FileResponse, StreamingHttpResponse
from django.core.files.storage import default_storage
from wsgiref.util import FileWrapper
import os
import zipfile
import tempfile
def serve_file_download(request, file_id):
"""Serve file for download"""
document = get_object_or_404(Document, id=file_id)
# Check permissions
if not can_access_file(request.user, document):
return HttpResponseForbidden("Access denied")
# Get file path
file_path = document.file.path
if not os.path.exists(file_path):
return HttpResponseNotFound("File not found")
# Determine content type
content_type, _ = mimetypes.guess_type(file_path)
if not content_type:
content_type = 'application/octet-stream'
# Create file response
response = FileResponse(
open(file_path, 'rb'),
content_type=content_type,
as_attachment=True,
filename=document.original_filename
)
return response
def serve_image_with_processing(request, image_id):
"""Serve processed image"""
image = get_object_or_404(UserImage, id=image_id)
# Get processing parameters
width = request.GET.get('width', type=int)
height = request.GET.get('height', type=int)
quality = min(int(request.GET.get('quality', 85)), 100)
# Process image if parameters provided
if width or height:
from PIL import Image
# Open original image
pil_image = Image.open(image.file.path)
# Resize if needed
if width and height:
pil_image = pil_image.resize((width, height), Image.Resampling.LANCZOS)
elif width:
ratio = width / pil_image.width
new_height = int(pil_image.height * ratio)
pil_image = pil_image.resize((width, new_height), Image.Resampling.LANCZOS)
elif height:
ratio = height / pil_image.height
new_width = int(pil_image.width * ratio)
pil_image = pil_image.resize((new_width, height), Image.Resampling.LANCZOS)
# Save to BytesIO
output = io.BytesIO()
pil_image.save(output, format='JPEG', quality=quality, optimize=True)
output.seek(0)
response = HttpResponse(output.getvalue(), content_type='image/jpeg')
response['Content-Length'] = len(output.getvalue())
return response
# Serve original image
return FileResponse(image.file.open('rb'), content_type='image/jpeg')
def create_zip_download(request):
"""Create and serve ZIP file"""
# Get files to include
documents = Document.objects.filter(user=request.user)
# Create temporary file
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
try:
with zipfile.ZipFile(temp_file, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for document in documents:
if os.path.exists(document.file.path):
# Add file to ZIP with original filename
zip_file.write(
document.file.path,
document.original_filename
)
temp_file.close()
# Serve ZIP file
response = FileResponse(
open(temp_file.name, 'rb'),
content_type='application/zip',
as_attachment=True,
filename='my_documents.zip'
)
# Clean up temp file after response
def cleanup():
try:
os.unlink(temp_file.name)
except OSError:
pass
response.close = cleanup
return response
except Exception as e:
# Clean up on error
try:
os.unlink(temp_file.name)
except OSError:
pass
return HttpResponseServerError(f"Error creating ZIP file: {str(e)}")
def streaming_csv_response(request):
"""Stream large CSV file"""
def generate_csv_rows():
"""Generator for CSV rows"""
yield 'ID,Title,Author,Created\n'
# Stream posts in batches
batch_size = 1000
offset = 0
while True:
posts = Post.objects.select_related('author')[offset:offset + batch_size]
if not posts:
break
for post in posts:
yield f'{post.id},"{post.title}",{post.author.username},{post.created_at.isoformat()}\n'
offset += batch_size
response = StreamingHttpResponse(
generate_csv_rows(),
content_type='text/csv'
)
response['Content-Disposition'] = 'attachment; filename="all_posts.csv"'
return response
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import json
class APIResponse:
"""Standardized API response format"""
@staticmethod
def success(data=None, message="Success", status=200):
response_data = {
'success': True,
'message': message,
'data': data
}
return JsonResponse(response_data, status=status)
@staticmethod
def error(message="Error", errors=None, status=400):
response_data = {
'success': False,
'message': message,
'errors': errors or {}
}
return JsonResponse(response_data, status=status)
@staticmethod
def paginated(data, page, total_pages, total_count):
response_data = {
'success': True,
'data': data,
'pagination': {
'current_page': page,
'total_pages': total_pages,
'total_count': total_count,
'has_next': page < total_pages,
'has_previous': page > 1
}
}
return JsonResponse(response_data)
@csrf_exempt
def api_post_list(request):
"""API endpoint for post list"""
if request.method == 'GET':
# Get query parameters
page = int(request.GET.get('page', 1))
per_page = min(int(request.GET.get('per_page', 10)), 100) # Max 100 per page
search = request.GET.get('search', '')
category_id = request.GET.get('category')
# Build queryset
posts = Post.objects.filter(status='published')
if search:
posts = posts.filter(title__icontains=search)
if category_id:
posts = posts.filter(category_id=category_id)
# Pagination
from django.core.paginator import Paginator
paginator = Paginator(posts, per_page)
page_obj = paginator.get_page(page)
# Serialize data
posts_data = []
for post in page_obj.object_list:
posts_data.append({
'id': post.id,
'title': post.title,
'slug': post.slug,
'excerpt': post.excerpt,
'author': {
'id': post.author.id,
'username': post.author.username,
'full_name': post.author.get_full_name()
},
'category': {
'id': post.category.id,
'name': post.category.name,
'slug': post.category.slug
} if post.category else None,
'created_at': post.created_at.isoformat(),
'url': post.get_absolute_url()
})
return APIResponse.paginated(
posts_data,
page_obj.number,
paginator.num_pages,
paginator.count
)
elif request.method == 'POST':
try:
data = json.loads(request.body)
# Validate required fields
required_fields = ['title', 'content']
missing_fields = [field for field in required_fields if not data.get(field)]
if missing_fields:
return APIResponse.error(
"Missing required fields",
{'missing_fields': missing_fields},
status=400
)
# Create post
post = Post.objects.create(
title=data['title'],
content=data['content'],
author=request.user,
status='published'
)
post_data = {
'id': post.id,
'title': post.title,
'slug': post.slug,
'created_at': post.created_at.isoformat(),
'url': post.get_absolute_url()
}
return APIResponse.success(post_data, "Post created successfully", status=201)
except json.JSONDecodeError:
return APIResponse.error("Invalid JSON", status=400)
except Exception as e:
return APIResponse.error(f"Error creating post: {str(e)}", status=500)
else:
return APIResponse.error("Method not allowed", status=405)
def api_post_detail(request, pk):
"""API endpoint for post detail"""
try:
post = Post.objects.select_related('author', 'category').get(pk=pk, status='published')
except Post.DoesNotExist:
return APIResponse.error("Post not found", status=404)
if request.method == 'GET':
post_data = {
'id': post.id,
'title': post.title,
'slug': post.slug,
'content': post.content,
'excerpt': post.excerpt,
'author': {
'id': post.author.id,
'username': post.author.username,
'full_name': post.author.get_full_name(),
'email': post.author.email
},
'category': {
'id': post.category.id,
'name': post.category.name,
'slug': post.category.slug
} if post.category else None,
'tags': [tag.name for tag in post.tags.all()],
'created_at': post.created_at.isoformat(),
'updated_at': post.updated_at.isoformat(),
'view_count': post.views,
'url': post.get_absolute_url()
}
return APIResponse.success(post_data)
elif request.method == 'PUT':
# Update post (simplified)
try:
data = json.loads(request.body)
# Check permissions
if post.author != request.user and not request.user.is_staff:
return APIResponse.error("Permission denied", status=403)
# Update fields
if 'title' in data:
post.title = data['title']
if 'content' in data:
post.content = data['content']
post.save()
return APIResponse.success({"id": post.id}, "Post updated successfully")
except json.JSONDecodeError:
return APIResponse.error("Invalid JSON", status=400)
elif request.method == 'DELETE':
# Check permissions
if post.author != request.user and not request.user.is_staff:
return APIResponse.error("Permission denied", status=403)
post.delete()
return APIResponse.success(None, "Post deleted successfully", status=204)
else:
return APIResponse.error("Method not allowed", status=405)
def content_negotiation_view(request, pk):
"""Handle different content types based on Accept header"""
post = get_object_or_404(Post, pk=pk, status='published')
# Get Accept header
accept_header = request.META.get('HTTP_ACCEPT', 'text/html')
# JSON response
if 'application/json' in accept_header:
post_data = {
'id': post.id,
'title': post.title,
'content': post.content,
'author': post.author.username,
'created_at': post.created_at.isoformat()
}
return JsonResponse(post_data)
# XML response
elif 'application/xml' in accept_header or 'text/xml' in accept_header:
xml_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<post id="{post.id}">
<title><![CDATA[{post.title}]]></title>
<content><![CDATA[{post.content}]]></content>
<author>{post.author.username}</author>
<created_at>{post.created_at.isoformat()}</created_at>
</post>'''
return HttpResponse(xml_content, content_type='application/xml')
# Plain text response
elif 'text/plain' in accept_header:
text_content = f"""
Title: {post.title}
Author: {post.author.username}
Created: {post.created_at}
{post.content}
"""
return HttpResponse(text_content, content_type='text/plain')
# Default HTML response
else:
return render(request, 'blog/post_detail.html', {'post': post})
def format_specific_view(request, pk, format=None):
"""Handle format-specific URLs (e.g., /posts/1.json)"""
post = get_object_or_404(Post, pk=pk, status='published')
if format == 'json':
post_data = {
'id': post.id,
'title': post.title,
'content': post.content,
'author': post.author.username,
'created_at': post.created_at.isoformat()
}
return JsonResponse(post_data)
elif format == 'xml':
xml_content = f'''<?xml version="1.0" encoding="UTF-8"?>
<post id="{post.id}">
<title><![CDATA[{post.title}]]></title>
<content><![CDATA[{post.content}]]></content>
<author>{post.author.username}</author>
<created_at>{post.created_at.isoformat()}</created_at>
</post>'''
return HttpResponse(xml_content, content_type='application/xml')
elif format == 'txt':
text_content = f"""
Title: {post.title}
Author: {post.author.username}
Created: {post.created_at}
{post.content}
"""
return HttpResponse(text_content, content_type='text/plain')
else:
# Default to HTML
return render(request, 'blog/post_detail.html', {'post': post})
from django.views.decorators.cache import cache_page, cache_control
from django.views.decorators.vary import vary_on_headers
from django.utils.cache import patch_cache_control, patch_vary_headers
from django.core.cache import cache
import hashlib
@cache_page(60 * 15) # Cache for 15 minutes
def cached_post_list(request):
"""Cached post list view"""
posts = Post.objects.select_related('author', 'category').filter(status='published')
context = {
'posts': posts,
'cache_timestamp': timezone.now()
}
return render(request, 'blog/post_list.html', context)
@vary_on_headers('User-Agent', 'Accept-Language')
def browser_specific_response(request):
"""Response that varies by browser and language"""
user_agent = request.META.get('HTTP_USER_AGENT', '')
accept_language = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
# Customize response based on browser/language
context = {
'user_agent': user_agent,
'language': accept_language,
'posts': Post.objects.all()
}
response = render(request, 'blog/browser_specific.html', context)
# Set cache control headers
patch_cache_control(response, max_age=3600, must_revalidate=True)
return response
def conditional_response(request, pk):
"""Response with conditional headers"""
post = get_object_or_404(Post, pk=pk)
# Generate ETag
etag_content = f"{post.id}-{post.updated_at.timestamp()}"
etag = hashlib.md5(etag_content.encode()).hexdigest()
# Check If-None-Match header
if request.META.get('HTTP_IF_NONE_MATCH') == etag:
return HttpResponseNotModified()
# Check If-Modified-Since header
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE')
if if_modified_since:
from django.utils.http import parse_http_date
try:
if_modified_timestamp = parse_http_date(if_modified_since)
if post.updated_at.timestamp() <= if_modified_timestamp:
return HttpResponseNotModified()
except ValueError:
pass
# Generate response
response = render(request, 'blog/post_detail.html', {'post': post})
# Set caching headers
response['ETag'] = etag
response['Last-Modified'] = post.updated_at.strftime('%a, %d %b %Y %H:%M:%S GMT')
response['Cache-Control'] = 'max-age=3600, must-revalidate'
return response
Understanding response types and rendering techniques enables you to build flexible Django applications that can serve different content types, handle various client requirements, and optimize performance through proper caching and conditional responses.
View Decorators
View decorators are a powerful way to modify view behavior without changing the view function itself. Django provides built-in decorators for common tasks and supports custom decorators for specialized functionality.
Redirects
Redirects are essential for guiding users through your application, handling URL changes, and implementing proper navigation flows. Django provides multiple ways to handle redirects with different HTTP status codes and use cases.