Django Models & ORM
Master Django database integrations. Learn model declarations, migrations, custom managers, queryset lookups, and database migrations.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is Django's ORM and what problem does it solve?
Django's ORM (Object-Relational Mapper) lets developers interact with the database using Python classes and methods instead of writing raw SQL, mapping Python objects to database tables automatically.
# models.py - defining a model maps directly to a database table
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
published_date = models.DateField()
price = models.DecimalField(max_digits=6, decimal_places=2)
# Using the ORM - no raw SQL needed
book = Book.objects.create(
title='Django for Beginners',
author=Author.objects.create(name='John', email='john@example.com'),
published_date='2024-01-01',
price=29.99
)
# Equivalent raw SQL the ORM generates behind the scenes (roughly):
# INSERT INTO myapp_book (title, author_id, published_date, price)
# VALUES ('Django for Beginners', 1, '2024-01-01', 29.99);
books = Book.objects.filter(price__lt=50)
for b in books:
print(b.title, b.author.name)| Benefit | Explanation |
|---|---|
| Database portability | Same Python code works across PostgreSQL, MySQL, SQLite, etc. |
| SQL injection protection | ORM automatically parameterizes queries |
| Migrations | Schema changes tracked and versioned as Python code |
Q2. What is the difference between ForeignKey, OneToOneField, and ManyToManyField in Django models?
| Field type | Relationship | Example |
|---|---|---|
| ForeignKey | Many-to-one | Many Books, one Author |
| OneToOneField | One-to-one | One User, one Profile |
| ManyToManyField | Many-to-many | Many Students, many Courses |
from django.db import models
from django.contrib.auth.models import User
# ForeignKey - many books can belong to one author
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, related_name='books')
author = Author.objects.get(id=1)
author.books.all() # access related books via related_name
# OneToOneField - each User has exactly one Profile
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
user = User.objects.get(id=1)
profile = user.profile # direct one-to-one access
# ManyToManyField - students can enroll in many courses, courses can
# have many students; Django creates a hidden junction table automatically
class Course(models.Model):
name = models.CharField(max_length=100)
class Student(models.Model):
name = models.CharField(max_length=100)
courses = models.ManyToManyField(Course, related_name='students')
student = Student.objects.get(id=1)
student.courses.add(course1, course2) # adding relationships
student.courses.all() # all courses this student is enrolled in
course = Course.objects.get(id=1)
course.students.all() # all students enrolled in this course
Q3. What are Django migrations and how do they work?
Migrations are Django's way of tracking and applying changes to your database schema over time, generated automatically from changes to your models.py files.
# After modifying models.py (e.g. adding a new field)
# Generate a migration file describing the change
# $ python manage.py makemigrations
# Migrations for 'myapp':
# myapp/migrations/0002_book_isbn.py
# - Add field isbn to book
# Apply the migration to the actual database
# $ python manage.py migrate
# A generated migration file looks like this:
# myapp/migrations/0002_book_isbn.py
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('myapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='book',
name='isbn',
field=models.CharField(max_length=13, default=''),
),
]
# Checking migration status
# $ python manage.py showmigrations
# Rolling back to a previous migration
# $ python manage.py migrate myapp 0001
# Viewing the SQL a migration will run without applying it
# $ python manage.py sqlmigrate myapp 0002
# Creating an empty migration for custom data operations
# $ python manage.py makemigrations myapp --empty
def populate_default_isbn(apps, schema_editor):
Book = apps.get_model('myapp', 'Book')
Book.objects.filter(isbn='').update(isbn='UNKNOWN')
class Migration(migrations.Migration):
dependencies = [('myapp', '0002_book_isbn')]
operations = [
migrations.RunPython(populate_default_isbn),
]
Q4. What is the difference between QuerySet.filter(), .exclude(), and .get()?
| Method | Returns | No match behavior | Multiple matches behavior |
|---|---|---|---|
| filter() | QuerySet (possibly empty) | Returns empty QuerySet | Returns all matches |
| exclude() | QuerySet (inverse of filter) | Returns empty QuerySet | Returns all non-matches |
| get() | A single model instance | Raises DoesNotExist | Raises MultipleObjectsReturned |
from myapp.models import Book
# filter() - returns a QuerySet, safe even with zero results
cheap_books = Book.objects.filter(price__lt=20)
print(cheap_books) # <QuerySet [...]> - could be empty, no error
# exclude() - opposite of filter, returns non-matching rows
expensive_books = Book.objects.exclude(price__lt=20)
# get() - expects EXACTLY one result
try:
book = Book.objects.get(id=1) # returns a single Book instance directly
except Book.DoesNotExist:
print('Book not found')
except Book.MultipleObjectsReturned:
print('More than one book matched - id should be unique!')
# Chaining filter() calls - each filter narrows the QuerySet further
results = Book.objects.filter(price__lt=50).filter(author__name='John')
# Common field lookups used with filter()/exclude()
Book.objects.filter(title__icontains='django') # case-insensitive contains
Book.objects.filter(price__gte=10, price__lte=50) # range
Book.objects.filter(published_date__year=2024) # date component
Book.objects.filter(author__name__startswith='J') # traverse relationships
# Combining conditions with Q objects for OR logic
from django.db.models import Q
Book.objects.filter(Q(price__lt=20) | Q(author__name='John'))
Q5. What is the N+1 query problem in Django and how do select_related() and prefetch_related() fix it?
The N+1 problem occurs when fetching a list of objects triggers one additional query PER object to load related data, instead of one combined query - a common performance killer in Django apps.
# PROBLEM - N+1 queries
books = Book.objects.all() # 1 query
for book in books:
print(book.author.name) # 1 EXTRA query per book, since author is a ForeignKey
# Total: 1 + N queries for N books
# FIX for ForeignKey/OneToOne - select_related() uses a SQL JOIN
books = Book.objects.select_related('author').all() # 1 query, JOIN included
for book in books:
print(book.author.name) # no extra query - author data already loaded
# FIX for ManyToMany/reverse ForeignKey - prefetch_related() uses a SEPARATE query,
# then joins results in Python (JOIN doesn't work well for these relationships)
authors = Author.objects.prefetch_related('books').all() # 2 queries total
for author in authors:
for book in author.books.all(): # no extra query per author
print(book.title)
# Chaining multiple select_related for nested ForeignKeys
reviews = Review.objects.select_related('book__author').all()
for review in reviews:
print(review.book.author.name) # both book and author preloaded, 1 query
# Combining both when needed
books = Book.objects.select_related('author').prefetch_related('reviews').all()
# Detecting N+1 issues: use django-debug-toolbar in development,
# or set 'django.db.backends' logger to DEBUG to see all executed queries
import logging
logging.basicConfig()
logging.getLogger('django.db.backends').setLevel(logging.DEBUG)
Q6. What is the difference between Django's abstract base classes and multi-table inheritance for models?
| Type | Database tables created | Use case |
|---|---|---|
| Abstract base class | Only for child models, parent has NO table | Share common fields/methods without a shared table |
| Multi-table inheritance | Separate table for BOTH parent and child, linked by implicit OneToOne | Real 'is-a' relationships needing independent parent queries |
| Proxy models | No new table - changes only behavior (Meta options, methods) | Different Python behavior for the same underlying data |
# Abstract base class - no table for CommonInfo itself
class CommonInfo(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True # KEY - prevents Django from creating a table for this
class Article(CommonInfo):
title = models.CharField(max_length=200)
# Article table includes: id, title, created_at, updated_at
# Multi-table inheritance - separate tables, linked automatically
class Place(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=200)
class Restaurant(Place): # NOT abstract - creates its own table too
serves_pizza = models.BooleanField(default=False)
# Restaurant table has: place_ptr_id (OneToOne to Place), serves_pizza
# Place table has: id, name, address
restaurant = Restaurant.objects.create(name='Pizzeria', address='123 St', serves_pizza=True)
place = Place.objects.get(id=restaurant.id) # accessible as a Place too
print(place.name) # Pizzeria - works because Restaurant IS-A Place in the DB
# Proxy model - same table, different Python-level behavior
class OrderedArticle(Article):
class Meta:
proxy = True
ordering = ['title'] # only affects default query ordering, no new table
def summary(self):
return self.title[:50]
Q7. What are Django signals and when should you use them?
Signals let certain senders notify a set of receivers when specific actions occur (like a model being saved or deleted), enabling decoupled code that reacts to events without direct coupling.
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.contrib.auth.models import User
from myapp.models import Profile
# post_save - runs AFTER a model instance is saved
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created: # only run when a NEW user is created, not on every update
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
# Now Profile is automatically created whenever a User is created
user = User.objects.create(username='john') # triggers create_user_profile automatically
# pre_delete - runs BEFORE a model instance is deleted
@receiver(pre_delete, sender=Profile)
def log_profile_deletion(sender, instance, **kwargs):
print(f'About to delete profile for {instance.user.username}')
# Common signals: pre_save, post_save, pre_delete, post_delete,
# m2m_changed, request_started, request_finished
# IMPORTANT: signals must be imported somewhere Django loads, typically
# in the app's apps.py ready() method, or signals silently won't connect
# apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
import myapp.signals # ensures signal handlers get registered
# When to AVOID signals: they can make code flow hard to trace -
# for simple cases, overriding save() directly is often clearer
class Profile(models.Model):
def save(self, *args, **kwargs):
# custom logic here instead of a signal
super().save(*args, **kwargs)
Q8. What is the difference between Model.objects.create() and Model.save()?
| Method | Steps required | Returns |
|---|---|---|
| objects.create() | One step - instantiate and save together | The saved instance |
| Model() + .save() | Two steps - instantiate, then save separately | None (save() returns None) |
# objects.create() - shorthand, does both steps at once
book = Book.objects.create(title='Django Basics', price=25.99)
# equivalent to:
book2 = Book(title='Django Basics', price=25.99)
book2.save()
# Using .save() gives you a chance to modify the instance BEFORE saving
book3 = Book(title='Advanced Django', price=35.99)
book3.price = book3.price * 0.9 # apply a discount before persisting
book3.save()
# .save() is also used for UPDATING an existing instance
book = Book.objects.get(id=1)
book.price = 19.99 # modify a field
book.save() # UPDATE query, not INSERT, since the instance already has a pk
# update_or_create() - avoids checking existence manually
book, created = Book.objects.update_or_create(
title='Django Basics', # lookup fields
defaults={'price': 22.99} # fields to set if found/created
)
print('Created new' if created else 'Updated existing')
# get_or_create() - fetch if exists, otherwise create
book, created = Book.objects.get_or_create(
title='New Book',
defaults={'price': 15.00}
)
# bulk_create() - efficient for inserting many objects in ONE query
Book.objects.bulk_create([
Book(title='Book A', price=10),
Book(title='Book B', price=20),
Book(title='Book C', price=30),
]) # single INSERT statement instead of 3 separate ones
Q9. What is the difference between values(), values_list(), and regular QuerySets?
| Method | Returns | Element type |
|---|---|---|
| Regular QuerySet | Full model instances | Model objects with all fields/methods |
| values() | Dictionaries | {'field': value, ...} per row |
| values_list() | Tuples (or flat values with flat=True) | (value1, value2, ...) per row |
# Regular QuerySet - full model instances, more memory, full functionality
books = Book.objects.all()
for book in books:
print(book.title, book.get_absolute_url()) # can call model methods
# values() - dictionaries, useful for APIs or when you only need specific fields
book_dicts = Book.objects.values('title', 'price')
# [{'title': 'Django Basics', 'price': Decimal('25.99')}, ...]
for b in book_dicts:
print(b['title'])
# values_list() - tuples, more compact than dicts
book_tuples = Book.objects.values_list('title', 'price')
# [('Django Basics', Decimal('25.99')), ...]
# flat=True - use ONLY when selecting a SINGLE field, gives a flat list
titles = Book.objects.values_list('title', flat=True)
# ['Django Basics', 'Advanced Django', ...]
# Performance benefit: values()/values_list() skip creating full model
# instances, reducing memory usage and query overhead for large datasets
# when you only need a subset of fields
# Useful for populating dropdowns or simple lookups
author_names = Author.objects.values_list('name', flat=True).distinct()
# only() and defer() - alternative approach, still returns model instances
# but limits which fields are fetched from the database
books = Book.objects.only('title', 'price') # only these fields loaded initially
books2 = Book.objects.defer('description') # all fields EXCEPT this one loaded initially
Q10. How do you use database transactions and atomic() in Django?
Django's atomic() ensures a block of database operations either all succeed together or all roll back together, preventing partial updates that leave data in an inconsistent state.
from django.db import transaction
# Using atomic() as a context manager
def transfer_funds(from_account_id, to_account_id, amount):
with transaction.atomic():
from_account = Account.objects.select_for_update().get(id=from_account_id)
to_account = Account.objects.select_for_update().get(id=to_account_id)
if from_account.balance < amount:
raise ValueError('Insufficient funds')
from_account.balance -= amount
from_account.save()
to_account.balance += amount
to_account.save()
# If ANY exception occurs inside this block, BOTH updates roll back
# Using atomic() as a decorator - the whole function is one transaction
@transaction.atomic
def create_order(user, items):
order = Order.objects.create(user=user)
for item in items:
OrderItem.objects.create(order=order, product=item['product'], qty=item['qty'])
Product.objects.filter(id=item['product'].id).update(
stock=models.F('stock') - item['qty'])
return order
# select_for_update() - locks selected rows until the transaction completes,
# preventing race conditions when multiple requests modify the same rows
with transaction.atomic():
account = Account.objects.select_for_update().get(id=1)
account.balance += 100
account.save()
# Nested atomic blocks use savepoints internally
with transaction.atomic():
order = Order.objects.create(user=user)
try:
with transaction.atomic(): # creates a savepoint
risky_operation()
except SomeException:
pass # only the inner block rolls back, outer transaction continues
# Manually rolling back within a transaction
with transaction.atomic():
do_something()
if some_condition:
transaction.set_rollback(True) # marks the transaction for rollback
Django Models & ORM
Master Django database integrations. Learn model declarations, migrations, custom managers, queryset lookups, and database migrations.
What is Django's ORM and what problem does it solve?
Django's ORM (Object-Relational Mapper) lets developers interact with the database using Python classes and me...
What is the difference between ForeignKey, OneToOneField, and ManyToManyField in Django models?
Field typeRelationshipExampleForeignKeyMany-to-oneMany Books, one AuthorOneToOneFieldOne-to-oneOne User, one P...
What are Django migrations and how do they work?
Migrations are Django's way of tracking and applying changes to your database schema over time, generated auto...
What is the difference between QuerySet.filter(), .exclude(), and .get()?
MethodReturnsNo match behaviorMultiple matches behaviorfilter()QuerySet (possibly empty)Returns empty QuerySet...
What is the N+1 query problem in Django and how do select_related() and prefetch_related() fix it?
The N+1 problem occurs when fetching a list of objects triggers one additional query PER object to load relate...
What is the difference between Django's abstract base classes and multi-table inheritance for models?
TypeDatabase tables createdUse caseAbstract base classOnly for child models, parent has NO tableShare common f...
What are Django signals and when should you use them?
Signals let certain senders notify a set of receivers when specific actions occur (like a model being saved or...
What is the difference between Model.objects.create() and Model.save()?
MethodSteps requiredReturnsobjects.create()One step - instantiate and save togetherThe saved instanceModel() +...
What is the difference between values(), values_list(), and regular QuerySets?
MethodReturnsElement typeRegular QuerySetFull model instancesModel objects with all fields/methodsvalues()Dict...
How do you use database transactions and atomic() in Django?
Django's atomic() ensures a block of database operations either all succeed together or all roll back together...