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.
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 makemigrationsandmigratecreate 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=10andname__icontains="mug"becomeWHEREclauses. F("stock")refers to a column in the database, soupdate(stock=F("stock") - 1)is atomic and race-free.
The code
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; useF(). - Calling
len(qs)or iterating just to count; useqs.count()orqs.exists(). - Using floats for money; use
DecimalField.
Key points
- Models define tables; migrations change them.
- QuerySets are lazy and compose with filters,
QandF. - 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.