Skip to content

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#

  1. Use forms - Always use Django forms for validation
  2. Check permissions - Verify user can perform action
  3. Use messages - Provide user feedback
  4. Handle errors - Use try/except for database operations
  5. Use get_or_create - Avoid duplicates
  6. Use update_fields - Only update changed fields
  7. Use bulk operations - For multiple objects
  8. Redirect after POST - Prevent duplicate submissions

✅ Next Steps#


Pro Tip: Always use commit=False in forms when you need to set additional fields before saving, then call save() manually!