Django from Basics to Production · Django Fundamentals

Models and the ORM

Define models, create and query rows, filter with lookups, Q objects and F expressions, and aggregate.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A Product model with a few rows, and the queries you use every day: filters with field lookups, Q objects for OR conditions, F expressions to compare fields and update atomically, ordering and aggregation.

The idea

  • A model class maps to a table; fields map to columns. python manage.py makemigrations and migrate create and change tables (the examples create tables directly).
  • QuerySets are lazy: they build SQL and run it only when you iterate, slice or call methods like count().
  • Lookups like price__lt=10 and name__icontains="mug" become WHERE clauses.
  • F("stock") refers to a column in the database, so update(stock=F("stock") - 1) is atomic and race-free.

The code

Everyday ORM queries
import sys, types
import django
from django.conf import settings

sys.modules["shop"] = types.ModuleType("shop")       # lets this single file act as the "shop" app
sys.modules["shop"].__file__ = __file__
settings.configure(
    SECRET_KEY="dev-only", DEBUG=False, ALLOWED_HOSTS=["testserver"], ROOT_URLCONF=__name__,
    DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
    INSTALLED_APPS=["django.contrib.contenttypes", "django.contrib.auth", "shop"],
)
django.setup()

from django.db import connection, models
from django.db.models import Avg, Count, F, Q

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.CharField(max_length=30)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    stock = models.PositiveIntegerField(default=0)

    class Meta:
        app_label = "shop"

with connection.schema_editor() as editor:      # in a project: makemigrations + migrate
    editor.create_model(Product)

Product.objects.bulk_create([
    Product(name="Mug", category="kitchen", price="8.50", stock=12),
    Product(name="Teapot", category="kitchen", price="24.00", stock=0),
    Product(name="Notebook", category="office", price="4.00", stock=40),
    Product(name="Desk lamp", category="office", price="39.90", stock=3),
])

cheap = Product.objects.filter(price__lt=10).order_by("price").values_list("name", flat=True)
print("under 10:", list(cheap))
print("kitchen OR out of stock:", list(Product.objects.filter(Q(category="kitchen") | Q(stock=0)).values_list("name", flat=True)))
Product.objects.filter(name="Mug").update(stock=F("stock") - 1)          # atomic, in SQL
print("mug stock:", Product.objects.get(name="Mug").stock)
for row in Product.objects.values("category").annotate(n=Count("id"), avg=Avg("price")).order_by("category"):
    print(f"{row['category']}: {row['n']} products, average price {row['avg']:.2f}")
qs = Product.objects.filter(category="office")
print("SQL:", str(qs.query).replace('"shop_product".', ""))

Output

under 10: ['Notebook', 'Mug']
kitchen OR out of stock: ['Mug', 'Teapot']
mug stock: 11
kitchen: 2 products, average price 16.25
office: 2 products, average price 21.95
SQL: SELECT "id", "name", "category", "price", "stock" FROM "shop_product" WHERE "category" = office

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Read-modify-write in Python (p.stock -= 1; p.save()), which loses updates under concurrency; use F().
  • Calling len(qs) or iterating just to count; use qs.count() or qs.exists().
  • Using floats for money; use DecimalField.

Key points

  • Models define tables; migrations change them.
  • QuerySets are lazy and compose with filters, Q and F.
  • Push work to the database: atomic updates, counts and aggregates.

Further reading: Making queries · Query expressions (F)

Practice questions

Why use update(stock=F('stock') - 1) instead of reading, changing and saving?

Answer

The F expression performs the decrement in one SQL statement, so concurrent requests can't overwrite each other's changes.

When does a QuerySet hit the database?

Answer

When it's evaluated: iterated, sliced with a step, converted to a list, or when methods like count(), exists() or get() are called.

How do you express an OR condition in the Django ORM?

Answer

With Q objects combined by |, for example filter(Q(category='kitchen') | Q(stock=0)).

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?