Understanding Django ORM

Ellen Chan - Oct 8 - - Dev Community

What is ORM?

Object-relational mapping(ORM) is a feature in Django that allows us to interact with databases using Python code without writing SQL queries. The ORM translates CRUD operations into SQL under the hood, enabling easy creation, retrieval, updating, and deletion of database objects.

Working with ORM

In Django, a model class represents a database table, and an instance of that class represents a record in the table.

Every model has at least one Manager, which is called objects. We can retrieve records from the database through this manager, resulting in a QuerySet.

QuerySets are lazy, meaning that results aren't fetched until explicitly requested.

Common QuerySet methods
filter(): Retrieve records matching certain criteria.
all(): Retrieve all records.
order_by(): Order records based on specific fields.
distinct(): Return unique records.
annotate(): Add aggregate values to each record.
aggregate(): Calculate a value from a queryset.
defer(): Load only some fields of a model, deferring others.

Advanced ORM Features

Q and F Objects allow for complex queries and efficient database-level operations. We can use 'Q' for queries that involve OR conditions, while 'F' allows you to reference model fields directly in queries.

from django.db.models import Q, F

# Using Q to filter published posts or created after a specific date
posts = Post.objects.filter(Q(status='published') | Q(created_at__gte='2024-01-01'))

# Using F to compare fields within a model (e.g., for a discount calculation)
class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    discounted_price = models.DecimalField(max_digits=10, decimal_places=2)

# Retrieve products where discounted price is less than price
discounted_products = Product.objects.filter(discounted_price__lt=F('price'))
Enter fullscreen mode Exit fullscreen mode

Query Expressions (referencing model fields) and Database Functions (applying SQL-like functions) both allow us to perform operations at the database level instead of pulling data into Python for processing. This helps optimize queries and reduce database load.

from django.db.models import Count, Max

# Count the number of posts for each status
status_count = Post.objects.values('status').annotate(count=Count('id'))

# Get the latest created post
latest_post = Post.objects.aggregate(latest=Max('created_at'))

Enter fullscreen mode Exit fullscreen mode

Custom managers let us add extra manager methods or modify the QuerySet the manager initially returns.

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status='published')

class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    status = models.CharField(max_length=50)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = models.Manager()  # Default manager
    published = PublishedManager()  # Custom manager for published posts

# Use the custom manager to get published posts
published_posts = Post.published.all()

Enter fullscreen mode Exit fullscreen mode

ContentType is a model which is useful for creating generic relationships between models without specifying them with direct foreign keys. Common use cases include comments or tags that need to be attached to different types of models.

from django.contrib.contenttypes.models import ContentType

# Example model for comments
class Comment(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    text = models.TextField()

# Creating a comment for a Post instance
post = Post.objects.get(id=1)
comment = Comment.objects.create(
    content_type=ContentType.objects.get_for_model(Post),
    object_id=post.id,
    text='Great post!'
)
Enter fullscreen mode Exit fullscreen mode

Transactions bundle database operations as a single unit ensuring data consistency. We can use the @transaction.atomic decorator or the transaction.atomic() context manager to wrap code in a transaction block.

from django.db import transaction

# Using a transaction block
with transaction.atomic():
    post = Post.objects.create(title='New Post', content='Content here...', status='published')
    # Any other database operations will be part of this transaction
Enter fullscreen mode Exit fullscreen mode

Django allows executing raw SQL queries for complex query where you need flexibility. However, it should be used with caution.

from django.db import connection

def get_published_posts():
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM blog_post WHERE status = %s", ['published'])
        rows = cursor.fetchall()
    return rows
Enter fullscreen mode Exit fullscreen mode

Conclusion

Django's ORM simplifies database interactions by providing a high-level API for working with models, managers, and queries. Understanding and utilizing these features can greatly enhance your productivity and the performance of your applications.

.
Terabox Video Player