Use when Django Class-Based Views for building modular, reusable views. Use when creating CRUD operations and complex view logic.
Provides Django Class-Based View patterns for building modular, reusable CRUD operations and complex view logic. Use when creating Django views with mixins, custom methods, and advanced patterns like multi-step forms, filtering, and API endpoints.
/plugin marketplace add TheBushidoCollective/han/plugin install jutsu-cucumber@hanThis skill is limited to using the following tools:
Master Django Class-Based Views for building modular, reusable view logic with proper separation of concerns.
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')
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
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'
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)
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)
)
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
})
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
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
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
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
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
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!'
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
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
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)
Use django-cbv-patterns when building modern, production-ready applications that require advanced patterns, best practices, and optimal performance.
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)
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']
)
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')
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
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'])