Django provides several base classes that form the foundation of all class-based views. Understanding these base classes is essential for effectively using and customizing CBVs in your applications.
from django.views.generic import View
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib import messages
class BaseView(View):
"""The most basic class-based view"""
def get(self, request, *args, **kwargs):
"""Handle GET requests"""
return HttpResponse("Hello from BaseView GET method")
def post(self, request, *args, **kwargs):
"""Handle POST requests"""
return HttpResponse("Hello from BaseView POST method")
def put(self, request, *args, **kwargs):
"""Handle PUT requests"""
return HttpResponse("Hello from BaseView PUT method")
def delete(self, request, *args, **kwargs):
"""Handle DELETE requests"""
return HttpResponse("Hello from BaseView DELETE method")
# Advanced View with custom dispatch logic
class CustomDispatchView(View):
"""View with custom dispatch behavior"""
def dispatch(self, request, *args, **kwargs):
"""Override dispatch for custom routing logic"""
# Log all requests
print(f"Request: {request.method} {request.path}")
# Custom authentication check
if request.method in ['POST', 'PUT', 'DELETE']:
if not request.user.is_authenticated:
return redirect('accounts:login')
# Rate limiting check
if self.is_rate_limited(request):
return JsonResponse({
'error': 'Rate limit exceeded'
}, status=429)
return super().dispatch(request, *args, **kwargs)
def is_rate_limited(self, request):
"""Check if request is rate limited"""
# Implement rate limiting logic
return False
def get(self, request, *args, **kwargs):
return JsonResponse({'message': 'GET request processed'})
def post(self, request, *args, **kwargs):
return JsonResponse({'message': 'POST request processed'})
# View with setup method customization
class SetupView(View):
"""View demonstrating setup method usage"""
def setup(self, request, *args, **kwargs):
"""Initialize view instance"""
super().setup(request, *args, **kwargs)
# Custom initialization
self.user_agent = request.META.get('HTTP_USER_AGENT', '')
self.is_mobile = self.detect_mobile()
self.client_ip = self.get_client_ip()
def detect_mobile(self):
"""Detect if request is from mobile device"""
mobile_indicators = ['mobile', 'android', 'iphone', 'ipad']
return any(indicator in self.user_agent.lower()
for indicator in mobile_indicators)
def get_client_ip(self):
"""Get client IP address"""
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return x_forwarded_for.split(',')[0]
return self.request.META.get('REMOTE_ADDR')
def get(self, request, *args, **kwargs):
context = {
'is_mobile': self.is_mobile,
'user_agent': self.user_agent,
'client_ip': self.client_ip,
}
return render(request, 'base/setup_view.html', context)
class RESTfulView(View):
"""RESTful API view handling all HTTP methods"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options']
def get(self, request, *args, **kwargs):
"""Retrieve resource(s)"""
resource_id = kwargs.get('pk')
if resource_id:
# Single resource
try:
resource = MyModel.objects.get(pk=resource_id)
data = self.serialize_resource(resource)
return JsonResponse(data)
except MyModel.DoesNotExist:
return JsonResponse({'error': 'Resource not found'}, status=404)
else:
# List resources
resources = MyModel.objects.all()
data = {
'resources': [self.serialize_resource(r) for r in resources]
}
return JsonResponse(data)
def post(self, request, *args, **kwargs):
"""Create new resource"""
try:
data = self.parse_request_data()
resource = MyModel.objects.create(**data)
return JsonResponse(
self.serialize_resource(resource),
status=201
)
except Exception as e:
return JsonResponse({'error': str(e)}, status=400)
def put(self, request, *args, **kwargs):
"""Update entire resource"""
resource_id = kwargs.get('pk')
if not resource_id:
return JsonResponse({'error': 'Resource ID required'}, status=400)
try:
resource = MyModel.objects.get(pk=resource_id)
data = self.parse_request_data()
# Update all fields
for field, value in data.items():
setattr(resource, field, value)
resource.save()
return JsonResponse(self.serialize_resource(resource))
except MyModel.DoesNotExist:
return JsonResponse({'error': 'Resource not found'}, status=404)
def patch(self, request, *args, **kwargs):
"""Partially update resource"""
resource_id = kwargs.get('pk')
if not resource_id:
return JsonResponse({'error': 'Resource ID required'}, status=400)
try:
resource = MyModel.objects.get(pk=resource_id)
data = self.parse_request_data()
# Update only provided fields
updated_fields = []
for field, value in data.items():
if hasattr(resource, field):
setattr(resource, field, value)
updated_fields.append(field)
if updated_fields:
resource.save(update_fields=updated_fields)
return JsonResponse({
'resource': self.serialize_resource(resource),
'updated_fields': updated_fields
})
except MyModel.DoesNotExist:
return JsonResponse({'error': 'Resource not found'}, status=404)
def delete(self, request, *args, **kwargs):
"""Delete resource"""
resource_id = kwargs.get('pk')
if not resource_id:
return JsonResponse({'error': 'Resource ID required'}, status=400)
try:
resource = MyModel.objects.get(pk=resource_id)
resource.delete()
return JsonResponse({'message': 'Resource deleted'}, status=204)
except MyModel.DoesNotExist:
return JsonResponse({'error': 'Resource not found'}, status=404)
def options(self, request, *args, **kwargs):
"""Return allowed methods"""
response = HttpResponse()
response['Allow'] = ', '.join(self.http_method_names).upper()
response['Access-Control-Allow-Methods'] = response['Allow']
return response
def parse_request_data(self):
"""Parse JSON request data"""
import json
try:
return json.loads(self.request.body)
except json.JSONDecodeError:
raise ValueError("Invalid JSON data")
def serialize_resource(self, resource):
"""Serialize model instance to dict"""
return {
'id': resource.id,
'name': resource.name,
'created_at': resource.created_at.isoformat(),
# Add other fields as needed
}
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
class HomeView(TemplateView):
"""Simple template view"""
template_name = 'home.html'
def get_context_data(self, **kwargs):
"""Add context data to template"""
context = super().get_context_data(**kwargs)
context.update({
'page_title': 'Welcome to Our Site',
'featured_posts': Post.objects.filter(
featured=True,
status='published'
)[:3],
'total_users': User.objects.count(),
'latest_news': News.objects.filter(
published=True
).order_by('-created_at')[:5],
})
return context
class AboutView(TemplateView):
"""About page with dynamic content"""
template_name = 'about.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add team members
context['team_members'] = TeamMember.objects.filter(
active=True
).order_by('order')
# Add company stats
context['stats'] = {
'years_in_business': self.calculate_years_in_business(),
'projects_completed': Project.objects.filter(status='completed').count(),
'happy_clients': Client.objects.filter(satisfied=True).count(),
}
return context
def calculate_years_in_business(self):
"""Calculate years in business"""
from datetime import date
founding_year = 2010 # Your company's founding year
return date.today().year - founding_year
# Dynamic template selection
class DynamicTemplateView(TemplateView):
"""View with dynamic template selection"""
def get_template_names(self):
"""Select template based on conditions"""
# Different template for mobile users
user_agent = self.request.META.get('HTTP_USER_AGENT', '').lower()
if any(device in user_agent for device in ['mobile', 'android', 'iphone']):
return ['mobile/home.html']
# Different template for staff users
if self.request.user.is_staff:
return ['admin/home.html']
# Different template based on URL parameter
theme = self.request.GET.get('theme')
if theme in ['dark', 'light']:
return [f'themes/{theme}/home.html']
# Default template
return ['home.html']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add theme information
context['current_theme'] = self.request.GET.get('theme', 'default')
context['available_themes'] = ['default', 'dark', 'light']
return context
# TemplateView with form handling
class ContactView(TemplateView):
"""Contact page with form handling"""
template_name = 'contact.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add contact form if not already in context
if 'form' not in context:
context['form'] = ContactForm()
# Add contact information
context.update({
'office_locations': OfficeLocation.objects.filter(active=True),
'contact_methods': ContactMethod.objects.filter(active=True),
})
return context
def post(self, request, *args, **kwargs):
"""Handle form submission"""
form = ContactForm(request.POST)
if form.is_valid():
# Process form
self.send_contact_email(form.cleaned_data)
messages.success(request, 'Thank you for your message!')
return redirect('contact')
# Form invalid, redisplay with errors
context = self.get_context_data(form=form)
return self.render_to_response(context)
def send_contact_email(self, form_data):
"""Send contact form email"""
from django.core.mail import send_mail
subject = f"Contact Form: {form_data['subject']}"
message = f"""
Name: {form_data['name']}
Email: {form_data['email']}
Message: {form_data['message']}
"""
send_mail(
subject,
message,
'noreply@example.com',
['contact@example.com'],
fail_silently=False,
)
class DashboardView(LoginRequiredMixin, TemplateView):
"""User dashboard with personalized content"""
template_name = 'dashboard/main.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
# User-specific data
context.update({
'user_posts': Post.objects.filter(author=user)[:5],
'user_comments': Comment.objects.filter(author=user)[:10],
'notifications': Notification.objects.filter(
user=user,
read=False
)[:5],
'user_stats': self.get_user_stats(user),
})
# Recent activity
context['recent_activity'] = self.get_recent_activity(user)
return context
def get_user_stats(self, user):
"""Calculate user statistics"""
return {
'total_posts': Post.objects.filter(author=user).count(),
'total_comments': Comment.objects.filter(author=user).count(),
'posts_this_month': Post.objects.filter(
author=user,
created_at__month=timezone.now().month
).count(),
'profile_completion': self.calculate_profile_completion(user),
}
def calculate_profile_completion(self, user):
"""Calculate profile completion percentage"""
fields_to_check = ['first_name', 'last_name', 'email']
completed_fields = sum(1 for field in fields_to_check
if getattr(user, field))
# Check profile fields if profile exists
if hasattr(user, 'profile'):
profile_fields = ['bio', 'avatar', 'website']
completed_fields += sum(1 for field in profile_fields
if getattr(user.profile, field))
total_fields = len(fields_to_check) + len(profile_fields)
else:
total_fields = len(fields_to_check)
return int((completed_fields / total_fields) * 100)
def get_recent_activity(self, user):
"""Get user's recent activity"""
activities = []
# Recent posts
recent_posts = Post.objects.filter(author=user).order_by('-created_at')[:3]
for post in recent_posts:
activities.append({
'type': 'post',
'title': f'Published "{post.title}"',
'date': post.created_at,
'url': post.get_absolute_url(),
})
# Recent comments
recent_comments = Comment.objects.filter(author=user).order_by('-created_at')[:3]
for comment in recent_comments:
activities.append({
'type': 'comment',
'title': f'Commented on "{comment.post.title}"',
'date': comment.created_at,
'url': comment.post.get_absolute_url(),
})
# Sort by date
activities.sort(key=lambda x: x['date'], reverse=True)
return activities[:10]
class ReportView(TemplateView):
"""Generate and display reports"""
template_name = 'reports/report.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Get report parameters
report_type = self.kwargs.get('report_type', 'summary')
date_from = self.request.GET.get('date_from')
date_to = self.request.GET.get('date_to')
# Generate report data
context.update({
'report_type': report_type,
'report_data': self.generate_report_data(report_type, date_from, date_to),
'date_from': date_from,
'date_to': date_to,
'available_reports': self.get_available_reports(),
})
return context
def generate_report_data(self, report_type, date_from, date_to):
"""Generate report data based on type and date range"""
queryset = Post.objects.all()
# Apply date filters
if date_from:
queryset = queryset.filter(created_at__gte=date_from)
if date_to:
queryset = queryset.filter(created_at__lte=date_to)
if report_type == 'summary':
return {
'total_posts': queryset.count(),
'published_posts': queryset.filter(status='published').count(),
'draft_posts': queryset.filter(status='draft').count(),
'posts_by_author': queryset.values('author__username').annotate(
count=Count('id')
).order_by('-count'),
}
elif report_type == 'engagement':
return {
'most_viewed': queryset.order_by('-views')[:10],
'most_commented': queryset.annotate(
comment_count=Count('comments')
).order_by('-comment_count')[:10],
}
return {}
def get_available_reports(self):
"""Get list of available report types"""
return [
{'key': 'summary', 'name': 'Summary Report'},
{'key': 'engagement', 'name': 'Engagement Report'},
{'key': 'analytics', 'name': 'Analytics Report'},
]
from django.views.generic import RedirectView
from django.urls import reverse_lazy
class HomeRedirectView(RedirectView):
"""Redirect to home page"""
url = '/home/'
permanent = True # 301 redirect
class DynamicRedirectView(RedirectView):
"""Dynamic redirect based on conditions"""
def get_redirect_url(self, *args, **kwargs):
"""Determine redirect URL dynamically"""
user = self.request.user
if not user.is_authenticated:
return reverse_lazy('accounts:login')
if user.is_staff:
return reverse_lazy('admin:index')
if hasattr(user, 'profile') and user.profile.is_premium:
return reverse_lazy('premium:dashboard')
# Default redirect
return reverse_lazy('accounts:profile')
class PostRedirectView(RedirectView):
"""Redirect old post URLs to new format"""
permanent = True
def get_redirect_url(self, *args, **kwargs):
"""Redirect old post ID to new slug-based URL"""
old_id = kwargs.get('old_id')
try:
post = Post.objects.get(legacy_id=old_id)
return reverse_lazy('blog:post_detail', kwargs={'slug': post.slug})
except Post.DoesNotExist:
# Redirect to post list if post not found
return reverse_lazy('blog:post_list')
class ConditionalRedirectView(RedirectView):
"""Redirect based on various conditions"""
def get_redirect_url(self, *args, **kwargs):
"""Conditional redirect logic"""
# Check maintenance mode
if getattr(settings, 'MAINTENANCE_MODE', False):
return reverse_lazy('maintenance')
# Check user agent for mobile redirect
user_agent = self.request.META.get('HTTP_USER_AGENT', '').lower()
if 'mobile' in user_agent:
return reverse_lazy('mobile:home')
# Check geographic location (if available)
country_code = self.request.META.get('HTTP_CF_IPCOUNTRY') # Cloudflare
if country_code in ['GB', 'IE']:
return reverse_lazy('uk:home')
# Default redirect
return reverse_lazy('home')
# URL patterns for redirects
urlpatterns = [
path('', HomeRedirectView.as_view()),
path('dashboard/', DynamicRedirectView.as_view(), name='dashboard_redirect'),
path('old-post/<int:old_id>/', PostRedirectView.as_view(), name='old_post_redirect'),
path('smart-redirect/', ConditionalRedirectView.as_view(), name='smart_redirect'),
]
class SmartRedirectView(RedirectView):
"""Intelligent redirect with logging and analytics"""
def dispatch(self, request, *args, **kwargs):
"""Log redirect before processing"""
self.log_redirect_attempt(request, *args, **kwargs)
return super().dispatch(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs):
"""Smart redirect with fallback logic"""
# Try to get redirect from URL mapping
redirect_url = self.get_url_mapping(**kwargs)
if redirect_url:
return redirect_url
# Try to find similar content
similar_url = self.find_similar_content(**kwargs)
if similar_url:
return similar_url
# Fallback to search page with suggested query
query = self.extract_search_query(**kwargs)
if query:
return f"{reverse_lazy('search')}?q={query}"
# Final fallback
return reverse_lazy('home')
def get_url_mapping(self, **kwargs):
"""Check URL mapping table"""
old_path = kwargs.get('path', '')
try:
mapping = URLMapping.objects.get(old_path=old_path)
return mapping.new_path
except URLMapping.DoesNotExist:
return None
def find_similar_content(self, **kwargs):
"""Find similar content based on URL patterns"""
path = kwargs.get('path', '')
# Extract potential slug from path
path_parts = path.strip('/').split('/')
potential_slug = path_parts[-1] if path_parts else ''
if potential_slug:
# Try to find post with similar slug
try:
post = Post.objects.get(slug__icontains=potential_slug)
return post.get_absolute_url()
except (Post.DoesNotExist, Post.MultipleObjectsReturned):
pass
return None
def extract_search_query(self, **kwargs):
"""Extract potential search query from URL"""
path = kwargs.get('path', '')
# Convert path to search terms
path_parts = path.strip('/').split('/')
search_terms = []
for part in path_parts:
# Clean up part and add to search terms
clean_part = part.replace('-', ' ').replace('_', ' ')
if len(clean_part) > 2: # Only meaningful terms
search_terms.append(clean_part)
return ' '.join(search_terms) if search_terms else None
def log_redirect_attempt(self, request, *args, **kwargs):
"""Log redirect attempt for analytics"""
RedirectLog.objects.create(
original_path=kwargs.get('path', ''),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
referer=request.META.get('HTTP_REFERER', ''),
ip_address=self.get_client_ip(request),
timestamp=timezone.now()
)
def get_client_ip(self, request):
"""Get client IP address"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
return x_forwarded_for.split(',')[0]
return request.META.get('REMOTE_ADDR')
class MaintenanceRedirectView(RedirectView):
"""Redirect to maintenance page when needed"""
def dispatch(self, request, *args, **kwargs):
"""Check maintenance mode before redirect"""
# Allow staff to bypass maintenance mode
if request.user.is_staff:
return super().dispatch(request, *args, **kwargs)
# Check if maintenance mode is active
if self.is_maintenance_mode():
return render(request, 'maintenance.html', status=503)
return super().dispatch(request, *args, **kwargs)
def is_maintenance_mode(self):
"""Check if site is in maintenance mode"""
# Check settings
if getattr(settings, 'MAINTENANCE_MODE', False):
return True
# Check database flag
try:
from myapp.models import SiteSettings
site_settings = SiteSettings.objects.first()
return site_settings and site_settings.maintenance_mode
except:
return False
def get_redirect_url(self, *args, **kwargs):
"""Normal redirect logic"""
return reverse_lazy('home')
Understanding these base classes provides the foundation for all Django class-based views. The View class offers maximum flexibility, TemplateView simplifies template rendering, and RedirectView handles URL redirection patterns. These classes can be combined and extended to create powerful, reusable view components.
Introduction to Class-Based Views
Class-based views (CBVs) provide an object-oriented approach to handling HTTP requests in Django. They leverage Python's class inheritance to create reusable, composable view components that reduce code duplication and improve maintainability.
Built-in Generic Views
Django's built-in generic views provide powerful, reusable patterns for common web application tasks. These views handle the most frequent operations like displaying lists of objects, showing object details, and managing object lifecycle operations.