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)
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
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)
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)
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!