- name
- django-cbv-patterns
- user-invocable
- false
- 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)
عرض على GitHub