CRUD Operations#
Complete guide to Create, Read, Update, and Delete operations in Django.
➕ Create#
Create Single Object#
from myapp.models import Post
# Method 1: Create and save
post = Post(title='My Post', content='Content here')
post.save()
# Method 2: Create in one step
post = Post.objects.create(
title='My Post',
content='Content here',
is_published=True
)
# Method 3: Create with get_or_create (avoid duplicates)
post, created = Post.objects.get_or_create(
title='My Post',
defaults={
'content': 'Content here',
'is_published': True
}
)
# Returns (object, created_boolean)
Create with ForeignKey#
from django.contrib.auth.models import User
user = User.objects.get(username='admin')
# Method 1
post = Post.objects.create(
title='My Post',
content='Content here',
author=user
)
# Method 2
post = Post(title='My Post', content='Content here')
post.author = user
post.save()
Create Multiple Objects#
# Bulk create (efficient)
posts = [
Post(title='Post 1', content='Content 1'),
Post(title='Post 2', content='Content 2'),
Post(title='Post 3', content='Content 3'),
]
Post.objects.bulk_create(posts)
# Bulk create with ignore conflicts (PostgreSQL)
Post.objects.bulk_create(posts, ignore_conflicts=True)
# Bulk create with update on conflict (PostgreSQL)
Post.objects.bulk_create(
posts,
update_conflicts=True,
update_fields=['content', 'updated_at'],
unique_fields=['title']
)
Create with ManyToMany#
post = Post.objects.create(title='My Post', content='Content')
# Add tags after creation
tag1 = Tag.objects.get(name='Django')
tag2 = Tag.objects.create(name='Python')
post.tags.add(tag1, tag2)
# Or
post.tags.set([tag1, tag2]) # Replace all tags
📖 Read#
Get Single Object#
# By primary key
post = Post.objects.get(pk=1)
post = Post.objects.get(id=1)
# By field
post = Post.objects.get(title='My Post')
# Safe get (returns None if not found)
post = Post.objects.filter(pk=1).first()
# Get or 404 (for views)
from django.shortcuts import get_object_or_404
post = get_object_or_404(Post, pk=1)
Get Multiple Objects#
# All objects
posts = Post.objects.all()
# Filtered
published_posts = Post.objects.filter(is_published=True)
# Exclude
draft_posts = Post.objects.exclude(is_published=True)
# Limit
recent_posts = Post.objects.all()[:10]
# See [ORM Queries](./orm-queries.md) for detailed query patterns
✏️ Update#
Update Single Object#
# Method 1: Modify and save
post = Post.objects.get(pk=1)
post.title = 'Updated Title'
post.content = 'Updated content'
post.save()
# Method 2: Update specific fields (more efficient)
post = Post.objects.get(pk=1)
post.title = 'Updated Title'
post.save(update_fields=['title'])
# Method 3: Update in one step
Post.objects.filter(pk=1).update(title='Updated Title')
Update Multiple Objects#
# Update all matching objects
Post.objects.filter(is_published=False).update(is_published=True)
# Update with conditions
Post.objects.filter(views__lt=10).update(is_featured=False)
# Update with F() expressions (update based on current value)
from django.db.models import F
Post.objects.all().update(views=F('views') + 1)
# Update with multiple fields
Post.objects.filter(pk=1).update(
title='New Title',
content='New Content',
views=F('views') + 1
)
Update or Create#
# Update if exists, create if not
post, created = Post.objects.update_or_create(
title='My Post', # Lookup field
defaults={
'content': 'Updated content',
'is_published': True
}
)
# Returns (object, created_boolean)
Bulk Update#
# Update multiple objects efficiently
posts = Post.objects.filter(is_published=False)
for post in posts:
post.is_published = True
post.views = 0
Post.objects.bulk_update(posts, ['is_published', 'views'])
# Update with different values
posts = [
Post(id=1, title='Title 1', views=10),
Post(id=2, title='Title 2', views=20),
]
Post.objects.bulk_update(posts, ['title', 'views'])
🗑️ Delete#
Delete Single Object#
# Method 1: Delete instance
post = Post.objects.get(pk=1)
post.delete()
# Method 2: Delete from queryset
Post.objects.filter(pk=1).delete()
# Method 3: Get or 404 then delete
from django.shortcuts import get_object_or_404
post = get_object_or_404(Post, pk=1)
post.delete()
Delete Multiple Objects#
# Delete all matching objects
Post.objects.filter(is_published=False).delete()
# Delete all objects (careful!)
Post.objects.all().delete()
# Delete with conditions
Post.objects.filter(created_at__lt=timezone.now() - timedelta(days=365)).delete()
🎯 CRUD in Views#
Create View#
# myapp/views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .models import Post
from .forms import PostForm
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
form.save_m2m() # Save ManyToMany if any
messages.success(request, 'Post created successfully!')
return redirect('myapp:post_detail', pk=post.pk)
else:
form = PostForm()
return render(request, 'myapp/post_form.html', {'form': form})
# Class-based view
from django.views.generic import CreateView
from django.urls import reverse_lazy
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = 'myapp/post_form.html'
success_url = reverse_lazy('myapp:post_list')
def form_valid(self, form):
form.instance.author = self.request.user
messages.success(self.request, 'Post created!')
return super().form_valid(form)
Read View#
# List view
def post_list(request):
posts = Post.objects.filter(is_published=True).order_by('-created_at')
return render(request, 'myapp/post_list.html', {'posts': posts})
# Detail view
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk, is_published=True)
return render(request, 'myapp/post_detail.html', {'post': post})
# Class-based views
from django.views.generic import ListView, DetailView
class PostListView(ListView):
model = Post
template_name = 'myapp/post_list.html'
context_object_name = 'posts'
queryset = Post.objects.filter(is_published=True)
paginate_by = 10
class PostDetailView(DetailView):
model = Post
template_name = 'myapp/post_detail.html'
queryset = Post.objects.filter(is_published=True)
Update View#
# Function-based view
def post_update(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()
messages.success(request, 'Post updated!')
return redirect('myapp:post_detail', pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, 'myapp/post_form.html', {'form': form, 'post': post})
# Class-based view
from django.views.generic import UpdateView
class PostUpdateView(UpdateView):
model = Post
form_class = PostForm
template_name = 'myapp/post_form.html'
def get_success_url(self):
return reverse_lazy('myapp:post_detail', kwargs={'pk': self.object.pk})
def form_valid(self, form):
messages.success(self.request, 'Post updated!')
return super().form_valid(form)
Delete View#
# Function-based view
def post_delete(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'POST':
post.delete()
messages.success(request, 'Post deleted!')
return redirect('myapp:post_list')
return render(request, 'myapp/post_confirm_delete.html', {'post': post})
# Class-based view
from django.views.generic import DeleteView
class PostDeleteView(DeleteView):
model = Post
template_name = 'myapp/post_confirm_delete.html'
success_url = reverse_lazy('myapp:post_list')
def delete(self, request, *args, **kwargs):
messages.success(self.request, 'Post deleted!')
return super().delete(request, *args, **kwargs)
📝 Forms for CRUD#
Create Form Template#
<!-- myapp/templates/myapp/post_form.html -->
{% extends 'myapp/base.html' %}
{% block content %}
<h1>{% if post %}Edit{% else %}Create{% endif %} Post</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
<a href="{% url 'myapp:post_list' %}">Cancel</a>
</form>
{% endblock %}
Delete Confirmation Template#
<!-- myapp/templates/myapp/post_confirm_delete.html -->
{% extends 'myapp/base.html' %}
{% block content %}
<h1>Delete Post</h1>
<p>Are you sure you want to delete "{{ post.title }}"?</p>
<form method="post">
{% csrf_token %}
<button type="submit">Yes, delete</button>
<a href="{% url 'myapp:post_detail' pk=post.pk %}">Cancel</a>
</form>
{% endblock %}
🔐 Permission Checks#
Check Permissions in Views#
from django.contrib.auth.decorators import login_required, permission_required
from django.core.exceptions import PermissionDenied
@login_required
def post_create(request):
# Only logged-in users can access
pass
@permission_required('myapp.add_post', raise_exception=True)
def post_create(request):
# Only users with permission can access
pass
# In view function
def post_update(request, pk):
post = get_object_or_404(Post, pk=pk)
# Check if user owns the post
if post.author != request.user and not request.user.is_staff:
raise PermissionDenied
# Update logic...
Class-Based View Permissions#
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
# Only logged-in users can access
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Post
permission_required = 'myapp.change_post'
# Requires permission
✅ Best Practices#
- ✅ Use forms - Always use Django forms for validation
- ✅ Check permissions - Verify user can perform action
- ✅ Use messages - Provide user feedback
- ✅ Handle errors - Use try/except for database operations
- ✅ Use get_or_create - Avoid duplicates
- ✅ Use update_fields - Only update changed fields
- ✅ Use bulk operations - For multiple objects
- ✅ Redirect after POST - Prevent duplicate submissions
✅ Next Steps#
- Learn Forms for advanced form handling
- Learn Advanced Topics for transactions
- Learn Admin Customization for admin CRUD
Pro Tip: Always use commit=False in forms when you need to set additional fields before saving, then call save() manually!