Django Tips:
Advanced Django Tips
Q Objects for Complex Queries:
Example models.
from django.db import models
class Ticker(models.Model):
symbol = models.CharField(max_length=50, unique=True)
class TickerPrice(models.Model):
ticker = models.ForeignKey(
Ticker, on_delete=models.CASCADE, related_name="ticker_prices"
)
price = models.DecimalField(max_digits=7, decimal_places=2)
close_date = models.DateField()
Q objects can be combined together with &, representing AND, or |, representing OR. Let’s look at an example query.
today_and_yesterday_prices = TickerPrice.objects.filter(
models.Q(close_date=today) | models.Q(close_date=yesterday)
)
# Fetching the ticker prices with close dates of today or yesterday.
today_and_yesterday_greater_than_1000 = TickerPrice.objects.filter(
models.Q(price__gt=1000),
(models.Q(close_date=today) | models.Q(close_date=yesterday)),
)
# Getting all prices with a close date of today or yesterday
# with a price greater than 1000
today_and_yesterday_greater_than_1000_without_BRK = (
TickerPrice.objects.filter(
models.Q(price__gt=1000),
~models.Q(ticker__symbol__startswith="BRK"),
(models.Q(close_date=today) | models.Q(close_date=yesterday)),
)
)
# Fetching all ticker prices greater than 1000 that don’t start
# with BRK with close dates of either today or yesterday.
# We added the condition that the ticker’s symbol does not start with BRK
Prefetch Related and Select Related:
Optimize Database Calls with Prefetch Related and Select Related. We use prefetch related
when we want to fetch a reverse foreign key or a many to many relationship. We use select related
when we want to fetch a foreign key or a one to one relationship.
# prefetch related
apple_with_all_prices = Ticker.objects.prefetch_related(
"ticker_prices"
).get(symbol="AAPL")
# Select related
latest_prices = TickerPrice.objects.filter(
close_date=today
).select_related("ticker")
Annotate Querysets to Fetch Specific Values
Annotating a queryset enables us to add attributes to each object in the queryset. Annotations can be a reference to a value on the model or related model or an expression such as a sum or count.
tickers_with_latest_price = Ticker.objects.annotate(
latest_price=TickerPrice.objects.filter(
ticker=models.OuterRef("pk")
)
.order_by("-close_date")
.values("price")[:1]
)
tickers_with_latest_price = (
Ticker.objects.annotate(
latest_price=TickerPrice.objects.filter(ticker=models.OuterRef("pk"))
.order_by("-close_date")
.values("price")[:1]
)
.filter(latest_price__gte=50)
)
This queryset fetches all the tickers and annotates each ticker object with a latest_price
attribute. The latest price comes from the most recent related ticker price. The OuterRef
allows us to reference the primary key of the ticker object. We use order_by
to get the most recent price and use values
to select only the price. Finally, the [:1]
ensures we retrieve only one TickerPrice
object.
Use Prefetch Objects to Control Your Prefetch Related
Prefetch objects enable Django developers to control the operation of prefetch related. When we pass in a string argument to prefetch related, we’re saying fetch all of the related objects. A prefetch object lets us pass in a custom queryset to fetch a subset of the related objects.
tickers_with_prefetch = Ticker.objects.all().prefetch_related(
models.Prefetch(
"ticker_prices",
queryset=TickerPrice.objects.filter(
models.Q(close_date=today)
| models.Q(close_date=yesterday)
),
)
)
Define Custom Query Sets and Model Managers for Code Reuse
Custom model managers and custom querysets let Django developers add extra methods to or modify the initial queryset for a model. Using these promotes the “don’t repeat yourself” (DRY) principle in software development and promotes reuse of common queries.
import datetime
from django.db import models
class TickerQuerySet(models.QuerySet):
def annotate_latest_price(self):
return self.annotate(
latest_price=TickerPrice.objects.filter(
ticker=models.OuterRef("pk")
)
.order_by("-close_date")
.values("price")[:1]
)
def prefetch_related_yesterday_and_today_prices(self):
today = datetime.datetime.today()
yesterday = today - datetime.timedelta(days=1)
return self.prefetch_related(
models.Prefetch(
"ticker_prices",
queryset=TickerPrice.objects.filter(
models.Q(close_date=today)
| models.Q(close_date=yesterday)
),
)
)
class TickerManager(models.Manager):
def get_queryset(self):
return TickerQuerySet(self.model, using=self._db)
class Ticker(models.Model):
symbol = models.CharField(max_length=50, unique=True)
objects = TickerManager()
class TickerPrice(models.Model):
ticker = models.ForeignKey(
Ticker, on_delete=models.CASCADE, related_name="ticker_prices"
)
price = models.DecimalField(max_digits=7, decimal_places=2)
close_date = models.DateField()
In the above code, we’ve created a custom queryset with some of the previously demonstrated queries as methods. We added this new queryset to our custom manager and overrode the default objects
manager on the Ticker
model with our custom manager. With the custom manager and queryset, we can do the following.
tickers_with_prefetch = (
Ticker.objects.all().prefetch_related_yesterday_and_today_prices()
)
tickers_with_latest_price = Ticker.objects.all().annotate_latest_price()
Instead of having to write the actual query for each of these examples, we call the methods defined in the custom queryset. This is especially useful if we use these queries in multiple places throughout the codebase.
Use of functools.partial in Django
Creating many upload_to
callables for FileField
s
upload_to
callables for FileField
sfrom functools import partial
from django.db import models
def user_upload_to(instance, filename, category):
return f"users/{instance.id}/{category}/{filename}"
class User(models.Model):
...
profile_picture = models.ImageField(
upload_to=partial(user_upload_to, category="profile"),
)
background_picture = models.ImageField(
upload_to=partial(user_upload_to, category="background"),
transaction.on_commit()
callbacks
transaction.on_commit()
callbacksfrom django.db import transaction
# With a function
def send_email():
send_admin_email(
subject="Author created",
# ...
)
transaction.on_commit(send_email)
# With a lambda
transaction.on_commit(
lambda: send_admin_email(
subject="Author created",
# ...
)
)
# With partial
transaction.on_commit(
partial(
send_admin_email,
subject="Author created",
# ...
)
)
Database functions
from functools import partial
from django.db.models.functions import Substr
FirstChar = partial(Substr, pos=1, length=1)
from example.core.models import Author, FirstChar
Author.objects.annotate(first_char=FirstChar("name")).filter(first_char="C")
Last updated
Was this helpful?