Querying with QuerySets

Django · Essentials · 14 min

Almost every view reads or writes data, and QuerySets are how Django does it safely and readably.

Each model has a manager, objects, that produces QuerySets: lazy, chainable descriptions of a query.

Reading:

  • Post.objects.all(), .filter(status="pub"), .exclude(views=0)
  • .get(pk=1) returns exactly one object or raises Post.DoesNotExist / MultipleObjectsReturned
  • .order_by("-created", "title"), .first(), .last(), .count(), .exists()
  • slicing [:10] becomes LIMIT 10; [10:20] adds OFFSET

Field lookups use double underscores: title__icontains="django", views__gte=100, created__year=2025, status__in=["pub", "draft"], body__isnull=True, slug__startswith="how".

Several keyword arguments in one filter() are combined with AND.

Writing:

  • Post.objects.create(title=..., ...) inserts and returns the object
  • change attributes then post.save(); save(update_fields=["title"]) updates only those columns
  • Post.objects.filter(status="draft").update(status="pub") runs one UPDATE without loading objects
  • post.delete() or queryset.delete()
  • get_or_create(...) and update_or_create(...) for idempotent writes

values("title") returns dicts and values_list("title", flat=True) plain values, which is cheaper when you don't need model instances.

QuerySets are lazy: building one runs no SQL. The query runs when you iterate, slice with a step, call len(), list(), bool() or print it. The ORM always passes values as query parameters, so filters are safe from SQL injection.

Example

from blog.models import Post

post = Post.objects.create(title="Hello Django", slug="hello", status="pub")

published = Post.objects.filter(status="pub").exclude(views=0)   # no SQL yet
recent = published.order_by("-created")[:5]                     # still lazy
for p in recent:                                                 # SQL runs here
    print(p.title)

Post.objects.filter(title__icontains="django", created__year=2025).count()
Post.objects.filter(status__in=["pub", "draft"]).exists()        # True

try:
    Post.objects.get(slug="nope")
except Post.DoesNotExist:
    print("not found")

post.title = "Hello, Django"
post.save(update_fields=["title"])
Post.objects.filter(status="draft").update(status="pub")          # one UPDATE
titles = list(Post.objects.values_list("title", flat=True))      # ['Hello, Django', ...]
print(Post.objects.filter(status="pub").query)   # SELECT ... WHERE "blog_post"."status" = pub ...

Remember

  • filter/exclude/order_by chain lazily; SQL runs on iteration or evaluation
  • field__lookup=value expresses conditions (icontains, gte, in, year...)
  • get() raises DoesNotExist; update()/delete() work on whole QuerySets

Watch out for

  • Using get() for something that may be missing without handling DoesNotExist (or get_object_or_404 in views).

Check yourself

  1. When does Post.objects.filter(status="pub").order_by("-created") hit the database?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.djangoproject.com/en/6.0/topics/db/queries/