Django’s ORM (Object-Relational Mapper) is one of its most powerful features. It allows you to interact with your database using Python code instead of writing raw SQL.
At the heart of the ORM is the QuerySet — a powerful abstraction that lets you retrieve, filter, and manipulate data efficiently.
In this tutorial, you will go beyond the basics and learn how to:
This knowledge is essential because every Django application depends heavily on database queries.
By the end of this tutorial, you will understand:
__icontains, __gt, etc.)select_related, prefetch_related)You should already know:
ListView and DetailView)A QuerySet is a collection of database queries.
When you write:
Post.objects.all()Django does NOT immediately fetch data from the database. Instead, it creates a QuerySet.
👉 A QuerySet is:
Post.objects.all()Post.objects.get(id=1)⚠️ If no object or multiple objects exist, get() raises an error.
Post.objects.filter(title="Django")Returns all posts with the exact title.
Post.objects.exclude(title="Django")Returns all posts except those with that title.
Post.objects.order_by('created_at')Ascending order
Post.objects.order_by('-created_at')Descending order
One of the most powerful features is chaining.
Post.objects.filter(title__icontains='django').order_by('-created_at')This means:
Each method returns a new QuerySet, so you can chain as many as you want.
Django provides many lookup expressions.
Post.objects.filter(title__icontains='django')Post.objects.filter(id__gt=5)Post.objects.filter(id__lt=10)Post.objects.filter(id__range=(1, 10))Post.objects.filter(title__startswith='Django')Post.objects.filter(title__endswith='Guide')Post.objects.filter(created_at__year=2025)You can combine conditions using Q objects.
from django.db.models import Q
Post.objects.filter(
Q(title__icontains='django') | Q(content__icontains='python')
)This means:
Post.objects.filter(
Q(title__icontains='django') & Q(content__icontains='python')
)Post.objects.first()Post.objects.last()Post.objects.all()[:5]Post.objects.all()[5:10]Instead of:
if Post.objects.filter(title='Django'):Use:
Post.objects.filter(title='Django').exists()👉 This is faster and more efficient.
Post.objects.count()Counts rows directly in the database.
Post.objects.filter(title='Old').update(title='New')post = Post.objects.get(id=1)
post.title = "Updated title"
post.save()post = Post.objects.get(id=1)
post.delete()
Post.objects.filter(title='Test').delete()Aggregation is used to compute values like:
from django.db.models import Count
Post.objects.aggregate(total_posts=Count('id'))from django.db.models import Avg, Max
Post.objects.aggregate(
avg_id=Avg('id'),
max_id=Max('id')
)Annotation adds calculated fields to each object.
from django.db.models import Count
from .models import Category
Category.objects.annotate(post_count=Count('post'))Each category will now have a post_count attribute.
QuerySets are lazy.
This means Django does NOT hit the database until necessary.
qs = Post.objects.all()No query yet.
But when you do:
for post in qs:
print(post.title)👉 Now the query runs.
for)len()Example:
for post in Post.objects.all():
print(post.author.name)This may cause many database queries.
select_relatedPost.objects.select_related('author')Used for ForeignKey relationships.
prefetch_relatedPost.objects.prefetch_related('tags')Used for:
| Method | Use Case |
|---|---|
select_related | ForeignKey |
prefetch_related | ManyToMany |
class PostListView(ListView):
model = Post
def get_queryset(self):
return Post.objects.filter(title__icontains='django').order_by('-created_at')This lets you customize what data is shown.
Sometimes you need raw SQL.
Post.objects.raw("SELECT * FROM blog_post WHERE id > %s", [5])⚠️ Use this only when necessary.
get() instead of filter()Post.objects.get(title='Django')⚠️ Can crash if multiple results exist.
exists()if Post.objects.filter(title='Django'):❌ Inefficient
✔️ Better:
Post.objects.filter(title='Django').exists()Not using select_related or prefetch_related can slow your app.
filter() instead of get() when unsureexists() for checksselect_related and prefetch_relatedCreate a search feature.
class PostSearchView(ListView):
model = Post
template_name = 'blog/search.html'
context_object_name = 'posts'
def get_queryset(self):
query = self.request.GET.get('q')
if query:
return Post.objects.filter(
title__icontains=query
)
return Post.objects.none()<form method="get">
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% empty %}
<p>No results found.</p>
{% endfor %}In this tutorial, you learned:
Mastering QuerySets is essential for building efficient Django applications.
A. A database table
B. A collection of queries
C. A Python list
D. A model
A. get()
B. filter()
C. all()
D. order_by()
A. count()
B. exists()
C. len()
D. first()
A. prefetch_related()
B. select_related()
C. annotate()
D. aggregate()
A. Immediate
B. Lazy
C. Static
D. Fixed
Next tutorial:
Tutorial: Relationships in Django Models
(ForeignKey, OneToOne, ManyToMany — deep understanding of data relationships)