Claude-skill-registry django-cbv-patterns
Use when Django Class-Based Views for building modular, reusable views. Use when creating CRUD operations and complex view logic.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/django-cbv-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-django-cbv-patterns && rm -rf "$T"
skills/data/django-cbv-patterns/SKILL.mdDjango 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.
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.
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.
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.
# 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.
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.
# 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.
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.
# 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
- Follow MRO carefully - Order mixins correctly: permission mixins first, then functionality mixins, base view last
- Use built-in mixins - Leverage LoginRequiredMixin, UserPassesTestMixin instead of writing custom permission logic
- Override get_queryset() - Customize querysets in get_queryset(), not in the class attribute
- Use get_context_data() - Add extra context properly by calling super() first
- Keep views focused - Each view should have a single responsibility
- Leverage generic views - Use built-in generic views for CRUD operations
- Create custom mixins - Extract reusable functionality into mixins
- Use get_form_kwargs() - Pass additional data to forms through get_form_kwargs()
- Optimize queries - Use select_related and prefetch_related in get_queryset()
- Test thoroughly - Use RequestFactory for unit testing views
- Use success_url wisely - Prefer get_success_url() for dynamic URLs
- Handle AJAX requests - Check request.is_ajax() and return appropriate responses
- Implement proper pagination - Always paginate large querysets
- Cache where appropriate - Use method decorators for caching expensive views
- Document mixin order - Comment why mixins are ordered a certain way
Django CBV Common Pitfalls
- Wrong mixin order - Incorrect MRO causes mixins to not work or override each other incorrectly
- Not calling super() - Forgetting super() breaks the inheritance chain
- Hardcoded querysets - Defining queryset as class attribute instead of using get_queryset()
- Overusing CBVs - Using CBVs for simple views that would be clearer as functions
- Not understanding dispatch() - Misusing dispatch() method leads to unexpected behavior
- Ignoring context_object_name - Templates are less readable without proper context names
- Mixing concerns - Putting too much logic in views instead of models or forms
- Not optimizing queries - N+1 problems from not using select_related/prefetch_related
- Testing with client only - Not unit testing with RequestFactory
- Complex inheritance chains - Too many mixins make code hard to understand and debug
- Forgetting CSRF protection - Disabling CSRF without understanding security implications
- Not handling exceptions - Not catching DoesNotExist or PermissionDenied in custom methods
- Incorrect success_url usage - Using reverse() instead of reverse_lazy() in class attributes
- Template name conflicts - Not setting explicit template_name when needed
- Missing get_object() customization - Not customizing get_object() for permission checks