Skillsdjango-cbv-patterns
D

django-cbv-patterns

Use when Django Class-Based Views for building modular, reusable views. Use when creating CRUD operations and complex view logic.

TheBushidoCollective
64 stars
1.3k downloads
Updated 6d ago

Readme

django-cbv-patterns follows the SKILL.md standard. Use the install command to add it to your agent stack.

---
name: django-cbv-patterns
description: Use when Django Class-Based Views for building modular, reusable views. Use when creating CRUD operations and complex view logic.
allowed-tools:
  - Bash
  - Read
---

# Django Class-Based Views

Master Django Class-Based Views for building modular, reusable view
logic with proper separation of concerns.

## Generic Views

Use Django's built-in generic views for common patterns.

```python
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy

class PostListView(ListView):
    model = Post
    template_name = 'posts/list.html'
    context_object_name = 'posts'
    paginate_by = 10
    ordering = ['-created_at']

    def get_queryset(self):
        queryset = super().get_queryset()
        # Only show published posts
        return queryset.filter(published=True).select_related('author')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['total_posts'] = self.get_queryset().count()
        return context

class PostDetailView(DetailView):
    model = Post
    template_name = 'posts/detail.html'
    context_object_name = 'post'

    def get_queryset(self):
        return super().get_queryset().select_related('author').prefetch_related('comments')

class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'content', 'published']
    template_name = 'posts/create.html'
    success_url = reverse_lazy('post-list')

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

class PostUpdateView(UpdateView):
    model = Post
    fields = ['title', 'content', 'published']
    template_name = 'posts/update.html'

    def get_success_url(self):
        return reverse_lazy('post-detail', kwargs={'pk': self.object.pk})

class PostDeleteView(DeleteView):
    model = Post
    template_name = 'posts/confirm_delete.html'
    success_url = reverse_lazy('post-list')
```

## Built-in Mixins

Leverage Django's built-in mixins for common functionality.

```python
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin, PermissionRequiredMixin
from django.views.generic import CreateView, UpdateView

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'content']
    login_url = '/login/'
    redirect_field_name = 'next'

class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Post
    fields = ['title', 'content']

    def test_func(self):
        post = self.get_object()
        return self.request.user == post.author

    def handle_no_permission(self):
        # Custom handling when test fails
        messages.error(self.request, 'You can only edit your own posts')
        return redirect('post-list')

class AdminPostListView(PermissionRequiredMixin, ListView):
    model = Post
    permission_required = 'posts.view_post'
    raise_exception = True  # Return 403 instead of redirect
```

## Custom Mixins

Create reusable mixins for common patterns.

```python
from django.views.generic import View
from django.shortcuts import redirect
from django.contrib import messages

class AuthorRequiredMixin:
    """Ensure the current user is the object's author."""

    def dispatch(self, request, *args, **kwargs):
        obj = self.get_object()
        if obj.author != request.user:
            messages.error(request, 'You do not have permission')
            return redirect('post-list')
        return super().dispatch(request, *args, **kwargs)

class FormMessageMixin:
    """Add success messages to form views."""
    success_message = ''

    def form_valid(self, form):
        response = super().form_valid(form)
        if self.success_message:
            messages.success(self.request, self.success_message)
        return response

class AjaxableResponseMixin:
    """Handle AJAX requests differently."""

    def form_valid(self, form):
        if self.request.is_ajax():
            data = {
                'pk': form.instance.pk,
                'success': True
            }
            return JsonResponse(data)
        return super().form_valid(form)

    def form_invalid(self, form):
        if self.request.is_ajax():
            return JsonResponse(form.errors, status=400)
        return super().form_invalid(form)

# Usage
class PostUpdateView(LoginRequiredMixin, AuthorRequiredMixin, FormMessageMixin, UpdateView):
    model = Post
    fields = ['title', 'content']
    success_message = 'Post updated successfully'
```

## Method Resolution Order (MRO)

Understand how Django resolves methods in CBVs.

```python
# MRO matters! Order from left to right
class PostUpdateView(
    LoginRequiredMixin,      # Check login first
    AuthorRequiredMixin,     # Then check authorship
    FormMessageMixin,        # Add messages
    UpdateView               # Base view last
):
    model = Post
    fields = ['title', 'content']

# View the MRO
print(PostUpdateView.__mro__)

# Bad example - wrong order
class BadPostUpdateView(
    UpdateView,              # Base view first - wrong!
    LoginRequiredMixin,
    AuthorRequiredMixin
):
    pass  # Mixins won't work correctly

# Override dispatch to control flow
class CustomView(LoginRequiredMixin, UpdateView):
    def dispatch(self, request, *args, **kwargs):
        # Custom logic before any other processing
        if not request.user.is_verified:
            return redirect('verify-email')
        return super().dispatch(request, *args, **kwargs)
```

## Form Handling in CBVs

Advanced form handling patterns.

```python
from django.views.generic.edit import FormView
from django.contrib import messages

class ContactFormView(FormView):
    template_name = 'contact.html'
    form_class = ContactForm
    success_url = '/thanks/'

    def get_form_kwargs(self):
        """Pass request to form."""
        kwargs = super().get_form_kwargs()
        kwargs['request'] = self.request
        return kwargs

    def get_initial(self):
        """Pre-populate form."""
        initial = super().get_initial()
        if self.request.user.is_authenticated:
            initial['email'] = self.request.user.email
            initial['name'] = self.request.user.name
        return initial

    def form_valid(self, form):
        form.send_email()
        messages.success(self.request, 'Message sent!')
        return super().form_valid(form)

    def form_invalid(self, form):
        messages.error(self.request, 'Please correct the errors')
        return super().form_invalid(form)

# Multiple forms in one view
class ProfileUpdateView(LoginRequiredMixin, TemplateView):
    template_name = 'profile.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if 'user_form' not in context:
            context['user_form'] = UserForm(instance=self.request.user)
        if 'profile_form' not in context:
            context['profile_form'] = ProfileForm(instance=self.request.user.profile)
        return context

    def post(self, request, *args, **kwargs):
        user_form = UserForm(request.POST, instance=request.user)
        profile_form = ProfileForm(request.POST, instance=request.user.profile)

        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Profile updated')
            return redirect('profile')

        return self.render_to_response(
            self.get_context_data(user_form=user_form, profile_form=profile_form)
        )
```

## When to Use CBVs vs FBVs

Guidelines for choosing between class-based and function-based views.

```python
# Use CBVs for:
# 1. Standard CRUD operations
class PostListView(ListView):
    model = Post

# 2. Reusable view logic
class OwnerRequiredMixin:
    def get_queryset(self):
        return super().get_queryset().filter(owner=self.request.user)

# 3. Multiple similar views
class UserPostListView(OwnerRequiredMixin, ListView):
    model = Post

class UserDraftListView(OwnerRequiredMixin, ListView):
    model = Post
    queryset = Post.objects.filter(published=False)

# Use FBVs for:
# 1. Simple one-off views
def simple_view(request):
    return render(request, 'simple.html')

# 2. Complex custom logic that doesn't fit CBV patterns
def complex_workflow(request):
    if request.method == 'POST':
        # Complex multi-step logic
        step = request.POST.get('step')
        if step == '1':
            # Process step 1
            pass
        elif step == '2':
            # Process step 2
            pass
    return render(request, 'workflow.html')

# 3. Views that handle multiple models in non-standard ways
def dashboard(request):
    posts = Post.objects.filter(author=request.user)
    comments = Comment.objects.filter(post__author=request.user)
    analytics = calculate_analytics(request.user)
    return render(request, 'dashboard.html', {
        'posts': posts,
        'comments': comments,
        'analytics': analytics
    })
```

## Testing CBVs

Comprehensive testing strategies for class-based views.

```python
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User, AnonymousUser

class PostListViewTest(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass'
        )

    def test_list_view(self):
        request = self.factory.get('/posts/')
        request.user = self.user
        response = PostListView.as_view()(request)
        self.assertEqual(response.status_code, 200)

    def test_queryset_only_published(self):
        Post.objects.create(title='Published', author=self.user, published=True)
        Post.objects.create(title='Draft', author=self.user, published=False)

        request = self.factory.get('/posts/')
        request.user = self.user
        response = PostListView.as_view()(request)
        self.assertEqual(len(response.context_data['posts']), 1)

    def test_login_required(self):
        request = self.factory.get('/posts/create/')
        request.user = AnonymousUser()
        response = PostCreateView.as_view()(request)
        self.assertEqual(response.status_code, 302)  # Redirect to login

    def test_author_required(self):
        other_user = User.objects.create_user('other', password='pass')
        post = Post.objects.create(title='Test', author=other_user)

        request = self.factory.get(f'/posts/{post.pk}/edit/')
        request.user = self.user
        response = PostUpdateView.as_view()(request, pk=post.pk)
        self.assertEqual(response.status_code, 302)  # Redirect denied
```

## Advanced Patterns

Complex CBV patterns for production applications.

```python
# Filtering with GET parameters
class PostFilterView(ListView):
    model = Post
    paginate_by = 20

    def get_queryset(self):
        queryset = super().get_queryset()
        author = self.request.GET.get('author')
        published = self.request.GET.get('published')

        if author:
            queryset = queryset.filter(author__name__icontains=author)
        if published is not None:
            queryset = queryset.filter(published=published == 'true')

        return queryset

# Dynamic template selection
class PostDetailView(DetailView):
    model = Post

    def get_template_names(self):
        if self.request.user == self.object.author:
            return ['posts/detail_owner.html']
        return ['posts/detail.html']

# JSON response view
from django.http import JsonResponse

class PostJSONView(DetailView):
    model = Post

    def render_to_response(self, context, **response_kwargs):
        return JsonResponse({
            'id': self.object.id,
            'title': self.object.title,
            'content': self.object.content,
            'author': self.object.author.name
        })

# Conditional form fields
class PostCreateView(CreateView):
    model = Post
    template_name = 'posts/create.html'

    def get_form_class(self):
        if self.request.user.is_staff:
            return AdminPostForm
        return UserPostForm

# Multiple object types
from django.views.generic import TemplateView

class SearchView(TemplateView):
    template_name = 'search.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        query = self.request.GET.get('q', '')

        if query:
            context['posts'] = Post.objects.filter(title__icontains=query)
            context['users'] = User.objects.filter(name__icontains=query)
            context['query'] = query

        return context
```

## Pagination in CBVs

Implement sophisticated pagination patterns.

```python
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.views.generic import ListView

class PostListView(ListView):
    model = Post
    paginate_by = 20
    paginate_orphans = 5  # Avoid last page with few items

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # Add custom pagination data
        paginator = context['paginator']
        page_obj = context['page_object']

        # Calculate page range for display
        index = page_obj.number - 1
        max_index = len(paginator.page_range)
        start_index = index - 3 if index >= 3 else 0
        end_index = index + 4 if index <= max_index - 4 else max_index

        context['page_range'] = list(paginator.page_range)[start_index:end_index]
        context['total_pages'] = paginator.num_pages

        return context

# AJAX pagination
class AjaxPostListView(ListView):
    model = Post
    paginate_by = 10
    template_name = 'posts/list.html'

    def get_template_names(self):
        if self.request.is_ajax():
            return ['posts/partials/post_list.html']
        return [self.template_name]

    def render_to_response(self, context, **response_kwargs):
        if self.request.is_ajax():
            from django.http import JsonResponse
            posts = [
                {
                    'id': post.id,
                    'title': post.title,
                    'author': post.author.name
                }
                for post in context['object_list']
            ]
            return JsonResponse({
                'posts': posts,
                'has_next': context['page_obj'].has_next(),
                'page': context['page_obj'].number
            })
        return super().render_to_response(context, **response_kwargs)

# Infinite scroll pagination
class InfiniteScrollListView(ListView):
    model = Post
    paginate_by = 20

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['is_infinite_scroll'] = True
        return context
```

## Context Data Manipulation

Master advanced context manipulation techniques.

```python
class PostDetailView(DetailView):
    model = Post

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        # Add related data
        post = self.object
        context['related_posts'] = Post.objects.filter(
            category=post.category
        ).exclude(id=post.id)[:5]

        # Add user-specific data
        if self.request.user.is_authenticated:
            context['has_liked'] = post.likes.filter(
                user=self.request.user
            ).exists()
            context['is_bookmarked'] = post.bookmarks.filter(
                user=self.request.user
            ).exists()

        # Add computed data
        context['reading_time'] = post.calculate_reading_time()
        context['share_url'] = self.request.build_absolute_uri()

        return context

# Multiple context mixins
class AnalyticsMixin:
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['analytics_enabled'] = True
        context['tracking_id'] = settings.ANALYTICS_ID
        return context

class BreadcrumbMixin:
    breadcrumbs = []

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['breadcrumbs'] = self.get_breadcrumbs()
        return context

    def get_breadcrumbs(self):
        return self.breadcrumbs

class PostDetailView(AnalyticsMixin, BreadcrumbMixin, DetailView):
    model = Post
    breadcrumbs = [
        ('Home', '/'),
        ('Posts', '/posts/'),
    ]

    def get_breadcrumbs(self):
        breadcrumbs = super().get_breadcrumbs()
        breadcrumbs.append((self.object.title, None))
        return breadcrumbs
```

## Method Override Patterns

Override specific methods for fine-grained control.

```python
class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'content', 'category']

    # Control initial form data
    def get_initial(self):
        initial = super().get_initial()
        if self.request.user.is_authenticated:
            initial['author'] = self.request.user
        return initial

    # Control form kwargs
    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.request.user
        kwargs['categories'] = Category.objects.filter(active=True)
        return kwargs

    # Control form class selection
    def get_form_class(self):
        if self.request.user.is_staff:
            return AdminPostForm
        return PostForm

    # Control success URL dynamically
    def get_success_url(self):
        if 'save_and_add' in self.request.POST:
            return reverse('post-create')
        return reverse('post-detail', kwargs={'pk': self.object.pk})

    # Customize form validation
    def form_valid(self, form):
        form.instance.author = self.request.user
        form.instance.ip_address = self.request.META.get('REMOTE_ADDR')

        # Additional validation
        if form.instance.published and not form.instance.content:
            form.add_error('content', 'Published posts must have content')
            return self.form_invalid(form)

        response = super().form_valid(form)

        # Post-save actions
        messages.success(self.request, 'Post created successfully')

        return response

    # Customize form error handling
    def form_invalid(self, form):
        messages.error(self.request, 'Please correct the errors below')
        return super().form_invalid(form)

# Override get_object for custom logic
class PostUpdateView(UpdateView):
    model = Post

    def get_object(self, queryset=None):
        obj = super().get_object(queryset)

        # Track view
        obj.views += 1
        obj.save(update_fields=['views'])

        # Check permissions
        if obj.author != self.request.user and not self.request.user.is_staff:
            raise PermissionDenied('You can only edit your own posts')

        return obj

    def get_queryset(self):
        queryset = super().get_queryset()

        # Filter based on user
        if not self.request.user.is_staff:
            queryset = queryset.filter(author=self.request.user)

        # Optimize queries
        queryset = queryset.select_related('author', 'category')

        return queryset
```

## Advanced Mixin Composition

Build complex functionality through mixin composition.

```python
from django.contrib import messages
from django.shortcuts import redirect
from django.core.exceptions import PermissionDenied

class SetHeadlineMixin:
    """Add a headline to the context."""
    headline = None

    def get_headline(self):
        return self.headline

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['headline'] = self.get_headline()
        return context

class SetButtonTextMixin:
    """Add button text to the context."""
    button_text = 'Submit'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['button_text'] = self.button_text
        return context

class FormValidMessageMixin:
    """Display success message after form submission."""
    success_message = 'Form submitted successfully'

    def form_valid(self, form):
        response = super().form_valid(form)
        messages.success(self.request, self.get_success_message(form))
        return response

    def get_success_message(self, form):
        return self.success_message

class DeleteConfirmMixin:
    """Require confirmation before deletion."""
    def delete(self, request, *args, **kwargs):
        if not request.POST.get('confirm'):
            messages.warning(request, 'Please confirm deletion')
            return redirect(self.get_success_url())

        messages.success(request, 'Item deleted successfully')
        return super().delete(request, *args, **kwargs)

class StaffRequiredMixin:
    """Require staff user."""
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_staff:
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

class AuditMixin:
    """Track creation and updates."""
    def form_valid(self, form):
        if not form.instance.pk:
            form.instance.created_by = self.request.user
        form.instance.updated_by = self.request.user
        return super().form_valid(form)

# Compose multiple mixins
class PostCreateView(
    LoginRequiredMixin,
    SetHeadlineMixin,
    SetButtonTextMixin,
    FormValidMessageMixin,
    AuditMixin,
    CreateView
):
    model = Post
    fields = ['title', 'content']
    headline = 'Create New Post'
    button_text = 'Create Post'
    success_message = 'Post created successfully!'
```

## Search and Filter Views

Implement advanced search and filtering.

```python
from django.db.models import Q
from django.views.generic import ListView

class PostSearchView(ListView):
    model = Post
    template_name = 'posts/search.html'
    paginate_by = 20

    def get_queryset(self):
        queryset = super().get_queryset()
        query = self.request.GET.get('q')

        if query:
            queryset = queryset.filter(
                Q(title__icontains=query) |
                Q(content__icontains=query) |
                Q(author__name__icontains=query)
            )

        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['query'] = self.request.GET.get('q', '')
        context['result_count'] = context['paginator'].count
        return context

class PostFilterView(ListView):
    model = Post
    template_name = 'posts/filter.html'
    paginate_by = 20

    def get_queryset(self):
        queryset = super().get_queryset()

        # Category filter
        category = self.request.GET.get('category')
        if category:
            queryset = queryset.filter(category_id=category)

        # Author filter
        author = self.request.GET.get('author')
        if author:
            queryset = queryset.filter(author_id=author)

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

        # Published filter
        published = self.request.GET.get('published')
        if published is not None:
            queryset = queryset.filter(published=published == 'true')

        # Sorting
        sort = self.request.GET.get('sort', '-created_at')
        allowed_sorts = ['created_at', '-created_at', 'title', '-title', 'views', '-views']
        if sort in allowed_sorts:
            queryset = queryset.order_by(sort)

        return queryset.select_related('author', 'category')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['categories'] = Category.objects.all()
        context['authors'] = User.objects.filter(posts__isnull=False).distinct()
        context['filters'] = self.request.GET
        return context
```

## File Upload Views

Handle file uploads with CBVs.

```python
from django.views.generic.edit import FormView
from django.core.files.storage import default_storage

class FileUploadView(FormView):
    template_name = 'upload.html'
    form_class = FileUploadForm
    success_url = '/success/'

    def form_valid(self, form):
        file = form.cleaned_data['file']

        # Save file
        filename = default_storage.save(f'uploads/{file.name}', file)

        # Process file
        self.process_file(filename)

        messages.success(self.request, f'File {file.name} uploaded successfully')
        return super().form_valid(form)

    def process_file(self, filename):
        # Custom file processing logic
        pass

class MultipleFileUploadView(FormView):
    template_name = 'upload_multiple.html'
    form_class = MultipleFileUploadForm
    success_url = '/success/'

    def form_valid(self, form):
        files = self.request.FILES.getlist('files')

        for file in files:
            # Validate file
            if file.size > 10 * 1024 * 1024:  # 10MB limit
                form.add_error('files', f'{file.name} exceeds size limit')
                return self.form_invalid(form)

            # Save file
            filename = default_storage.save(f'uploads/{file.name}', file)

            # Create database record
            FileUpload.objects.create(
                filename=filename,
                original_name=file.name,
                size=file.size,
                uploaded_by=self.request.user
            )

        messages.success(self.request, f'{len(files)} files uploaded successfully')
        return super().form_valid(form)

class ImageUploadView(CreateView):
    model = Image
    fields = ['title', 'image', 'description']
    template_name = 'images/upload.html'

    def form_valid(self, form):
        form.instance.uploaded_by = self.request.user

        # Validate image
        image = form.cleaned_data['image']
        if image.size > 5 * 1024 * 1024:  # 5MB
            form.add_error('image', 'Image too large')
            return self.form_invalid(form)

        # Process image (resize, thumbnail, etc.)
        form.instance.thumbnail = self.create_thumbnail(image)

        return super().form_valid(form)

    def create_thumbnail(self, image):
        # Thumbnail creation logic
        pass
```

## Performance Optimization

Optimize CBVs for better performance.

```python
class OptimizedPostListView(ListView):
    model = Post
    paginate_by = 20

    def get_queryset(self):
        return Post.objects.select_related(
            'author'
        ).prefetch_related(
            'comments'
        ).only(
            'id', 'title', 'created_at', 'author__name'
        ).filter(
            published=True
        )

# Caching
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator

@method_decorator(cache_page(60 * 15), name='dispatch')
class CachedPostListView(ListView):
    model = Post

# Conditional caching based on user
class SmartCachedView(ListView):
    model = Post

    @method_decorator(cache_page(60 * 15))
    def dispatch(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            # Don't cache for authenticated users
            return super().dispatch(request, *args, **kwargs)
        return super().dispatch(request, *args, **kwargs)

# ETags for caching
from django.views.decorators.http import condition

def latest_post(request, *args, **kwargs):
    return Post.objects.latest('updated_at').updated_at

def post_etag(request, *args, **kwargs):
    return str(Post.objects.latest('updated_at').updated_at.timestamp())

class PostListView(ListView):
    model = Post

    @method_decorator(condition(etag_func=post_etag, last_modified_func=latest_post))
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
```

## When to Use This Skill

Use django-cbv-patterns when building modern, production-ready
applications that require
advanced patterns, best practices, and optimal performance.

## API Views with CBVs

Build API endpoints using CBVs without DRF.

```python
from django.http import JsonResponse
from django.views.generic import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import json

@method_decorator(csrf_exempt, name='dispatch')
class PostAPIView(View):
    def get(self, request, *args, **kwargs):
        posts = Post.objects.filter(published=True).values(
            'id', 'title', 'content', 'author__name'
        )
        return JsonResponse(list(posts), safe=False)

    def post(self, request, *args, **kwargs):
        try:
            data = json.loads(request.body)
            post = Post.objects.create(
                title=data['title'],
                content=data['content'],
                author=request.user
            )
            return JsonResponse({
                'id': post.id,
                'title': post.title,
                'message': 'Post created successfully'
            }, status=201)
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=400)

class PostDetailAPIView(View):
    def get(self, request, pk, *args, **kwargs):
        try:
            post = Post.objects.select_related('author').get(pk=pk)
            return JsonResponse({
                'id': post.id,
                'title': post.title,
                'content': post.content,
                'author': post.author.name,
                'created_at': post.created_at.isoformat()
            })
        except Post.DoesNotExist:
            return JsonResponse({'error': 'Post not found'}, status=404)

    def put(self, request, pk, *args, **kwargs):
        try:
            post = Post.objects.get(pk=pk)
            data = json.loads(request.body)

            post.title = data.get('title', post.title)
            post.content = data.get('content', post.content)
            post.save()

            return JsonResponse({'message': 'Post updated successfully'})
        except Post.DoesNotExist:
            return JsonResponse({'error': 'Post not found'}, status=404)

    def delete(self, request, pk, *args, **kwargs):
        try:
            post = Post.objects.get(pk=pk)
            post.delete()
            return JsonResponse({'message': 'Post deleted successfully'})
        except Post.DoesNotExist:
            return JsonResponse({'error': 'Post not found'}, status=404)
```

## Wizard and Multi-Step Forms

Implement multi-step form wizards with CBVs.

```python
from django.views.generic import TemplateView
from django.shortcuts import redirect
from django.urls import reverse

class MultiStepFormMixin:
    """Mixin for multi-step form handling."""

    def get_step(self):
        return int(self.request.GET.get('step', 1))

    def get_session_key(self, step):
        return f'form_data_step_{step}'

    def save_step_data(self, step, data):
        self.request.session[self.get_session_key(step)] = data

    def get_step_data(self, step):
        return self.request.session.get(self.get_session_key(step), {})

    def clear_wizard_data(self):
        for key in list(self.request.session.keys()):
            if key.startswith('form_data_step_'):
                del self.request.session[key]

class UserRegistrationWizard(MultiStepFormMixin, TemplateView):
    template_name = 'registration/wizard.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        step = self.get_step()

        if step == 1:
            context['form'] = UserBasicInfoForm(initial=self.get_step_data(1))
        elif step == 2:
            context['form'] = UserProfileForm(initial=self.get_step_data(2))
        elif step == 3:
            context['form'] = UserPreferencesForm(initial=self.get_step_data(3))

        context['step'] = step
        context['total_steps'] = 3
        return context

    def post(self, request, *args, **kwargs):
        step = self.get_step()

        if step == 1:
            form = UserBasicInfoForm(request.POST)
            if form.is_valid():
                self.save_step_data(1, form.cleaned_data)
                return redirect(f'{reverse("registration-wizard")}?step=2')

        elif step == 2:
            form = UserProfileForm(request.POST)
            if form.is_valid():
                self.save_step_data(2, form.cleaned_data)
                return redirect(f'{reverse("registration-wizard")}?step=3')

        elif step == 3:
            form = UserPreferencesForm(request.POST)
            if form.is_valid():
                self.save_step_data(3, form.cleaned_data)

                # Create user with all data
                self.create_user()
                self.clear_wizard_data()

                return redirect('registration-complete')

        return self.render_to_response(self.get_context_data(form=form))

    def create_user(self):
        data1 = self.get_step_data(1)
        data2 = self.get_step_data(2)
        data3 = self.get_step_data(3)

        user = User.objects.create_user(
            username=data1['username'],
            email=data1['email'],
            password=data1['password']
        )

        Profile.objects.create(
            user=user,
            bio=data2['bio'],
            avatar=data2['avatar']
        )

        Preferences.objects.create(
            user=user,
            notifications=data3['notifications'],
            privacy=data3['privacy']
        )
```

## Redirect and Success URL Patterns

Master URL redirection strategies.

```python
from django.urls import reverse, reverse_lazy
from django.views.generic import CreateView, UpdateView

class PostCreateView(CreateView):
    model = Post
    fields = ['title', 'content']

    # Static success URL
    success_url = reverse_lazy('post-list')

    # Dynamic success URL based on object
    def get_success_url(self):
        return reverse('post-detail', kwargs={'pk': self.object.pk})

class PostUpdateView(UpdateView):
    model = Post
    fields = ['title', 'content']

    # Success URL based on form submission button
    def get_success_url(self):
        if 'save_and_continue' in self.request.POST:
            return reverse('post-update', kwargs={'pk': self.object.pk})
        elif 'save_and_add' in self.request.POST:
            return reverse('post-create')
        else:
            return reverse('post-detail', kwargs={'pk': self.object.pk})

class FlexibleRedirectMixin:
    """Redirect to next parameter or default."""

    def get_success_url(self):
        next_url = self.request.GET.get('next') or self.request.POST.get('next')
        if next_url:
            return next_url
        return super().get_success_url()

class PostDeleteView(FlexibleRedirectMixin, DeleteView):
    model = Post
    success_url = reverse_lazy('post-list')
```

## Template and Response Customization

Customize template selection and response rendering.

```python
from django.views.generic import DetailView
from django.http import HttpResponse
from django.template.loader import render_to_string

class PostDetailView(DetailView):
    model = Post

    # Dynamic template selection
    def get_template_names(self):
        # Mobile template
        if self.request.user_agent.is_mobile:
            return ['posts/detail_mobile.html']

        # Owner template
        if self.request.user == self.object.author:
            return ['posts/detail_owner.html']

        # Default template
        return ['posts/detail.html']

class ExportMixin:
    """Add export functionality to views."""

    def render_to_response(self, context, **response_kwargs):
        export_format = self.request.GET.get('format')

        if export_format == 'pdf':
            return self.render_to_pdf(context)
        elif export_format == 'csv':
            return self.render_to_csv(context)
        elif export_format == 'json':
            return self.render_to_json(context)

        return super().render_to_response(context, **response_kwargs)

    def render_to_pdf(self, context):
        # PDF rendering logic
        html = render_to_string(self.template_name, context)
        # Convert to PDF
        return HttpResponse(pdf_content, content_type='application/pdf')

    def render_to_csv(self, context):
        import csv
        from io import StringIO

        output = StringIO()
        writer = csv.writer(output)

        # Write CSV data
        for obj in context['object_list']:
            writer.writerow([obj.id, obj.title, obj.author.name])

        response = HttpResponse(output.getvalue(), content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="export.csv"'
        return response

    def render_to_json(self, context):
        from django.http import JsonResponse
        data = [
            {
                'id': obj.id,
                'title': obj.title,
                'author': obj.author.name
            }
            for obj in context['object_list']
        ]
        return JsonResponse(data, safe=False)

class PostListView(ExportMixin, ListView):
    model = Post
```

## Advanced Testing Patterns

Write comprehensive tests for CBVs.

```python
from django.test import TestCase, RequestFactory, Client
from django.contrib.auth.models import User, AnonymousUser
from django.urls import reverse

class PostViewTestCase(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass'
        )
        self.post = Post.objects.create(
            title='Test Post',
            author=self.user
        )

    def test_list_view_with_factory(self):
        """Test using RequestFactory."""
        request = self.factory.get('/posts/')
        request.user = self.user

        response = PostListView.as_view()(request)
        self.assertEqual(response.status_code, 200)

    def test_detail_view_with_client(self):
        """Test using Client."""
        client = Client()
        response = client.get(reverse('post-detail', kwargs={'pk': self.post.pk}))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Post')

    def test_create_view_requires_login(self):
        """Test login requirement."""
        request = self.factory.get('/posts/create/')
        request.user = AnonymousUser()

        response = PostCreateView.as_view()(request)
        self.assertEqual(response.status_code, 302)  # Redirect to login

    def test_update_view_author_only(self):
        """Test author-only access."""
        other_user = User.objects.create_user('other', password='pass')
        request = self.factory.get(f'/posts/{self.post.pk}/edit/')
        request.user = other_user

        with self.assertRaises(PermissionDenied):
            PostUpdateView.as_view()(request, pk=self.post.pk)

    def test_context_data(self):
        """Test context data."""
        request = self.factory.get('/posts/')
        request.user = self.user

        view = PostListView()
        view.request = request
        view.object_list = Post.objects.all()

        context = view.get_context_data()
        self.assertIn('object_list', context)
        self.assertIn('view', context)

    def test_form_valid(self):
        """Test form submission."""
        data = {
            'title': 'New Post',
            'content': 'Test content'
        }
        request = self.factory.post('/posts/create/', data)
        request.user = self.user

        response = PostCreateView.as_view()(request)
        self.assertEqual(response.status_code, 302)  # Redirect after success
        self.assertTrue(Post.objects.filter(title='New Post').exists())

    def test_queryset_optimization(self):
        """Test query optimization."""
        with self.assertNumQueries(1):
            request = self.factory.get('/posts/')
            request.user = self.user
            response = OptimizedPostListView.as_view()(request)
            list(response.context_data['object_list'])
```

## Django CBV Best Practices

1. **Follow MRO carefully** - Order mixins correctly: permission mixins first,
   then functionality mixins, base view last
2. **Use built-in mixins** - Leverage LoginRequiredMixin, UserPassesTestMixin
   instead of writing custom permission logic
3. **Override get_queryset()** - Customize querysets in get_queryset(), not
   in the class attribute
4. **Use get_context_data()** - Add extra context properly by calling super()
   first
5. **Keep views focused** - Each view should have a single responsibility
6. **Leverage generic views** - Use built-in generic views for CRUD operations
7. **Create custom mixins** - Extract reusable functionality into mixins
8. **Use get_form_kwargs()** - Pass additional data to forms through
   get_form_kwargs()
9. **Optimize queries** - Use select_related and prefetch_related in
   get_queryset()
10. **Test thoroughly** - Use RequestFactory for unit testing views
11. **Use success_url wisely** - Prefer get_success_url() for dynamic URLs
12. **Handle AJAX requests** - Check request.is_ajax() and return appropriate
    responses
13. **Implement proper pagination** - Always paginate large querysets
14. **Cache where appropriate** - Use method decorators for caching expensive
    views
15. **Document mixin order** - Comment why mixins are ordered a certain way

## Django CBV Common Pitfalls

1. **Wrong mixin order** - Incorrect MRO causes mixins to not work or override
   each other incorrectly
2. **Not calling super()** - Forgetting super() breaks the inheritance chain
3. **Hardcoded querysets** - Defining queryset as class attribute instead of
   using get_queryset()
4. **Overusing CBVs** - Using CBVs for simple views that would be clearer as
   functions
5. **Not understanding dispatch()** - Misusing dispatch() method leads to
   unexpected behavior
6. **Ignoring context_object_name** - Templates are less readable without
   proper context names
7. **Mixing concerns** - Putting too much logic in views instead of models or
   forms
8. **Not optimizing queries** - N+1 problems from not using
   select_related/prefetch_related
9. **Testing with client only** - Not unit testing with RequestFactory
10. **Complex inheritance chains** - Too many mixins make code hard to
    understand and debug
11. **Forgetting CSRF protection** - Disabling CSRF without understanding
    security implications
12. **Not handling exceptions** - Not catching DoesNotExist or PermissionDenied
    in custom methods
13. **Incorrect success_url usage** - Using reverse() instead of reverse_lazy()
    in class attributes
14. **Template name conflicts** - Not setting explicit template_name when
    needed
15. **Missing get_object() customization** - Not customizing get_object() for
    permission checks

## Resources

- [Django Class-Based Views Documentation](https://docs.djangoproject.com/en/stable/topics/class-based-views/)
- [Classy Class-Based Views](https://ccbv.co.uk/)
- [Django CBV Inspector](https://ccbv.co.uk/)
- [Django Mixins Documentation](https://docs.djangoproject.com/en/stable/topics/class-based-views/mixins/)
- [Django Forms in CBVs](https://docs.djangoproject.com/en/stable/topics/class-based-views/generic-editing/)

Install

Requires askill CLI v1.0+

Metadata

LicenseUnknown
Version-
Updated6d ago
PublisherTheBushidoCollective

Tags

apici-cddatabasegithub-actionssecuritytesting