Topic 10 / 12

Basic QuerySets: CRUD Operations

~19 min read  //  Django Part 1 Series  //  Coding India

1. The Django Shell

The Django shell is an interactive Python interpreter that has your entire project loaded. It is the perfect playground for experimenting with the ORM before putting code into your views. Start it with:

python manage.py shell

2. CREATE — Adding Records

There are two ways to create new database records:

from blog.models import Post
from django.contrib.auth.models import User

author = User.objects.get(username='admin')

# Method 1: create() — creates AND saves in one step
post = Post.objects.create(
    title='My First Post',
    slug='my-first-post',
    body='This is the content of my first post.',
    author=author,
    published=True,
)

# Method 2: instantiate then save() — useful when you need to
# do something before saving
post = Post(title='Draft Post', slug='draft-post', body='...', author=author)
post.published = False
post.save()  # Nothing is written to the database until save() is called

3. READ — Fetching Records

The ORM provides a rich API for reading data. All queries return either a single object or a QuerySet — a lazy, chainable list of objects.

# Get ALL posts (returns a QuerySet — no DB query yet)
all_posts = Post.objects.all()

# Get FILTERED posts — only published ones
published = Post.objects.filter(published=True)

# Chain filters — published posts by a specific author
user_posts = Post.objects.filter(published=True, author=author)

# Field lookups with double underscore — "contains", "startswith", "gt", etc.
recent = Post.objects.filter(title__icontains='django')   # case-insensitive
old = Post.objects.filter(created_at__year=2023)

# Get a SINGLE object — raises Post.DoesNotExist if not found
post = Post.objects.get(slug='my-first-post')

# Safe single fetch — returns None if not found (use in views!)
from django.shortcuts import get_object_or_404
post = get_object_or_404(Post, slug='my-first-post')

# Count, order, slice
count = Post.objects.filter(published=True).count()
newest = Post.objects.order_by('-created_at')[:5]  # Latest 5 posts

4. UPDATE — Modifying Records

# Update a single object: fetch → modify → save()
post = Post.objects.get(slug='my-first-post')
post.title = 'My First Post (Updated)'
post.save()

# Bulk update — more efficient for many records (one SQL UPDATE)
Post.objects.filter(published=False).update(published=True)

5. DELETE — Removing Records

# Delete a single object
post = Post.objects.get(slug='draft-post')
post.delete()

# Bulk delete — all unpublished posts
Post.objects.filter(published=False).delete()

6. Wiring CRUD into Views

Now let’s use these QuerySet calls inside real view functions that render templates:

# blog/views.py
from django.shortcuts import render, get_object_or_404
from .models import Post

def post_list(request):
    """Shows all published posts."""
    posts = Post.objects.filter(published=True)
    return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, slug):
    """Shows a single post, or a 404 page if it doesn't exist."""
    # get_object_or_404 is a shortcut: it calls Post.objects.get(slug=slug)
    # and automatically raises a 404 HTTP response if the post is not found.
    post = get_object_or_404(Post, slug=slug, published=True)
    return render(request, 'blog/post_detail.html', {'post': post})
<!-- templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% block content %}
  <h1>Latest Posts</h1>
  {% for post in posts %}
    <article>
      <h2><a href="{% url 'post_detail' slug=post.slug %}">{{ post.title }}</a></h2>
      <p>By {{ post.author.username }} on {{ post.created_at|date:"N j, Y" }}</p>
      <p>{{ post.body|truncatewords:25 }}</p>
    </article>
  {% empty %}
    <p>No posts published yet.</p>
  {% endfor %}
{% endblock %}

Add the new URL pattern in blog/urls.py:

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('blog/<slug:slug>/', views.post_detail, name='post_detail'),
]

You now have a working blog with real database-backed post listing and detail pages.