#django #pattern #python

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.

 1import datetime
 2
 3from django.db import models
 4
 5class TickerQuerySet(models.QuerySet):
 6    def annotate_latest_price(self):
 7        return self.annotate(
 8            latest_price=TickerPrice.objects.filter(
 9                ticker=models.OuterRef("pk")
10            )
11            .order_by("-close_date")
12            .values("price")[:1]
13        )
14
15    def prefetch_related_yesterday_and_today_prices(self):
16        today = datetime.datetime.today()
17        yesterday = today - datetime.timedelta(days=1)
18        return self.prefetch_related(
19            models.Prefetch(
20                "ticker_prices",
21                queryset=TickerPrice.objects.filter(
22                    models.Q(close_date=today)
23                    | models.Q(close_date=yesterday)
24                ),
25            )
26        )
27
28class TickerManager(models.Manager):
29    def get_queryset(self):
30        return TickerQuerySet(self.model, using=self._db)
31
32class Ticker(models.Model):
33    symbol = models.CharField(max_length=50, unique=True)
34    objects = TickerManager()
35
36class TickerPrice(models.Model):
37    ticker = models.ForeignKey(
38        Ticker, on_delete=models.CASCADE, related_name="ticker_prices"
39    )
40    price = models.DecimalField(max_digits=7, decimal_places=2)
41    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.

1tickers_with_prefetch = (
2    Ticker.objects.all().prefetch_related_yesterday_and_today_prices()
3)
1tickers_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.