Skip to main content

Django QuerySet API

Introduction

Django's QuerySet API is one of the most powerful features of Django's ORM (Object-Relational Mapping) system. It provides a rich, pythonic way to interact with your database without writing raw SQL queries. QuerySets allow you to retrieve, filter, order, and manipulate data from your database with ease and efficiency.

In this tutorial, we'll explore the QuerySet API in depth, learning how to perform common database operations while letting Django handle the underlying SQL complexity.

What is a QuerySet?

A QuerySet represents a collection of objects from your database. It can have zero, one, or many filters that narrow down the query results based on given parameters. QuerySets are lazy, which means Django won't actually execute the query until the QuerySet is evaluated (e.g., when you iterate over it, slice it, or convert it to a list).

python
# This does NOT execute any query yet
all_books = Book.objects.all()

# The query is executed when you:
for book in all_books: # Execute the query when iterating
print(book.title)

# Or when you evaluate it in other ways
book_list = list(all_books) # Execute the query
first_book = all_books[0] # Execute the query

Creating QuerySets with Simple Retrievals

Let's start with the basics of retrieving objects from your database.

Retrieving All Objects

To retrieve all objects of a model, use the all() method:

python
# Retrieve all books from the database
books = Book.objects.all()

Retrieving a Single Object

To retrieve a single object, you can use the get() method:

python
# Retrieve a book with id=1
book = Book.objects.get(id=1)

# Retrieve a book by title
try:
book = Book.objects.get(title="Django for Beginners")
print(f"Found book: {book.title}")
except Book.DoesNotExist:
print("Book not found")
except Book.MultipleObjectsReturned:
print("Multiple books found with that title")

The get() method will raise DoesNotExist if no object matches the query, and MultipleObjectsReturned if more than one object matches.

Filtering QuerySets

One of the most common operations is filtering QuerySets to get specific records.

Basic Filtering

python
# Get all books published in 2023
books_2023 = Book.objects.filter(publication_year=2023)

# Get all books by a specific author
python_books = Book.objects.filter(category="Python")

# Exclude books that are out of stock
available_books = Book.objects.exclude(stock=0)

Chaining Filters

You can chain filters to narrow down your query:

python
# Get Python books published in 2023 that are in stock
python_books_2023 = Book.objects.filter(
category="Python"
).filter(
publication_year=2023
).exclude(
stock=0
)

Field Lookups

Field lookups are specified as keyword arguments to the filter(), exclude() and get() methods:

python
# Books with titles containing the word "Django"
django_books = Book.objects.filter(title__contains="Django")

# Books published after 2020
new_books = Book.objects.filter(publication_year__gt=2020)

# Books with titles starting with "Python"
python_books = Book.objects.filter(title__startswith="Python")

Common field lookups include:

  • __exact: Exact match (this is the default)
  • __iexact: Case-insensitive exact match
  • __contains: Case-sensitive containment test
  • __icontains: Case-insensitive containment test
  • __gt, __gte, __lt, __lte: Greater than, greater than or equal to, less than, less than or equal to
  • __startswith, __istartswith: Starts with, case-insensitive starts with
  • __endswith, __iendswith: Ends with, case-insensitive ends with
  • __in: In a given list
  • __range: Within a given range

Complex Filtering with Q Objects

For more complex queries involving OR conditions or complex combinations of AND/OR, you can use Q objects:

python
from django.db.models import Q

# Books that are either Python books OR published in 2023
books = Book.objects.filter(
Q(category="Python") | Q(publication_year=2023)
)

# Books that are Python books published in 2023 OR Django books
books = Book.objects.filter(
Q(category="Python", publication_year=2023) | Q(category="Django")
)

# NOT condition: Books that are NOT Python books
books = Book.objects.filter(~Q(category="Python"))

Ordering QuerySets

To order your QuerySets, use the order_by() method:

python
# Order books by title (ascending)
books = Book.objects.order_by('title')

# Order books by publication year (descending)
books = Book.objects.order_by('-publication_year')

# Order by multiple fields (first by category, then by title)
books = Book.objects.order_by('category', 'title')

Limiting QuerySets

You can limit the number of returned results using Python's array slicing syntax:

python
# Get the first 5 books
first_five = Book.objects.all()[:5]

# Get 5 books, skipping the first 5 (pagination)
next_five = Book.objects.all()[5:10]

Aggregations and Annotations

Django's QuerySet API allows you to perform aggregations like count, sum, avg, etc.

Counting Objects

python
# Count all books
book_count = Book.objects.count()

# Count Python books
python_book_count = Book.objects.filter(category="Python").count()

Aggregation Functions

python
from django.db.models import Avg, Sum, Min, Max, Count

# Get average price of all books
avg_price = Book.objects.aggregate(Avg('price'))
# Returns: {'price__avg': 25.5}

# Multiple aggregations
book_stats = Book.objects.aggregate(
avg_price=Avg('price'),
max_price=Max('price'),
min_price=Min('price'),
total_books=Count('id')
)
# Returns: {'avg_price': 25.5, 'max_price': 50, 'min_price': 10, 'total_books': 100}

Annotations

Annotations allow you to add calculated fields to each object in a QuerySet:

python
from django.db.models import F, ExpressionWrapper, DecimalField

# Calculate discounted price for each book
books = Book.objects.annotate(
discounted_price=ExpressionWrapper(
F('price') * 0.9, output_field=DecimalField()
)
)

# Now each book has a discounted_price attribute
for book in books:
print(f"{book.title}: ${book.price} (Discounted: ${book.discounted_price})")

Django makes it easy to work with related models through foreign keys, many-to-many relationships, and more.

Assuming we have models like:

python
class Author(models.Model):
name = models.CharField(max_length=100)

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

You can access related objects like this:

python
# Get all books by an author
author = Author.objects.get(name="Jane Doe")
books_by_author = author.books.all() # Using the related_name

# Get the author of a book
book = Book.objects.get(title="Django for Beginners")
author = book.author
python
# Get all books whose author's name contains "Smith"
books = Book.objects.filter(author__name__contains="Smith")

# Get all authors who have written Python books
authors = Author.objects.filter(books__category="Python").distinct()

These methods help optimize database queries when working with related objects.

select_related is used for ForeignKey and OneToOneField relationships:

python
# Without select_related (causes N+1 query problem)
books = Book.objects.all()
for book in books:
print(f"{book.title} by {book.author.name}") # Each access to author triggers a new query

# With select_related (more efficient)
books = Book.objects.select_related('author')
for book in books:
print(f"{book.title} by {book.author.name}") # No additional queries

prefetch_related is used for ManyToMany and reverse ForeignKey relationships:

python
# Assuming a Book can have many Reviews
class Review(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')
text = models.TextField()
rating = models.IntegerField()

# Without prefetch_related
books = Book.objects.all()
for book in books:
print(f"{book.title}: {book.reviews.count()} reviews") # Each access triggers a new query

# With prefetch_related
books = Book.objects.prefetch_related('reviews')
for book in books:
print(f"{book.title}: {book.reviews.count()} reviews") # No additional queries

Bulk Operations

Django's QuerySet API provides methods for performing bulk operations efficiently.

Creating Multiple Objects

python
# Create objects individually (less efficient)
Author.objects.create(name="Author 1")
Author.objects.create(name="Author 2")

# Bulk create (more efficient)
authors = [
Author(name="Author 1"),
Author(name="Author 2"),
Author(name="Author 3"),
]
Author.objects.bulk_create(authors)

Updating Multiple Objects

python
# Update all Python books to increase price by 10%
Book.objects.filter(category="Python").update(price=F('price') * 1.1)

Deleting Objects

python
# Delete all books with 0 stock
Book.objects.filter(stock=0).delete()

Real-world Example: Building a Book Catalog System

Let's put everything together in a practical example of a book catalog system:

python
# models.py
from django.db import models

class Author(models.Model):
name = models.CharField(max_length=100)
bio = models.TextField(blank=True)

def __str__(self):
return self.name

class Category(models.Model):
name = models.CharField(max_length=50)

def __str__(self):
return self.name

class Meta:
verbose_name_plural = "Categories"

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
categories = models.ManyToManyField(Category, related_name='books')
publication_date = models.DateField()
price = models.DecimalField(max_digits=6, decimal_places=2)
stock = models.IntegerField(default=0)
is_featured = models.BooleanField(default=False)

def __str__(self):
return self.title

Now let's write some views to demonstrate QuerySet API usage:

python
# views.py
from django.shortcuts import render
from django.db.models import Count, Avg, F, Q
from .models import Book, Author, Category

def book_catalog(request):
# Get query parameters
category = request.GET.get('category')
search = request.GET.get('search')
sort = request.GET.get('sort', 'title')

# Start with all books
books = Book.objects.all()

# Filter by category if provided
if category:
books = books.filter(categories__name=category)

# Search in title or author name
if search:
books = books.filter(
Q(title__icontains=search) |
Q(author__name__icontains=search)
)

# Apply sorting
if sort == 'price':
books = books.order_by('price')
elif sort == 'price_desc':
books = books.order_by('-price')
elif sort == 'newest':
books = books.order_by('-publication_date')
else: # default to title
books = books.order_by('title')

# Optimize with select_related and prefetch_related
books = books.select_related('author').prefetch_related('categories')

# Get some stats
book_count = books.count()
avg_price = books.aggregate(avg_price=Avg('price'))['avg_price']

# Get featured books separately
featured_books = Book.objects.filter(is_featured=True)[:5]

# Get categories with book counts
categories = Category.objects.annotate(book_count=Count('books'))

context = {
'books': books,
'book_count': book_count,
'avg_price': avg_price,
'featured_books': featured_books,
'categories': categories,
}

return render(request, 'books/catalog.html', context)

def author_detail(request, author_id):
author = Author.objects.get(id=author_id)

# Get author's books with annotations
books = author.books.all().annotate(
is_low_stock=F('stock') < 5
)

# Get some stats about the author's books
book_stats = books.aggregate(
total_books=Count('id'),
avg_price=Avg('price')
)

context = {
'author': author,
'books': books,
'book_stats': book_stats,
}

return render(request, 'books/author_detail.html', context)

Summary

Django's QuerySet API is a powerful tool that allows you to interact with your database using Python code instead of raw SQL. Here's what we've covered:

  • Basic retrieval operations with all(), get(), filter(), and exclude()
  • Field lookups for more advanced filtering
  • Complex queries with Q objects
  • Ordering and limiting QuerySets
  • Aggregations and annotations for statistics
  • Working with related models
  • Performance optimization with select_related and prefetch_related
  • Bulk operations for efficiency
  • A real-world example of a book catalog system

By mastering the QuerySet API, you'll be able to build efficient, powerful Django applications that interact with your database in a clean, Pythonic way.

Additional Resources

Exercises

  1. Create a model for a blog application with Posts and Categories. Write queries to:

    • Find all posts in a specific category
    • Find the most popular category (with the most posts)
    • Get all posts published in the last week
  2. Implement a product inventory system with:

    • Products that belong to multiple categories
    • Each product has a stock level
    • Write a query to find products that are low on stock (less than 5 items)
    • Find the average price of products by category
  3. Create a movie database with:

    • Movies, Actors, and Directors
    • Write a query to find all movies by a specific director
    • Find actors who have appeared in more than 3 movies
    • Calculate the average rating of movies by director


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)