Class Based Views

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.

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.

ListView - Displaying Object Lists

Basic ListView

from django.views.generic import ListView
from django.db.models import Q, Count
from django.core.paginator import Paginator

class PostListView(ListView):
    """Basic post list view"""
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10
    ordering = ['-created_at']
    
    def get_queryset(self):
        """Filter published posts only"""
        return Post.objects.filter(status='published').select_related('author', 'category')

class CategoryPostListView(ListView):
    """Posts filtered by category"""
    model = Post
    template_name = 'blog/category_posts.html'
    context_object_name = 'posts'
    paginate_by = 12
    
    def get_queryset(self):
        """Filter posts by category"""
        category_slug = self.kwargs['category_slug']
        return Post.objects.filter(
            category__slug=category_slug,
            status='published'
        ).select_related('author', 'category')
    
    def get_context_data(self, **kwargs):
        """Add category to context"""
        context = super().get_context_data(**kwargs)
        category_slug = self.kwargs['category_slug']
        
        try:
            context['category'] = Category.objects.get(slug=category_slug)
        except Category.DoesNotExist:
            raise Http404("Category not found")
        
        return context

class SearchPostListView(ListView):
    """Search posts with query"""
    model = Post
    template_name = 'blog/search_results.html'
    context_object_name = 'posts'
    paginate_by = 15
    
    def get_queryset(self):
        """Search posts based on query"""
        query = self.request.GET.get('q', '')
        
        if not query:
            return Post.objects.none()
        
        return Post.objects.filter(
            Q(title__icontains=query) | 
            Q(content__icontains=query) |
            Q(tags__name__icontains=query),
            status='published'
        ).distinct().select_related('author', 'category')
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        query = self.request.GET.get('q', '')
        
        context.update({
            'query': query,
            'total_results': self.get_queryset().count() if query else 0,
        })
        
        return context

Advanced ListView Patterns

class AdvancedPostListView(ListView):
    """Advanced post list with filtering and sorting"""
    model = Post
    template_name = 'blog/advanced_list.html'
    context_object_name = 'posts'
    paginate_by = 20
    paginate_orphans = 3
    
    def get_queryset(self):
        """Advanced filtering and sorting"""
        queryset = Post.objects.filter(status='published').select_related(
            'author', 'category'
        ).prefetch_related('tags')
        
        # Apply filters from GET parameters
        queryset = self.apply_filters(queryset)
        
        # Apply sorting
        queryset = self.apply_sorting(queryset)
        
        return queryset
    
    def apply_filters(self, queryset):
        """Apply various filters"""
        
        # Category filter
        category = self.request.GET.get('category')
        if category:
            queryset = queryset.filter(category__slug=category)
        
        # Author filter
        author = self.request.GET.get('author')
        if author:
            queryset = queryset.filter(author__username=author)
        
        # Tag filter
        tag = self.request.GET.get('tag')
        if tag:
            queryset = queryset.filter(tags__slug=tag)
        
        # Date range filter
        date_from = self.request.GET.get('date_from')
        date_to = self.request.GET.get('date_to')
        
        if date_from:
            queryset = queryset.filter(created_at__gte=date_from)
        if date_to:
            queryset = queryset.filter(created_at__lte=date_to)
        
        # Featured filter
        featured = self.request.GET.get('featured')
        if featured == 'true':
            queryset = queryset.filter(featured=True)
        
        return queryset
    
    def apply_sorting(self, queryset):
        """Apply sorting based on GET parameter"""
        sort_by = self.request.GET.get('sort', 'newest')
        
        sort_options = {
            'newest': ['-created_at'],
            'oldest': ['created_at'],
            'title': ['title'],
            'author': ['author__username', 'title'],
            'popular': ['-views', '-created_at'],
            'comments': ['-comment_count', '-created_at'],
        }
        
        # Add comment count annotation for comment sorting
        if sort_by == 'comments':
            queryset = queryset.annotate(
                comment_count=Count('comments', filter=Q(comments__approved=True))
            )
        
        ordering = sort_options.get(sort_by, ['-created_at'])
        return queryset.order_by(*ordering)
    
    def get_context_data(self, **kwargs):
        """Add filter and sort context"""
        context = super().get_context_data(**kwargs)
        
        # Add current filters
        context.update({
            'current_category': self.request.GET.get('category', ''),
            'current_author': self.request.GET.get('author', ''),
            'current_tag': self.request.GET.get('tag', ''),
            'current_sort': self.request.GET.get('sort', 'newest'),
            'date_from': self.request.GET.get('date_from', ''),
            'date_to': self.request.GET.get('date_to', ''),
            'featured_only': self.request.GET.get('featured') == 'true',
        })
        
        # Add filter options
        context.update({
            'categories': Category.objects.annotate(
                post_count=Count('posts', filter=Q(posts__status='published'))
            ).filter(post_count__gt=0),
            'authors': User.objects.filter(
                posts__status='published'
            ).annotate(
                post_count=Count('posts')
            ).distinct(),
            'popular_tags': Tag.objects.annotate(
                post_count=Count('posts', filter=Q(posts__status='published'))
            ).filter(post_count__gt=0).order_by('-post_count')[:20],
            'sort_options': [
                ('newest', 'Newest First'),
                ('oldest', 'Oldest First'),
                ('title', 'Title A-Z'),
                ('author', 'Author A-Z'),
                ('popular', 'Most Popular'),
                ('comments', 'Most Commented'),
            ],
        })
        
        return context
    
    def get_paginate_by(self, queryset):
        """Dynamic pagination"""
        per_page = self.request.GET.get('per_page')
        if per_page:
            try:
                per_page = int(per_page)
                return min(max(per_page, 5), 100)  # Between 5 and 100
            except ValueError:
                pass
        
        return self.paginate_by

class AjaxPostListView(ListView):
    """AJAX-enabled post list"""
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10
    
    def get_queryset(self):
        return Post.objects.filter(status='published').select_related('author')
    
    def render_to_response(self, context, **response_kwargs):
        """Handle AJAX requests differently"""
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            # Return JSON for AJAX requests
            posts_data = []
            for post in context['posts']:
                posts_data.append({
                    'id': post.id,
                    'title': post.title,
                    'excerpt': post.excerpt,
                    'author': post.author.username,
                    'created_at': post.created_at.isoformat(),
                    'url': post.get_absolute_url(),
                })
            
            return JsonResponse({
                'posts': posts_data,
                'has_next': context['page_obj'].has_next() if context.get('page_obj') else False,
                'has_previous': context['page_obj'].has_previous() if context.get('page_obj') else False,
                'current_page': context['page_obj'].number if context.get('page_obj') else 1,
                'total_pages': context['paginator'].num_pages if context.get('paginator') else 1,
            })
        
        # Regular template response
        return super().render_to_response(context, **response_kwargs)

DetailView - Displaying Single Objects

Basic DetailView

from django.views.generic import DetailView
from django.shortcuts import get_object_or_404
from django.http import Http404

class PostDetailView(DetailView):
    """Basic post detail view"""
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    
    def get_queryset(self):
        """Only show published posts"""
        return Post.objects.filter(status='published').select_related(
            'author', 'category'
        ).prefetch_related('tags')
    
    def get_object(self, queryset=None):
        """Get object and increment view count"""
        obj = super().get_object(queryset)
        
        # Increment view count (avoid race conditions)
        Post.objects.filter(pk=obj.pk).update(views=F('views') + 1)
        
        return obj

class PostDetailBySlugView(DetailView):
    """Post detail view using slug"""
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    slug_field = 'slug'
    slug_url_kwarg = 'slug'
    
    def get_queryset(self):
        return Post.objects.filter(status='published').select_related(
            'author', 'category'
        ).prefetch_related('tags', 'comments__author')

class UserPostDetailView(DetailView):
    """Post detail with author verification"""
    model = Post
    template_name = 'blog/user_post_detail.html'
    context_object_name = 'post'
    
    def get_queryset(self):
        """Filter by author from URL"""
        author_username = self.kwargs['username']
        return Post.objects.filter(
            author__username=author_username,
            status='published'
        ).select_related('author', 'category')
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        # Add author information
        context['author'] = self.object.author
        
        # Add other posts by same author
        context['other_posts'] = Post.objects.filter(
            author=self.object.author,
            status='published'
        ).exclude(pk=self.object.pk)[:5]
        
        return context

Advanced DetailView Patterns

class AdvancedPostDetailView(DetailView):
    """Advanced post detail with comments and related content"""
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    
    def get_queryset(self):
        return Post.objects.filter(status='published').select_related(
            'author', 'category'
        ).prefetch_related(
            'tags',
            'comments__author',
            'comments__replies__author'
        )
    
    def get_object(self, queryset=None):
        """Get object with additional processing"""
        obj = super().get_object(queryset)
        
        # Check if user can view this post
        if not self.can_view_post(obj):
            raise Http404("Post not found")
        
        # Track view (with session-based deduplication)
        self.track_view(obj)
        
        return obj
    
    def can_view_post(self, post):
        """Check if user can view this post"""
        # Always allow published posts
        if post.status == 'published':
            return True
        
        # Allow author to view their own posts
        if post.author == self.request.user:
            return True
        
        # Allow staff to view any post
        if self.request.user.is_staff:
            return True
        
        return False
    
    def track_view(self, post):
        """Track post view with session deduplication"""
        session_key = f'viewed_post_{post.pk}'
        
        if not self.request.session.get(session_key):
            # Increment view count
            Post.objects.filter(pk=post.pk).update(views=F('views') + 1)
            
            # Mark as viewed in session
            self.request.session[session_key] = True
            
            # Create view log entry
            PostView.objects.create(
                post=post,
                user=self.request.user if self.request.user.is_authenticated else None,
                ip_address=self.get_client_ip(),
                user_agent=self.request.META.get('HTTP_USER_AGENT', ''),
                timestamp=timezone.now()
            )
    
    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_context_data(self, **kwargs):
        """Add comprehensive context data"""
        context = super().get_context_data(**kwargs)
        post = self.object
        
        # Add comments (approved only for non-staff)
        if self.request.user.is_staff:
            comments = post.comments.all()
        else:
            comments = post.comments.filter(approved=True)
        
        context['comments'] = comments.select_related('author').order_by('created_at')
        
        # Add comment form for authenticated users
        if self.request.user.is_authenticated:
            context['comment_form'] = CommentForm()
        
        # Add related posts
        context['related_posts'] = self.get_related_posts(post)
        
        # Add navigation (previous/next posts)
        context.update(self.get_post_navigation(post))
        
        # Add reading time estimate
        context['reading_time'] = self.calculate_reading_time(post.content)
        
        # Add social sharing data
        context['sharing_data'] = self.get_sharing_data(post)
        
        return context
    
    def get_related_posts(self, post):
        """Get related posts based on category and tags"""
        related_posts = Post.objects.filter(
            status='published'
        ).exclude(pk=post.pk)
        
        # Posts in same category
        category_posts = related_posts.filter(category=post.category)[:3]
        
        # Posts with similar tags
        tag_posts = related_posts.filter(
            tags__in=post.tags.all()
        ).distinct()[:3]
        
        # Combine and deduplicate
        related_ids = set()
        final_related = []
        
        for post_list in [category_posts, tag_posts]:
            for related_post in post_list:
                if related_post.id not in related_ids:
                    related_ids.add(related_post.id)
                    final_related.append(related_post)
                    if len(final_related) >= 6:
                        break
            if len(final_related) >= 6:
                break
        
        return final_related
    
    def get_post_navigation(self, post):
        """Get previous and next posts"""
        try:
            previous_post = Post.objects.filter(
                created_at__lt=post.created_at,
                status='published'
            ).order_by('-created_at').first()
        except Post.DoesNotExist:
            previous_post = None
        
        try:
            next_post = Post.objects.filter(
                created_at__gt=post.created_at,
                status='published'
            ).order_by('created_at').first()
        except Post.DoesNotExist:
            next_post = None
        
        return {
            'previous_post': previous_post,
            'next_post': next_post,
        }
    
    def calculate_reading_time(self, content):
        """Calculate estimated reading time"""
        words_per_minute = 200
        word_count = len(content.split())
        reading_time = max(1, round(word_count / words_per_minute))
        return reading_time
    
    def get_sharing_data(self, post):
        """Get social sharing data"""
        return {
            'title': post.title,
            'description': post.excerpt or post.content[:160],
            'url': self.request.build_absolute_uri(post.get_absolute_url()),
            'image': post.featured_image.url if post.featured_image else None,
            'author': post.author.get_full_name() or post.author.username,
        }

class MultiFormatDetailView(DetailView):
    """Detail view supporting multiple output formats"""
    model = Post
    
    def get_template_names(self):
        """Select template based on format"""
        format_param = self.request.GET.get('format', 'html')
        
        if format_param == 'print':
            return ['blog/post_detail_print.html']
        elif format_param == 'mobile':
            return ['blog/post_detail_mobile.html']
        elif format_param == 'amp':
            return ['blog/post_detail_amp.html']
        
        return ['blog/post_detail.html']
    
    def render_to_response(self, context, **response_kwargs):
        """Handle different response formats"""
        format_param = self.request.GET.get('format', 'html')
        
        if format_param == 'json':
            # Return JSON representation
            post = context['post']
            data = {
                'id': post.id,
                'title': post.title,
                'content': post.content,
                'author': post.author.username,
                'created_at': post.created_at.isoformat(),
                'tags': [tag.name for tag in post.tags.all()],
            }
            return JsonResponse(data)
        
        elif format_param == 'xml':
            # Return XML representation
            post = context['post']
            xml_content = f'''<?xml version="1.0" encoding="UTF-8"?>
            <post>
                <id>{post.id}</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')
        
        # Default HTML response
        return super().render_to_response(context, **response_kwargs)

ArchiveIndexView and Date-Based Views

Archive Views

from django.views.generic.dates import (
    ArchiveIndexView, YearArchiveView, MonthArchiveView, 
    DayArchiveView, DateDetailView
)

class PostArchiveIndexView(ArchiveIndexView):
    """Archive index showing latest posts"""
    model = Post
    date_field = 'created_at'
    template_name = 'blog/archive_index.html'
    context_object_name = 'posts'
    paginate_by = 20
    allow_empty = True
    
    def get_queryset(self):
        return Post.objects.filter(status='published').select_related('author')

class PostYearArchiveView(YearArchiveView):
    """Posts for a specific year"""
    model = Post
    date_field = 'created_at'
    template_name = 'blog/year_archive.html'
    context_object_name = 'posts'
    make_object_list = True
    allow_empty = True
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        year = self.get_year()
        
        # Add year statistics
        context['year_stats'] = {
            'total_posts': context['posts'].count(),
            'months_with_posts': context['posts'].dates('created_at', 'month'),
        }
        
        return context

class PostMonthArchiveView(MonthArchiveView):
    """Posts for a specific month"""
    model = Post
    date_field = 'created_at'
    template_name = 'blog/month_archive.html'
    context_object_name = 'posts'
    month_format = '%m'
    allow_empty = True
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        # Add month navigation
        current_date = datetime.date(int(self.get_year()), int(self.get_month()), 1)
        
        # Previous month
        if current_date.month == 1:
            prev_month = current_date.replace(year=current_date.year - 1, month=12)
        else:
            prev_month = current_date.replace(month=current_date.month - 1)
        
        # Next month
        if current_date.month == 12:
            next_month = current_date.replace(year=current_date.year + 1, month=1)
        else:
            next_month = current_date.replace(month=current_date.month + 1)
        
        context.update({
            'previous_month': prev_month,
            'next_month': next_month,
            'current_month': current_date,
        })
        
        return context

class PostDayArchiveView(DayArchiveView):
    """Posts for a specific day"""
    model = Post
    date_field = 'created_at'
    template_name = 'blog/day_archive.html'
    context_object_name = 'posts'
    month_format = '%m'
    allow_empty = True

class PostDateDetailView(DateDetailView):
    """Single post by date and slug"""
    model = Post
    date_field = 'created_at'
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'
    month_format = '%m'
    slug_field = 'slug'

Custom Archive Views

class CustomArchiveView(TemplateView):
    """Custom archive with calendar widget"""
    template_name = 'blog/custom_archive.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        # Get archive data
        context.update({
            'archive_dates': self.get_archive_dates(),
            'popular_posts': self.get_popular_posts(),
            'recent_posts': self.get_recent_posts(),
            'calendar_data': self.get_calendar_data(),
        })
        
        return context
    
    def get_archive_dates(self):
        """Get dates with post counts"""
        return Post.objects.filter(
            status='published'
        ).extra(
            select={'year': 'EXTRACT(year FROM created_at)',
                   'month': 'EXTRACT(month FROM created_at)'}
        ).values('year', 'month').annotate(
            post_count=Count('id')
        ).order_by('-year', '-month')
    
    def get_popular_posts(self):
        """Get most popular posts from archive"""
        return Post.objects.filter(
            status='published'
        ).order_by('-views')[:10]
    
    def get_recent_posts(self):
        """Get recent posts"""
        return Post.objects.filter(
            status='published'
        ).order_by('-created_at')[:10]
    
    def get_calendar_data(self):
        """Get calendar data for current month"""
        import calendar
        from datetime import date
        
        today = date.today()
        cal = calendar.monthcalendar(today.year, today.month)
        
        # Get posts for current month
        month_posts = Post.objects.filter(
            status='published',
            created_at__year=today.year,
            created_at__month=today.month
        ).values('created_at__day').annotate(
            post_count=Count('id')
        )
        
        # Create lookup dict
        posts_by_day = {item['created_at__day']: item['post_count'] 
                       for item in month_posts}
        
        # Add post counts to calendar
        calendar_with_posts = []
        for week in cal:
            week_with_posts = []
            for day in week:
                if day == 0:
                    week_with_posts.append({'day': 0, 'posts': 0})
                else:
                    week_with_posts.append({
                        'day': day,
                        'posts': posts_by_day.get(day, 0)
                    })
            calendar_with_posts.append(week_with_posts)
        
        return {
            'calendar': calendar_with_posts,
            'month_name': calendar.month_name[today.month],
            'year': today.year,
        }

Built-in generic views provide powerful abstractions for common patterns. ListView handles object collections with filtering, sorting, and pagination. DetailView manages single object display with related data. Archive views offer sophisticated date-based browsing capabilities. Understanding these views and their customization options enables rapid development of feature-rich Django applications.