Django CRUD Operations: Create, Read, Update, Delete with Models

Django CRUD operations form the foundation of database interactions enabling applications to Create, Read, Update, and Delete records using Django's powerful Object-Relational Mapping (ORM) system. Understanding CRUD operations is essential for building dynamic web applications that manage data effectively from user registration and blog posts to e-commerce products and customer records. Django ORM abstracts SQL queries into Python code making database operations intuitive, secure, and database-agnostic supporting PostgreSQL, MySQL, SQLite, and other databases. Mastering CRUD operations enables building complete data-driven applications with proper data management, validation, and business logic implementation supporting everything from simple contact forms to complex enterprise resource planning systems.
Create Operations
Creating records in Django involves instantiating model objects and calling save() method or using create() for single-line creation. Django provides multiple approaches including bulk creation for performance, get_or_create() to avoid duplicates, and update_or_create() for upsert operations. Understanding creation patterns enables efficient data insertion with proper validation and error handling.
# Django Create Operations
from django.shortcuts import render, redirect
from .models import Post, Author, Category
from django.utils import timezone
# Method 1: Create and save
def create_post_view(request):
if request.method == 'POST':
# Create new instance
post = Post()
post.title = request.POST['title']
post.content = request.POST['content']
post.author = request.user
post.published_date = timezone.now()
post.save() # Save to database
return redirect('post_detail', pk=post.pk)
# Method 2: Create in one line
def create_post_shorthand(request):
post = Post.objects.create(
title='My New Post',
content='Post content here',
author=request.user,
published_date=timezone.now()
)
# Already saved, no need to call save()
return redirect('post_detail', pk=post.pk)
# Method 3: Bulk create (efficient for multiple records)
def bulk_create_posts():
posts = [
Post(title='Post 1', content='Content 1', author_id=1),
Post(title='Post 2', content='Content 2', author_id=1),
Post(title='Post 3', content='Content 3', author_id=1),
]
Post.objects.bulk_create(posts)
# Creates all records in single database query
# Method 4: Get or create (avoid duplicates)
def get_or_create_category(name):
category, created = Category.objects.get_or_create(
name=name,
defaults={'description': 'Default description'}
)
if created:
print(f"Created new category: {name}")
else:
print(f"Category already exists: {name}")
return category
# Method 5: Update or create (upsert)
def update_or_create_author(username):
author, created = Author.objects.update_or_create(
username=username,
defaults={
'email': f'{username}@example.com',
'bio': 'Default bio'
}
)
return author
# With validation
def create_with_validation(request):
try:
post = Post(
title=request.POST['title'],
content=request.POST['content'],
author=request.user
)
post.full_clean() # Validate before saving
post.save()
return redirect('success')
except ValidationError as e:
return render(request, 'form.html', {'errors': e.message_dict})
# Create with related objects
def create_post_with_tags(request):
post = Post.objects.create(
title='Django Tutorial',
content='Learn Django',
author=request.user
)
# Add many-to-many relationships
post.tags.add(tag1, tag2, tag3)
# Or create and add
post.tags.create(name='Python')
return postRead Operations
Reading data from Django models uses QuerySet API providing methods like all(), filter(), get(), and exclude() for retrieving records. Understanding read operations enables querying databases efficiently with filtering, ordering, and relationship traversal. Django QuerySets are lazy meaning queries execute only when data is evaluated optimizing performance.
# Django Read Operations
from .models import Post, Author, Comment
from django.shortcuts import render, get_object_or_404
# Get all records
def list_all_posts(request):
posts = Post.objects.all()
return render(request, 'posts/list.html', {'posts': posts})
# Get single record
def get_post_detail(request, pk):
# Method 1: get() - raises exception if not found
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
return render(request, '404.html')
# Method 2: get_object_or_404 (recommended)
post = get_object_or_404(Post, pk=pk)
return render(request, 'posts/detail.html', {'post': post})
# Filter records
def filter_posts(request):
# Single condition
published_posts = Post.objects.filter(status='published')
# Multiple conditions (AND)
recent_posts = Post.objects.filter(
status='published',
published_date__gte=timezone.now() - timedelta(days=7)
)
# Exclude records
active_posts = Post.objects.exclude(status='draft')
# Chaining filters
posts = Post.objects.filter(status='published').exclude(title__icontains='test')
return render(request, 'posts/list.html', {'posts': posts})
# Ordering
def ordered_posts(request):
# Ascending order
posts = Post.objects.order_by('published_date')
# Descending order
posts = Post.objects.order_by('-published_date')
# Multiple fields
posts = Post.objects.order_by('-published_date', 'title')
return render(request, 'posts/list.html', {'posts': posts})
# Slicing (limit results)
def limited_posts(request):
# First 5 posts
posts = Post.objects.all()[:5]
# Posts 10-20
posts = Post.objects.all()[10:20]
# First post
first_post = Post.objects.first()
# Last post
last_post = Post.objects.last()
return render(request, 'posts/list.html', {'posts': posts})
# Related objects
def posts_with_relations(request):
# Forward relationship
post = Post.objects.get(pk=1)
author = post.author # Access related author
# Reverse relationship
author = Author.objects.get(pk=1)
author_posts = author.post_set.all() # All posts by this author
# Or with related_name:
author_posts = author.posts.all()
# Prefetch related (optimization)
posts = Post.objects.select_related('author').prefetch_related('comments')
return render(request, 'posts/list.html', {'posts': posts})
# Field lookups
def advanced_queries(request):
# Exact match
posts = Post.objects.filter(title__exact='Django Tutorial')
# Case-insensitive
posts = Post.objects.filter(title__iexact='django tutorial')
# Contains
posts = Post.objects.filter(title__contains='Django')
# Starts with / Ends with
posts = Post.objects.filter(title__startswith='Django')
posts = Post.objects.filter(title__endswith='Tutorial')
# Greater than / Less than
posts = Post.objects.filter(views__gt=100)
posts = Post.objects.filter(views__lte=50)
# Date lookups
posts = Post.objects.filter(published_date__year=2024)
posts = Post.objects.filter(published_date__month=1)
return posts
# Exists check
def check_exists(request):
has_posts = Post.objects.filter(author=request.user).exists()
# Count
post_count = Post.objects.filter(status='published').count()
return render(request, 'dashboard.html', {
'has_posts': has_posts,
'post_count': post_count
})Update Operations
Updating records in Django involves retrieving objects, modifying attributes, and saving changes or using update() method for bulk updates. Understanding update operations enables modifying existing data efficiently with proper validation and handling of concurrent modifications. Django provides both instance-level updates for single records and QuerySet updates for bulk operations.
# Django Update Operations
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from .models import Post
from .forms import PostForm
# Method 1: Update instance and save
def update_post_view(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
post.title = request.POST['title']
post.content = request.POST['content']
post.updated_at = timezone.now()
post.save() # Save changes
return redirect('post_detail', pk=post.pk)
return render(request, 'posts/edit.html', {'post': post})
# Method 2: Update specific fields only
def update_specific_fields(request, pk):
post = get_object_or_404(Post, pk=pk)
post.views += 1 # Increment views
post.save(update_fields=['views']) # Only update views field
# More efficient than saving all fields
# Method 3: Bulk update (QuerySet update)
def bulk_update_posts():
# Update multiple records at once
Post.objects.filter(status='draft').update(
status='published',
published_date=timezone.now()
)
# Returns number of rows updated
# Note: Does NOT call save() method or signals
# Update with form
def update_post_with_form(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'posts/edit.html', {'form': form, 'post': post})
# Conditional update
def conditional_update(post_id, user):
post = get_object_or_404(Post, pk=post_id)
# Check permissions
if post.author != user:
raise PermissionDenied
post.title = 'Updated Title'
post.save()
# Update with F expressions (avoid race conditions)
from django.db.models import F
def increment_views(request, pk):
# Thread-safe increment
Post.objects.filter(pk=pk).update(views=F('views') + 1)
post = get_object_or_404(Post, pk=pk)
return render(request, 'posts/detail.html', {'post': post})
# Update related objects
def update_with_relations(request, pk):
post = get_object_or_404(Post, pk=pk)
# Update many-to-many
post.tags.clear() # Remove all tags
post.tags.add(tag1, tag2) # Add new tags
post.tags.set([tag1, tag2, tag3]) # Replace all tags
# Update or create related
comment, created = post.comments.update_or_create(
user=request.user,
defaults={'text': 'Updated comment'}
)
# Partial update with validation
def update_with_validation(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
post.title = request.POST['title']
try:
post.full_clean() # Validate
post.save()
return redirect('post_detail', pk=post.pk)
except ValidationError as e:
return render(request, 'posts/edit.html', {
'post': post,
'errors': e.message_dict
})
# Atomic update (transaction)
from django.db import transaction
@transaction.atomic
def atomic_update(post_id):
post = Post.objects.select_for_update().get(pk=post_id)
post.status = 'published'
post.published_date = timezone.now()
post.save()
# Changes rolled back if exception occursDelete Operations
Deleting records in Django uses delete() method on model instances or QuerySets supporting single and bulk deletions. Understanding delete operations includes handling cascading deletes through on_delete options, soft deletes for data retention, and proper authorization checks. Django automatically handles related object deletion based on ForeignKey on_delete settings ensuring referential integrity.
# Django Delete Operations
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from .models import Post, Comment
# Method 1: Delete single instance
def delete_post_view(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
# Check permissions
if post.author != request.user:
messages.error(request, 'You cannot delete this post')
return redirect('post_detail', pk=pk)
post.delete() # Delete from database
messages.success(request, 'Post deleted successfully')
return redirect('post_list')
return render(request, 'posts/confirm_delete.html', {'post': post})
# Method 2: Bulk delete
def bulk_delete_posts():
# Delete multiple records
deleted_count, _ = Post.objects.filter(status='draft').delete()
print(f"Deleted {deleted_count} draft posts")
# Delete all (dangerous!)
# Post.objects.all().delete()
# Delete with related objects
def delete_with_cascade():
# When ForeignKey has on_delete=models.CASCADE
author = Author.objects.get(pk=1)
author.delete() # Also deletes all related posts
# Soft delete (mark as deleted without removing)
class Post(models.Model):
title = models.CharField(max_length=200)
is_deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
def soft_delete(self):
self.is_deleted = True
self.deleted_at = timezone.now()
self.save()
def restore(self):
self.is_deleted = False
self.deleted_at = None
self.save()
def soft_delete_view(request, pk):
post = get_object_or_404(Post, pk=pk)
post.soft_delete()
return redirect('post_list')
# Custom queryset for soft deletes
class PostQuerySet(models.QuerySet):
def active(self):
return self.filter(is_deleted=False)
def deleted(self):
return self.filter(is_deleted=True)
class PostManager(models.Manager):
def get_queryset(self):
return PostQuerySet(self.model, using=self._db)
def active(self):
return self.get_queryset().active()
# Usage
active_posts = Post.objects.active()
# Delete confirmation view
def delete_with_confirmation(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
if request.POST.get('confirm') == 'yes':
post_title = post.title
post.delete()
messages.success(request, f'Post "{post_title}" deleted')
return redirect('post_list')
return render(request, 'posts/delete_confirm.html', {'post': post})
# Delete with backup
import json
def delete_with_backup(request, pk):
post = get_object_or_404(Post, pk=pk)
# Backup before deleting
backup = {
'title': post.title,
'content': post.content,
'author_id': post.author_id,
'created_at': str(post.created_at)
}
# Save to file or archive table
with open(f'backups/post_{pk}.json', 'w') as f:
json.dump(backup, f)
post.delete()
return redirect('post_list')
# Atomic delete with related cleanup
from django.db import transaction
@transaction.atomic
def delete_with_cleanup(request, pk):
post = get_object_or_404(Post, pk=pk)
# Delete related objects first
post.comments.all().delete()
post.likes.all().delete()
# Delete files if any
if post.image:
post.image.delete(save=False)
# Finally delete post
post.delete()
return redirect('post_list')CRUD Best Practices
Effective CRUD operations follow patterns ensuring data integrity, security, and performance. Always validate data before creating or updating using full_clean() or forms. Check user permissions before allowing updates or deletions preventing unauthorized access. Use transactions for operations affecting multiple records ensuring atomic operations. Implement soft deletes for important data allowing recovery. Use select_for_update() in transactions preventing race conditions. Optimize queries with select_related() and prefetch_related() reducing database hits. Handle exceptions properly providing meaningful error messages. Log important operations for audit trails. These practices ensure robust data management supporting reliable applications from simple blogs to complex enterprise systems.
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


