Class Based Views

Views for CRUD Operations

Django's generic views provide comprehensive support for Create, Read, Update, and Delete (CRUD) operations. These views handle the common patterns of object lifecycle management with minimal code while maintaining flexibility for customization.

Views for CRUD Operations

Django's generic views provide comprehensive support for Create, Read, Update, and Delete (CRUD) operations. These views handle the common patterns of object lifecycle management with minimal code while maintaining flexibility for customization.

CreateView - Object Creation

Basic CreateView

from django.views.generic import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy

class PostCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
    """Basic post creation view"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_form.html'
    success_url = reverse_lazy('blog:post_list')
    success_message = "Post created successfully!"
    
    def form_valid(self, form):
        """Set the author before saving"""
        form.instance.author = self.request.user
        return super().form_valid(form)

class CategoryCreateView(LoginRequiredMixin, CreateView):
    """Category creation with custom success URL"""
    model = Category
    fields = ['name', 'description', 'parent']
    template_name = 'blog/category_form.html'
    
    def get_success_url(self):
        """Redirect to the created category"""
        return reverse('blog:category_detail', kwargs={'pk': self.object.pk})
    
    def form_valid(self, form):
        """Add custom processing before save"""
        # Auto-generate slug from name
        form.instance.slug = slugify(form.instance.name)
        
        # Set creator
        form.instance.created_by = self.request.user
        
        return super().form_valid(form)

class AjaxCreateView(CreateView):
    """AJAX-enabled create view"""
    model = Post
    form_class = PostForm
    
    def form_valid(self, form):
        """Handle AJAX form submission"""
        response = super().form_valid(form)
        
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': True,
                'object_id': self.object.pk,
                'redirect_url': self.get_success_url(),
                'message': 'Post created successfully!'
            })
        
        return response
    
    def form_invalid(self, form):
        """Handle AJAX form errors"""
        response = super().form_invalid(form)
        
        if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return JsonResponse({
                'success': False,
                'errors': form.errors,
                'non_field_errors': form.non_field_errors()
            })
        
        return response

Advanced CreateView Patterns

class AdvancedPostCreateView(LoginRequiredMixin, CreateView):
    """Advanced post creation with file handling"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_create.html'
    
    def get_form_kwargs(self):
        """Pass additional data to form"""
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs
    
    def get_context_data(self, **kwargs):
        """Add extra context for template"""
        context = super().get_context_data(**kwargs)
        
        context.update({
            'categories': Category.objects.filter(active=True),
            'tags': Tag.objects.all(),
            'user_drafts': Post.objects.filter(
                author=self.request.user,
                status='draft'
            ).count(),
        })
        
        return context
    
    def form_valid(self, form):
        """Custom form processing"""
        # Set author
        form.instance.author = self.request.user
        
        # Handle draft vs publish
        if 'save_draft' in self.request.POST:
            form.instance.status = 'draft'
        elif 'publish' in self.request.POST:
            form.instance.status = 'published'
            form.instance.published_at = timezone.now()
        
        # Process uploaded images
        self.process_uploaded_files(form)
        
        response = super().form_valid(form)
        
        # Send notification if published
        if form.instance.status == 'published':
            self.send_publication_notification()
        
        return response
    
    def process_uploaded_files(self, form):
        """Process any uploaded files"""
        uploaded_files = self.request.FILES.getlist('additional_files')
        
        for uploaded_file in uploaded_files:
            # Validate file
            if self.validate_file(uploaded_file):
                # Create attachment record
                PostAttachment.objects.create(
                    post=form.instance,
                    file=uploaded_file,
                    uploaded_by=self.request.user
                )
    
    def validate_file(self, uploaded_file):
        """Validate uploaded file"""
        # Check file size (10MB limit)
        if uploaded_file.size > 10 * 1024 * 1024:
            messages.error(self.request, f'File {uploaded_file.name} is too large')
            return False
        
        # Check file type
        allowed_types = ['image/jpeg', 'image/png', 'application/pdf']
        if uploaded_file.content_type not in allowed_types:
            messages.error(self.request, f'File type {uploaded_file.content_type} not allowed')
            return False
        
        return True
    
    def send_publication_notification(self):
        """Send notification when post is published"""
        # Notify subscribers
        subscribers = User.objects.filter(
            profile__subscribed_to_notifications=True
        )
        
        for subscriber in subscribers:
            send_notification.delay(
                user_id=subscriber.id,
                message=f'New post published: {self.object.title}',
                post_id=self.object.id
            )
    
    def get_success_url(self):
        """Dynamic success URL based on action"""
        if 'save_draft' in self.request.POST:
            messages.success(self.request, 'Post saved as draft')
            return reverse('blog:post_edit', kwargs={'pk': self.object.pk})
        else:
            messages.success(self.request, 'Post published successfully!')
            return self.object.get_absolute_url()

class BulkCreateView(LoginRequiredMixin, TemplateView):
    """Handle bulk object creation"""
    template_name = 'blog/bulk_create.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        
        # Create multiple form instances
        context['forms'] = [PostForm(prefix=str(i)) for i in range(5)]
        
        return context
    
    def post(self, request, *args, **kwargs):
        """Handle bulk form submission"""
        forms = [PostForm(request.POST, prefix=str(i)) for i in range(5)]
        
        valid_forms = []
        invalid_forms = []
        
        for form in forms:
            if form.is_valid() and form.has_changed():
                valid_forms.append(form)
            elif form.has_changed():
                invalid_forms.append(form)
        
        if invalid_forms:
            # Some forms have errors
            context = self.get_context_data()
            context['forms'] = forms
            return self.render_to_response(context)
        
        # Save valid forms
        created_objects = []
        for form in valid_forms:
            obj = form.save(commit=False)
            obj.author = request.user
            obj.save()
            form.save_m2m()
            created_objects.append(obj)
        
        messages.success(
            request, 
            f'{len(created_objects)} posts created successfully!'
        )
        
        return redirect('blog:post_list')

UpdateView - Object Modification

Basic UpdateView

class PostUpdateView(LoginRequiredMixin, UpdateView):
    """Basic post update view"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_form.html'
    
    def get_queryset(self):
        """Only allow editing own posts"""
        return Post.objects.filter(author=self.request.user)
    
    def get_success_url(self):
        return self.object.get_absolute_url()

class StaffPostUpdateView(LoginRequiredMixin, UpdateView):
    """Staff can edit any post"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_form.html'
    
    def dispatch(self, request, *args, **kwargs):
        """Check permissions"""
        if not request.user.is_staff:
            raise PermissionDenied("Only staff can edit posts")
        return super().dispatch(request, *args, **kwargs)

class ConditionalUpdateView(LoginRequiredMixin, UpdateView):
    """Update with conditional permissions"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_form.html'
    
    def get_object(self, queryset=None):
        """Get object with permission check"""
        obj = super().get_object(queryset)
        
        # Check if user can edit this post
        if not self.can_edit_post(obj):
            raise PermissionDenied("You don't have permission to edit this post")
        
        return obj
    
    def can_edit_post(self, post):
        """Check if user can edit the post"""
        user = self.request.user
        
        # Author can always edit
        if post.author == user:
            return True
        
        # Staff can edit any post
        if user.is_staff:
            return True
        
        # Editors can edit posts in their categories
        if hasattr(user, 'profile') and user.profile.is_editor:
            return post.category in user.profile.editable_categories.all()
        
        return False

Advanced UpdateView Patterns

class AdvancedPostUpdateView(LoginRequiredMixin, UpdateView):
    """Advanced update with version control and audit trail"""
    model = Post
    form_class = PostForm
    template_name = 'blog/post_update.html'
    
    def get_object(self, queryset=None):
        """Get object and create version backup"""
        obj = super().get_object(queryset)
        
        # Create version backup before editing
        self.create_version_backup(obj)
        
        return obj
    
    def create_version_backup(self, obj):
        """Create a backup version of the object"""
        PostVersion.objects.create(
            post=obj,
            title=obj.title,
            content=obj.content,
            status=obj.status,
            version_created_by=self.request.user,
            version_created_at=timezone.now()
        )
    
    def get_context_data(self, **kwargs):
        """Add version history to context"""
        context = super().get_context_data(**kwargs)
        
        context.update({
            'versions': PostVersion.objects.filter(
                post=self.object
            ).order_by('-version_created_at')[:10],
            'can_delete': self.can_delete_post(),
            'edit_history': self.get_edit_history(),
        })
        
        return context
    
    def can_delete_post(self):
        """Check if user can delete this post"""
        return (
            self.object.author == self.request.user or 
            self.request.user.is_staff
        )
    
    def get_edit_history(self):
        """Get edit history for this post"""
        return PostEditLog.objects.filter(
            post=self.object
        ).select_related('user').order_by('-timestamp')[:20]
    
    def form_valid(self, form):
        """Handle form validation with change tracking"""
        # Track changes
        changes = self.track_changes(form)
        
        # Handle status changes
        old_status = self.object.status
        new_status = form.cleaned_data.get('status')
        
        if old_status != new_status:
            self.handle_status_change(old_status, new_status)
        
        response = super().form_valid(form)
        
        # Log the edit
        self.log_edit(changes)
        
        return response
    
    def track_changes(self, form):
        """Track what fields were changed"""
        changes = {}
        
        for field_name, field in form.fields.items():
            old_value = getattr(self.object, field_name, None)
            new_value = form.cleaned_data.get(field_name)
            
            if old_value != new_value:
                changes[field_name] = {
                    'old': old_value,
                    'new': new_value
                }
        
        return changes
    
    def handle_status_change(self, old_status, new_status):
        """Handle post status changes"""
        if old_status == 'draft' and new_status == 'published':
            # Publishing a draft
            self.object.published_at = timezone.now()
            self.send_publication_notification()
        
        elif old_status == 'published' and new_status == 'draft':
            # Unpublishing a post
            self.object.published_at = None
            self.send_unpublication_notification()
    
    def log_edit(self, changes):
        """Log the edit action"""
        PostEditLog.objects.create(
            post=self.object,
            user=self.request.user,
            changes=changes,
            timestamp=timezone.now(),
            ip_address=self.get_client_ip()
        )
    
    def get_client_ip(self):
        """Get client IP address"""
        x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            return x_forwarded_for.split(',')[0]
        return self.request.META.get('REMOTE_ADDR')

class PartialUpdateView(UpdateView):
    """Handle partial updates (PATCH-like behavior)"""
    model = Post
    fields = ['title', 'content', 'status']
    
    def get_form_kwargs(self):
        """Only include changed fields in form"""
        kwargs = super().get_form_kwargs()
        
        # For PATCH requests, only update provided fields
        if self.request.method == 'PATCH':
            import json
            try:
                patch_data = json.loads(self.request.body)
                # Only include fields that are being updated
                kwargs['data'] = {
                    field: value for field, value in patch_data.items()
                    if field in self.fields
                }
            except json.JSONDecodeError:
                pass
        
        return kwargs
    
    def form_valid(self, form):
        """Save only changed fields"""
        if self.request.method == 'PATCH':
            # Only save fields that were actually changed
            changed_fields = []
            for field_name in form.changed_data:
                if field_name in self.fields:
                    setattr(self.object, field_name, form.cleaned_data[field_name])
                    changed_fields.append(field_name)
            
            if changed_fields:
                self.object.save(update_fields=changed_fields)
            
            return JsonResponse({
                'success': True,
                'updated_fields': changed_fields,
                'object_id': self.object.pk
            })
        
        return super().form_valid(form)

DeleteView - Object Removal

Basic DeleteView

class PostDeleteView(LoginRequiredMixin, DeleteView):
    """Basic post deletion view"""
    model = Post
    template_name = 'blog/post_confirm_delete.html'
    success_url = reverse_lazy('blog:post_list')
    
    def get_queryset(self):
        """Only allow deleting own posts"""
        return Post.objects.filter(author=self.request.user)
    
    def delete(self, request, *args, **kwargs):
        """Add success message"""
        messages.success(request, 'Post deleted successfully!')
        return super().delete(request, *args, **kwargs)

class SafeDeleteView(LoginRequiredMixin, DeleteView):
    """Soft delete implementation"""
    model = Post
    template_name = 'blog/post_confirm_delete.html'
    success_url = reverse_lazy('blog:post_list')
    
    def delete(self, request, *args, **kwargs):
        """Soft delete instead of hard delete"""
        self.object = self.get_object()
        
        # Mark as deleted instead of actually deleting
        self.object.deleted = True
        self.object.deleted_at = timezone.now()
        self.object.deleted_by = request.user
        self.object.save()
        
        messages.success(request, 'Post moved to trash')
        return HttpResponseRedirect(self.get_success_url())

class ConditionalDeleteView(LoginRequiredMixin, DeleteView):
    """Delete with conditions and confirmations"""
    model = Post
    template_name = 'blog/post_confirm_delete.html'
    
    def get_object(self, queryset=None):
        """Get object with permission check"""
        obj = super().get_object(queryset)
        
        if not self.can_delete_post(obj):
            raise PermissionDenied("You don't have permission to delete this post")
        
        return obj
    
    def can_delete_post(self, post):
        """Check if post can be deleted"""
        user = self.request.user
        
        # Author can delete their own posts
        if post.author == user:
            return True
        
        # Staff can delete any post
        if user.is_staff:
            return True
        
        # Can't delete published posts with comments
        if post.status == 'published' and post.comments.exists():
            return False
        
        return False
    
    def get_context_data(self, **kwargs):
        """Add deletion impact information"""
        context = super().get_context_data(**kwargs)
        
        context.update({
            'comment_count': self.object.comments.count(),
            'has_attachments': self.object.attachments.exists(),
            'related_posts': Post.objects.filter(
                related_posts=self.object
            ).count(),
            'deletion_warnings': self.get_deletion_warnings(),
        })
        
        return context
    
    def get_deletion_warnings(self):
        """Get warnings about deletion consequences"""
        warnings = []
        
        if self.object.comments.exists():
            warnings.append(f'This post has {self.object.comments.count()} comments that will be deleted')
        
        if self.object.attachments.exists():
            warnings.append(f'This post has {self.object.attachments.count()} file attachments that will be deleted')
        
        if self.object.featured:
            warnings.append('This is a featured post')
        
        return warnings

Advanced DeleteView Patterns

class AdvancedDeleteView(LoginRequiredMixin, DeleteView):
    """Advanced deletion with backup and recovery"""
    model = Post
    template_name = 'blog/post_confirm_delete.html'
    
    def delete(self, request, *args, **kwargs):
        """Delete with backup and cleanup"""
        self.object = self.get_object()
        
        # Create backup before deletion
        backup_data = self.create_backup()
        
        # Clean up related objects
        self.cleanup_related_objects()
        
        # Log deletion
        self.log_deletion()
        
        # Perform actual deletion
        response = super().delete(request, *args, **kwargs)
        
        # Send notification
        self.send_deletion_notification(backup_data)
        
        return response
    
    def create_backup(self):
        """Create backup of object before deletion"""
        backup_data = {
            'id': self.object.id,
            'title': self.object.title,
            'content': self.object.content,
            'author_id': self.object.author.id,
            'created_at': self.object.created_at,
            'deleted_at': timezone.now(),
            'deleted_by': self.request.user.id,
        }
        
        # Store backup
        PostBackup.objects.create(
            original_id=self.object.id,
            backup_data=backup_data,
            created_by=self.request.user
        )
        
        return backup_data
    
    def cleanup_related_objects(self):
        """Clean up related objects"""
        # Delete comments
        self.object.comments.all().delete()
        
        # Delete attachments and files
        for attachment in self.object.attachments.all():
            if attachment.file:
                attachment.file.delete()
            attachment.delete()
        
        # Remove from favorites
        self.object.favorited_by.clear()
        
        # Update related posts
        related_posts = Post.objects.filter(related_posts=self.object)
        for post in related_posts:
            post.related_posts.remove(self.object)
    
    def log_deletion(self):
        """Log the deletion action"""
        DeletionLog.objects.create(
            object_type='Post',
            object_id=self.object.id,
            object_title=self.object.title,
            deleted_by=self.request.user,
            deleted_at=timezone.now(),
            ip_address=self.get_client_ip(),
            reason=self.request.POST.get('deletion_reason', '')
        )
    
    def send_deletion_notification(self, backup_data):
        """Send notification about deletion"""
        # Notify author if different from deleter
        if self.object.author != self.request.user:
            send_notification.delay(
                user_id=self.object.author.id,
                message=f'Your post "{self.object.title}" was deleted',
                notification_type='post_deleted'
            )
        
        # Notify administrators
        admin_users = User.objects.filter(is_staff=True)
        for admin in admin_users:
            send_notification.delay(
                user_id=admin.id,
                message=f'Post "{self.object.title}" was deleted by {self.request.user.username}',
                notification_type='admin_post_deleted'
            )

class BulkDeleteView(LoginRequiredMixin, TemplateView):
    """Handle bulk deletion of objects"""
    template_name = 'blog/bulk_delete.html'
    
    def post(self, request, *args, **kwargs):
        """Handle bulk deletion"""
        object_ids = request.POST.getlist('object_ids')
        
        if not object_ids:
            messages.error(request, 'No objects selected for deletion')
            return redirect('blog:post_list')
        
        # Get objects user can delete
        queryset = Post.objects.filter(
            id__in=object_ids,
            author=request.user
        )
        
        if not queryset.exists():
            messages.error(request, 'No valid objects found for deletion')
            return redirect('blog:post_list')
        
        # Confirm deletion
        if 'confirm' in request.POST:
            count = queryset.count()
            
            # Log bulk deletion
            BulkDeletionLog.objects.create(
                user=request.user,
                object_type='Post',
                object_count=count,
                object_ids=object_ids,
                timestamp=timezone.now()
            )
            
            # Delete objects
            queryset.delete()
            
            messages.success(request, f'{count} posts deleted successfully')
            return redirect('blog:post_list')
        
        # Show confirmation page
        context = {
            'objects': queryset,
            'object_count': queryset.count(),
        }
        
        return self.render_to_response(context)

Complete CRUD Example

Unified CRUD Views

class PostCRUDMixin:
    """Common functionality for post CRUD operations"""
    model = Post
    
    def get_queryset(self):
        """Base queryset with optimizations"""
        return Post.objects.select_related('author', 'category').prefetch_related('tags')
    
    def get_context_data(self, **kwargs):
        """Common context data"""
        context = super().get_context_data(**kwargs)
        
        context.update({
            'categories': Category.objects.filter(active=True),
            'user_post_count': Post.objects.filter(author=self.request.user).count(),
        })
        
        return context

class PostListView(PostCRUDMixin, ListView):
    """List all posts"""
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 15

class PostDetailView(PostCRUDMixin, DetailView):
    """View single post"""
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

class PostCreateView(LoginRequiredMixin, PostCRUDMixin, CreateView):
    """Create new post"""
    form_class = PostForm
    template_name = 'blog/post_form.html'
    
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

class PostUpdateView(LoginRequiredMixin, PostCRUDMixin, UpdateView):
    """Update existing post"""
    form_class = PostForm
    template_name = 'blog/post_form.html'
    
    def get_queryset(self):
        return super().get_queryset().filter(author=self.request.user)

class PostDeleteView(LoginRequiredMixin, PostCRUDMixin, DeleteView):
    """Delete post"""
    template_name = 'blog/post_confirm_delete.html'
    success_url = reverse_lazy('blog:post_list')
    
    def get_queryset(self):
        return super().get_queryset().filter(author=self.request.user)

# URL patterns
urlpatterns = [
    path('', PostListView.as_view(), name='post_list'),
    path('<int:pk>/', PostDetailView.as_view(), name='post_detail'),
    path('create/', PostCreateView.as_view(), name='post_create'),
    path('<int:pk>/edit/', PostUpdateView.as_view(), name='post_edit'),
    path('<int:pk>/delete/', PostDeleteView.as_view(), name='post_delete'),
]

CRUD views provide the foundation for most web application functionality. Django's generic views handle the common patterns while allowing extensive customization for specific requirements. Understanding these patterns enables rapid development of robust, maintainable applications with proper security, validation, and user experience considerations.