Django ORM with objects

Stefan Alfbo - Oct 28 '23 - - Dev Community

I recently discovered a hidden gem in the Django ORM, the ability to handle application domain objects without any transformation before querying the database. This can be useful in a number of situations, such as when you need to perform complex queries on your data or when you need to use custom fields that are not directly represented in the database.

To show you how this works, let's take a look at an example based on the code from the Django tutorial, Writing your first Django app.

In that tutorial we are defining these database models.

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
Enter fullscreen mode Exit fullscreen mode

In my imaginary scenario, I have a domain class called DomainQuestion that represents a question in the application. The class looks like this:

from datetime import datetime
from dataclasses import dataclass

@dataclass
class DomainQuestion:
    id: int
    question: str
    publication: datetime

    def days_since_publication(self):
        delta = datetime.today() - self.publication
        return delta.days

    def __int__(self):
        return self.id
Enter fullscreen mode Exit fullscreen mode

The important thing to note here is the last function def __int__(self). This function allows us to use the DomainQuestion objects later in the code.

Now, when working with our database models and the Django ORM, we can do the following:

questions = [
    DomainQuestion(1, "How are you?", datetime(2023, 8, 3)),
    DomainQuestion(2, "Which is the latest version of Python?", datetime(2023, 9, 12)),
    DomainQuestion(3, "What is the capital of Sweden?", datetime(2023, 10, 1)),
]

filtered_questions = Question.objects.filter(pk__in=questions)
Enter fullscreen mode Exit fullscreen mode

The magic happens on the last line, pk__in=questions. Since the pk is of type int it expects each object in the list of questions to be of the same type. This is why we needed to define the __int__ function in our DomainQuestion class.

Before I discovered this, I was using list comprehension to make this type of query. For example:

ids = [q.id in q for questions]
filtered_questions = Question.objects.filter(pk__in=ids)
Enter fullscreen mode Exit fullscreen mode

However, this requires more code (plus an extra loop) and makes the query less readable, in my opinion.

This gem is really nice if you use your own primitive types for representing value objects and such in your domain.

Happy coding!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .