Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 23

Django QuerySet Basics: Filtering & Retrieval

Recommended resources

Hello! Welcome back to your backend engineering course.

In our last lesson, you mastered the four fundamental CRUD operations (Create, Read, Update, Delete) using the Django ORM. You learned how to use methods like create(), get(), all(), update(), and delete() to manage data in your database.

Today, we're going to significantly expand on the "Read" operation. While all() and get() are useful, real-world applications require much more precise control over data retrieval. This lesson is dedicated to today's learning outcome: to write basic queries using the Django QuerySet API for filtering and retrieval.

We'll explore how to construct sophisticated queries to find exactly the data you need. We'll cover:

  • Advanced filtering with various conditions.
  • Combining and chaining filters.
  • Ordering and limiting your results.

Given your interest in how systems work "under the hood," we'll pay close attention to how Django translates your Python code into efficient SQL queries. This will bridge the gap between the high-level abstraction of the ORM and the low-level execution in the database.

1. Your Query Laboratory: The Django Shell

Before we start writing complex queries inside our views, it's essential to have a space for experimentation. The Django shell is an interactive Python console configured for your project, making it the perfect laboratory for practicing ORM queries.

You can start it by running this command in your project's root directory:
python manage.py shell

Once inside, you can import your models and execute queries directly, getting immediate feedback.

Learn Django - Intro to Django Databases - Basic Queries and functions from the Shell

This short clip from Very Academy will introduce you to the Django shell and show you why it's an indispensable tool for development and debugging.

Watch from 08:42 to 10:47 and then from 18:43 to 20:55. Focus on: The explanation of what the shell is and why it's useful for practicing queries. The demonstration of how to import a model into the shell to start interacting with the database.


2. The Core of Retrieval: filter() and exclude()

In the previous lesson, we used filter() for simple equality checks. Now, we'll unlock its full power using field lookups. These are specified using a double-underscore (__) after the field name and allow you to express conditions beyond simple equality.

Django ORM Workflow
As a reminder, this diagram shows how the Django ORM, which we are about to use heavily, translates your Python commands into SQL that the database understands.

The filter() method returns a QuerySet containing objects that match the given lookup parameters. Its counterpart, exclude(), does the opposite, returning a QuerySet of objects that do not match.

Let's assume we are working with this Product model for our examples:

# models.py
class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock_count = models.IntegerField(default=0)
    release_date = models.DateField()

Common Field Lookups

Here are some of the most common lookups you'll use:

  • __gt, __gte: Greater than, Greater than or equal to.
    • Product.objects.filter(price__gt=100) -> SQL: WHERE price > 100
  • __lt, __lte: Less than, Less than or equal to.
    • Product.objects.filter(stock_count__lte=10) -> SQL: WHERE stock_count <= 10
  • __in: In a given list.
    • Product.objects.filter(id__in=[1, 5, 10]) -> SQL: WHERE id IN (1, 5, 10)
  • __startswith, __contains: String matching (case-sensitive).
    • Product.objects.filter(name__startswith='Wireless') -> SQL: WHERE name LIKE 'Wireless%'
  • __istartswith, __icontains: Case-insensitive string matching.
  • __range: Between two values (inclusive).
    • Product.objects.filter(release_date__range=["2023-01-01", "2023-12-31"]) -> SQL: WHERE release_date BETWEEN '2023-01-01' AND '2023-12-31'

The following video provides a fantastic, practical walkthrough of these concepts. As you watch, pay close attention to the generated SQL queries shown in the terminal—this directly connects the ORM method to the database action.

Django ORM - QuerySet Filtering and Lookups / Ordering and Slicing QuerySets

This video from BugBytes demonstrates how to use filter, exclude, and various field lookups to build powerful queries. It's a very practical guide.

Watch the following segments: filter() vs get() (04:34 - 07:16): A quick review of basic filtering. Chaining Filters (AND) and the __in Lookup (10:38 - 15:14): See how multiple arguments to filter() create an AND clause and how to use __in for multiple values. Using exclude() and Comparison Lookups (15:14 - 22:48): Understand how exclude() creates NOT clauses and see demonstrations of __lt, __gt, and __range lookups.

Combining Filters with AND and OR

  • AND: To combine conditions with AND, you simply pass them as multiple arguments to a single filter() call.
    # Products that start with 'Laptop' AND have a price greater than 1000
    Product.objects.filter(name__startswith='Laptop', price__gt=1000)
    
  • OR: For OR conditions, you need to use Q objects.
    from django.db.models import Q
    
    # Products that start with 'Laptop' OR 'Desktop'
    Product.objects.filter(Q(name__startswith='Laptop') | Q(name__startswith='Desktop'))
    

The following resource provides a concise reference for AND and OR queries, and includes a handy table of all available field lookups.

Django QuerySet - Filter

This W3Schools page is a great quick reference. Focus on the sections explaining AND/OR logic and the comprehensive table of field lookups.

Read the sections titled 'AND' and 'OR' to see how to construct these queries. Then, review the 'Field Lookups Reference' table to get a full sense of the different lookups available.


3. Refining Your Results: Ordering and Slicing

Retrieving the right data is only half the battle. You also need to present it in a useful way.

Ordering QuerySets with order_by()

The order_by() method allows you to sort your QuerySet by one or more model fields.

  • Ascending Order: Product.objects.order_by('price') -> SQL: ORDER BY price ASC
  • Descending Order: Prepend a minus sign (-) to the field name. Product.objects.order_by('-release_date') -> SQL: ORDER BY release_date DESC
  • Multiple Fields: Product.objects.order_by('name', '-price') sorts by name alphabetically, then by price descending for items with the same name.

Slicing QuerySets

Django's QuerySet can be sliced using Python's list indexing syntax. This is a powerful and efficient feature, as it translates directly to LIMIT and OFFSET clauses in SQL, preventing you from loading unnecessary objects into memory.

  • The first 5 products: Product.objects.all()[:5] -> SQL: LIMIT 5
  • Products from index 5 to 10: Product.objects.all()[5:10] -> SQL: LIMIT 5 OFFSET 5
  • A single object: Product.objects.all()[0] (Note: This is often less explicit than using .first() or .get()).

The next segment of the BugBytes video covers ordering and slicing in detail.

Django ORM - QuerySet Filtering and Lookups / Ordering and Slicing QuerySets

Let's return to the BugBytes video to see how ordering and slicing work.

Watch from 22:48 to 31:29. This section will show you: How to use order_by() with ascending, descending, and even case-insensitive ordering. How slicing a QuerySet is translated into SQL LIMIT and OFFSET clauses, which is a key performance optimization.

Test your understanding!

Using the Product model from our examples, write a single Django ORM query for each of the following tasks.

  1. Find all products with a price between $50 and $100 (inclusive).
  2. Find all products whose name contains the word 'Gaming', but exclude those with a stock count of 0.
  3. Find the 3 most expensive products that are in stock (stock count > 0).
Show answer
  1. Product.objects.filter(price__range=(50, 100))
  2. Product.objects.filter(name__icontains='Gaming').exclude(stock_count=0)
  3. Product.objects.filter(stock_count__gt=0).order_by('-price')[:3]

4. Querying Across Relationships

One of the most powerful features of the ORM is the ability to traverse relationships in your queries. Using the same double-underscore syntax, you can filter a QuerySet based on the fields of a related model.

Let's add an Author model and link it to a Book model:

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)

Now you can find books based on their author's attributes:

# Find all books by an author named 'J.R.R. Tolkien'
Book.objects.filter(author__name='J.R.R. Tolkien')

Behind the scenes, Django performs a SQL JOIN to make this possible.

Django ORM - QuerySet Filtering and Lookups / Ordering and Slicing QuerySets

The final clip from the BugBytes video will demonstrate how to perform these powerful cross-relationship queries.

Watch from 35:56 to 38:36. Notice how the __ syntax is used to 'jump' from the Rating model to the Restaurant model's fields, and how this results in an INNER JOIN in the SQL.


Conclusion

You have now moved beyond basic data retrieval and are equipped to write sophisticated queries. You can filter, exclude, order, and limit your data with fine-grained control, all while writing clean and readable Python.

Key Takeaways:

  • The Django shell is your primary tool for experimenting with queries.
  • Field Lookups (__gt, __in, __icontains, etc.) are used with filter() and exclude() to build WHERE clauses.
  • Chaining filters or passing multiple arguments to filter() creates an AND condition. Q objects are used for OR.
  • order_by() controls the sorting of your results (ORDER BY in SQL).
  • Slicing a QuerySet ([:10]) is an efficient way to limit results (LIMIT and OFFSET in SQL).
  • You can query across relationships using the same __ syntax, which prompts Django to perform a SQL JOIN.

Preview of the Next Lesson:
In our next lesson, we'll see a very practical application of everything we've learned in this module. We will explore how to interact with and manage model data using the Django Admin interface. The admin is a powerful, auto-generated scaffolding that uses the ORM and QuerySet API to provide you with a ready-to-use interface for performing CRUD operations on your models, saving you immense development time.