DEV Community

Cover image for Django notes #2 (ORM)
Elvin Seyidov
Elvin Seyidov

Posted on • Edited on

Django notes #2 (ORM)

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
Enter fullscreen mode Exit fullscreen mode
  • Usage:
User.objects.all()          # All users
User.active_users.all()     # Only active users
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
  • Aggregations: Use .annotate() and .aggregate().
def avg_age(self):
    return self.aggregate(Avg('age'))
Enter fullscreen mode Exit fullscreen mode
  • 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(), or list()).
  • .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()
Enter fullscreen mode Exit fullscreen mode
  • Filtering Data (filter() and exclude()):
# 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)
Enter fullscreen mode Exit fullscreen mode
  • get(): Retrieve a Single Object (raises DoesNotExistif not found):
product = Product.objects.get(id=1)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode
  • Chaining Filters (creates a new QuerySet without executing the previous one):
active_users = User.objects.filter(is_active=True).filter(age__gte=18)
Enter fullscreen mode Exit fullscreen mode
  • 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))
Enter fullscreen mode Exit fullscreen mode

Field Selection and Optimization

  • values() and values_list(): Selecting Specific Fields:
# Returns dictionaries
users = User.objects.values('name', 'email')

# Returns tuples
emails = User.objects.values_list('email', flat=True)
Enter fullscreen mode Exit fullscreen mode
  • select_related() and prefetch_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()
Enter fullscreen mode Exit fullscreen mode

Ordering and Slicing

  • Ordering Results:
products = Product.objects.order_by('-price')  # Descending order by price
Enter fullscreen mode Exit fullscreen mode
  • Slicing (Efficient Pagination):
top_products = Product.objects.all()[:10]  # First 10 products
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

Updating and Deleting Records

  • Bulk Update:
Product.objects.filter(price__lt=50).update(is_discounted=True)
Enter fullscreen mode Exit fullscreen mode
  • Bulk Delete:
Product.objects.filter(is_out_of_stock=True).delete()
Enter fullscreen mode Exit fullscreen mode

Evaluating QuerySets

  • Forcing Execution:
list(products)  # Executes the query immediately
Enter fullscreen mode Exit fullscreen mode
  • count(), exists(), and first():
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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

SQL Equivalent:

SELECT *, COUNT(posts.id) AS post_count FROM author
LEFT JOIN posts ON posts.author_id = author.id
GROUP BY author.id;
Enter fullscreen mode Exit fullscreen mode

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']}")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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;
Enter fullscreen mode Exit fullscreen mode

.get(): Fetching a Single Record

  • DoesNotExistif no record is found.
  • MultipleObjectsReturnedif more than one record is found.
product = Product.objects.get(id=1)

SELECT * FROM product WHERE id = 1 LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

.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;
Enter fullscreen mode Exit fullscreen mode

.exclude(): Fetching Records Excluding Certain Conditions

products = Product.objects.exclude(price__lt=50)

SELECT * FROM product WHERE NOT (price < 50);
Enter fullscreen mode Exit fullscreen mode

.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;
Enter fullscreen mode Exit fullscreen mode

.count(): Counting Records Efficiently

Product.objects.filter(is_active=True).count()

SELECT COUNT(*) FROM product WHERE is_active = TRUE;
Enter fullscreen mode Exit fullscreen mode

.exists(): Checking for Existence Efficiently

Product.objects.filter(is_active=True).exists()

SELECT 1 FROM product WHERE is_active = TRUE LIMIT 1;
Enter fullscreen mode Exit fullscreen mode

.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;
Enter fullscreen mode Exit fullscreen mode

.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, ...);
Enter fullscreen mode Exit fullscreen mode

.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;
Enter fullscreen mode Exit fullscreen mode

.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;
Enter fullscreen mode Exit fullscreen mode

All queries lazy or not, and behind query

Efficient Querying (select_related(), prefetch_related(), values(), values_list())

Subqueries and Expressions (Subquery, Exists, Case, When, F(), Q())

Transactions and Atomic Operations (transaction.atomic(), select_for_update())

Raw SQL Execution (.raw(), connection.cursor())

Field Lookups and Filtering (__contains, __icontains, __gte, __lte, __in)

Model Signals (pre_save, post_save, pre_delete, post_delete)

Complex Joins and Aggregations (OuterRef, Prefetch, Count, Sum, Avg, Max, Min)

Custom Fields and Methods in Models

Efficient Bulk Operations (bulk_create(), bulk_update())

ORM Performance Optimization (Indexing, Caching, N+1 Problem)

Constraints and Validation (unique_together, CheckConstraint)

Soft Deletes and Custom QuerySets

Window Functions and Analytics (Window, RowNumber, Rank, DenseRank).

Recursive Queries (Using CTE with Django’s With clause).

JSONField and PostgreSQL-Specific Features (JSON queries, ArrayField).

Database Functions in ORM (Coalesce, Concat, Length, Trunc).

QuerySet Optimization Techniques (iterator(), chunked(), avoiding .count()).

Deferred Constraints (Deferring unique constraints or FKs).

Custom Database Routers (Multiple databases management).

Advanced Lookups (regex, unaccent, full-text search in PostgreSQL).

Partial Indexes and Unique Constraints (index_together, unique_together).

Materialized Views Integration (Using views as models).

Dynamic QuerySet Filtering (filter() chaining and dynamic filters).

Audit Trails and History Tracking (Tracking changes to models).

Project Level VSC Settings, Linting, and Formatting Configuration

Automatically Populate Fields Based on Other Fields

Django Automatic Model Generation

String Representation of Any Object

Creating Custom Field Subclass

Creating a Custom Order Field

Custom Fields: Converting Values to and From a Database

A Bad Attempt to Explain How to Choose the Right Table Relationship

Performing Raw SQL Queries Without the ORM (only briefly mentioned .raw())

Django Debug Toolbar for SQL Optimization

Inheritance Optimization Exercises

Django Multiple Database Setup

Django Import CSV into Model

Create Django Fixtures from Excel/CSV File

Saving Pandas DataFrame to Django Model

Django Search (Search functionality within QuerySets)

Model Level Constraint (Beyond unique_together and CheckConstraint)

Foreign Key Deletion Constraints (CASCADE, PROTECT, etc.)

Custom Migration Actions (Custom operations in migrations)

Django Cron Job for Database User Management

Get vs. Filter in QuerySets (Detailed comparison)

Model Field Validators and Writing Custom Validators

Django Query Optimization with django-debug-toolbar (deep dive)

ManyToManyFields and Through-Models (Detailed usage)

COALESCE Function and Handling NULL Values

Content Types Framework and GenericForeignKey (Deep dive)

GeneratedField in Django 5 (New feature)

Graph Models Command for ER Diagrams

inspectdb Command for Multiple Databases

Database Routers for Multiple Databases (Advanced usage)

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Best practices for optimal infrastructure performance with Magento

Running a Magento store? Struggling with performance bottlenecks? Join us and get actionable insights and real-world strategies to keep your store fast and reliable.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️