Model Managers
- Definition: Model Managers act as an interface between Django models and the database, allowing custom query definitions. Default manager is objects.
- Creating a Custom Manager:
class ActiveUserManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_active=True)
class User(models.Model):
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
objects = models.Manager() # Default manager
active_users = ActiveUserManager() # Custom manager
- Usage:
User.objects.all() # All users
User.active_users.all() # Only active users
Advanced Methods:
- Custom Query Methods: Define reusable filters.
def with_email(self):
return self.filter(email__isnull=False)
# Usage Examples
active_users_with_email = User.objects.with_email().filter(is_active=True)
- Aggregations: Use
.annotate()
and.aggregate()
.
def avg_age(self):
return self.aggregate(Avg('age'))
-
Key Point: Avoid overriding
get_queryset()
in the default manager to ensure full access to all records when needed. Use custom managers for specific queries.
QuerySets
What is a QuerySet?
- Definition: A QuerySet is a collection of database queries to retrieve, filter, update, or delete data from the database using Django ORM.
-
Lazy Evaluation: QuerySets are not executed until explicitly iterated, sliced, or converted (e.g., using
.count()
, orlist()
). -
.all()
→ Does NOT run the query immediately (it's lazy). -
.count()
,.exists()
,.first()
,list()
→ DO run the query immediately.
Creating QuerySets
- Retrieve All Objects:
products = Product.objects.all()
- Filtering Data (
filter()
andexclude()
):
# Get products with price > 100
expensive_products = Product.objects.filter(price__gt=100)
# Exclude products with price < 50
affordable_products = Product.objects.exclude(price__lt=50)
-
get()
: Retrieve a Single Object (raisesDoesNotExist
if not found):
product = Product.objects.get(id=1)
Advanced Filtering and Lookups
- Field Lookups:
# Case-insensitive search
users = User.objects.filter(username__icontains='elvin')
# Range lookup
products_in_range = Product.objects.filter(price__range=(100, 500))
- Chaining Filters (creates a new QuerySet without executing the previous one):
active_users = User.objects.filter(is_active=True).filter(age__gte=18)
- Q Objects for Complex Queries:
from django.db.models import Q
# OR condition
users = User.objects.filter(Q(is_active=True) | Q(is_staff=True))
# NOT condition
non_staff_users = User.objects.filter(~Q(is_staff=True))
Field Selection and Optimization
-
values()
andvalues_list()
: Selecting Specific Fields:
# Returns dictionaries
users = User.objects.values('name', 'email')
# Returns tuples
emails = User.objects.values_list('email', flat=True)
-
select_related()
andprefetch_related()
: Reducing Database Hits:
# For ForeignKey (single query using JOIN)
posts = Post.objects.select_related('author').all()
# For ManyToManyField (separate queries)
authors = Author.objects.prefetch_related('books').all()
Ordering and Slicing
- Ordering Results:
products = Product.objects.order_by('-price') # Descending order by price
- Slicing (Efficient Pagination):
top_products = Product.objects.all()[:10] # First 10 products
Aggregate Functions
- Using
.aggregate()
for Aggregation:
from django.db.models import Avg, Sum
stats = Product.objects.aggregate(Avg('price'), Sum('stock'))
print(stats) # {'price__avg': 150.0, 'stock__sum': 500}
Updating and Deleting Records
- Bulk Update:
Product.objects.filter(price__lt=50).update(is_discounted=True)
- Bulk Delete:
Product.objects.filter(is_out_of_stock=True).delete()
Evaluating QuerySets
- Forcing Execution:
list(products) # Executes the query immediately
-
count()
,exists()
, andfirst()
:
Product.objects.filter(is_active=True).count() # Total active products
Product.objects.filter(is_active=True).exists() # Check if any exist
Product.objects.filter(is_active=True).first() # First active product
Advanced QuerySet Methods (annotate()
, aggregate()
, only()
, defer()
)
annotate()
: Adding Calculated Fields to QuerySets
- Purpose: Adds calculated fields to each object in a QuerySet using aggregation functions.
- Common Use Cases: Counts, sums, averages, or other calculations on related models. Example: Counting related objects (e.g., blog posts per author):
from django.db.models import Count
authors = Author.objects.annotate(post_count=Count('posts'))
for author in authors:
print(f"{author.name} has {author.post_count} posts")
SQL Equivalent:
SELECT *, COUNT(posts.id) AS post_count FROM author
LEFT JOIN posts ON posts.author_id = author.id
GROUP BY author.id;
aggregate()
: Calculations on Entire QuerySets
- Purpose: Returns a dictionary with aggregate values for the whole QuerySet.
- Common Use Cases: Total sums, averages, min, max across a model. Example: Calculating total and average price of products:
from django.db.models import Sum, Avg
result = Product.objects.aggregate(total_price=Sum('price'), avg_price=Avg('price'))
print(f"Total Price: {result['total_price']}, Average Price: {result['avg_price']}")
only()
: Optimizing Queries by Selecting Specific Fields
- Purpose: Fetches only specified fields from the database, reducing data load.
- Use Case: Useful when you need a few fields out of many in a model. Example: Fetch only name and email fields:
- Accessing other fields later triggers an additional database query (lazy loading).
users = User.objects.only('name', 'email')
for user in users:
print(user.name, user.email)
defer()
: Excluding Specific Fields to Optimize Queries
- Purpose: Fetches all fields except the specified ones, deferring them until accessed.
- Use Case: Useful for models with large fields (e.g., text or file fields). Example: Defer bio field for user objects:
users = User.objects.defer('bio')
for user in users:
print(user.name) # No extra query
print(user.bio) # Triggers an extra query only when accessed
- Like
only()
, deferred fields cause an additional query when accessed.
SQL Queries Behind
.all()
: Fetching All Records
-
.all()
creates a lazy QuerySet, which executes the SQL query only when needed.
products = Product.objects.all()
SELECT * FROM product;
.get(): Fetching a Single Record
-
DoesNotExist
if no record is found. -
MultipleObjectsReturned
if more than one record is found.
product = Product.objects.get(id=1)
SELECT * FROM product WHERE id = 1 LIMIT 1;
.filter()
: Fetching Multiple Records with Conditions
-
.filter()
never raises exceptions if no results are found; it returns an empty QuerySet.
products = Product.objects.filter(price__gte=100)
SELECT * FROM product WHERE price >= 100;
.exclude()
: Fetching Records Excluding Certain Conditions
products = Product.objects.exclude(price__lt=50)
SELECT * FROM product WHERE NOT (price < 50);
.values()
and .values_list()
: Fetching Specific Columns
- Reduces data load by fetching only the required columns.
Product.objects.values('name', 'price')
Product.objects.values_list('name', flat=True)
SELECT name, price FROM product;
SELECT name FROM product;
.count()
: Counting Records Efficiently
Product.objects.filter(is_active=True).count()
SELECT COUNT(*) FROM product WHERE is_active = TRUE;
.exists()
: Checking for Existence Efficiently
Product.objects.filter(is_active=True).exists()
SELECT 1 FROM product WHERE is_active = TRUE LIMIT 1;
.select_related()
: Optimizing ForeignKey Joins
- Fetches related ForeignKey data in one SQL query using JOIN.
- Avoids the N+1 problem.
posts = Post.objects.select_related('author').all()
SELECT post.*, author.* FROM post
LEFT JOIN author ON post.author_id = author.id;
.prefetch_related()
: Optimizing ManyToMany and Reverse ForeignKey
- Executes two SQL queries (Fetches authors, Fetches related books using IN condition.)
authors = Author.objects.prefetch_related('books').all()
SELECT * FROM author;
SELECT * FROM book WHERE author_id IN (1, 2, 3, ...);
.update()
: Updating Records Efficiently
- Executes one SQL query without fetching objects into memory.
Product.objects.filter(is_active=False).update(is_active=True)
UPDATE product SET is_active = TRUE WHERE is_active = FALSE;
.delete()
: Deleting Records
- Executes one SQL query but cascades deletions if related models exist.
Product.objects.filter(is_out_of_stock=True).delete()
DELETE FROM product WHERE is_out_of_stock = TRUE;
Top comments (0)