[Fixed]-DJANGO – Queryset and model design

1👍

Django’s annotate functionality is great for adding properties at the time of querying. From the docs –

Per-object summaries can be generated using the annotate() clause. When an annotate() clause is specified, each object in the QuerySet will be annotated with the specified values.

In order to keep your querying performance-minded, you can use this in your manager and not make the (possibly very slow) call of related objects for each of your Articles. Once you have an annotated property, you can use it in your query. Since Django only executes your query when objects are called, you can use this annotation instead of counting the click_set, which would call a separate query per related item. The current_clicks method may still be useful to you, but if calling it for multiple articles your queries will add up quickly and cause a big performance hit.

Please note – I added a related_name of clicks keyword arg to your click_article field in order to use it in place of ‘click_set’.

In addition, you’ll see the use of Q objects in the query below. What this allows us to do is chain together multiple filters together. These can be nested while using AND (,) / OR(|) operands. So, a reading of the Q objects below would be:

Find all articles where article publish date is before now AND (article has no finish date OR article finish date is after now)

from django.db.models.query import Q,Count

class ArticleManager(models.Manager):
    def get_queryset(self):
        return super(ArticleManager, self).get_queryset().filter(
                    Q(article_publish_date__lte=timezone.now()),
                    ( Q(article_finish_date__isnull=True)|
                      Q(article_finish_date__gte=timezone.now())
                ).annotate(
                    click_count=Count('clicks')
                ).filter(
                    article_max_clicks__gt=click_count
                )

class Article(models.Model):
    actives = ArticleManager()
    def current_clicks(self):
        return self.clicks.count()

# Now you can call Article.actives.all() to get all active articles        

class Click(models.Model):
    click_article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='clicks') # added a related_name for more explicit calling of prefetch_related

Leave a comment