Class Based Views

Common Base Classes

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.

Common Base Classes

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.

View - The Foundation Class

Basic View Class

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)

HTTP Method Handling

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
        }

TemplateView - Template Rendering

Basic TemplateView

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,
        )

Advanced TemplateView Patterns

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'},
        ]

RedirectView - URL Redirection

Basic RedirectView

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'),
]

Advanced Redirect Patterns

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.