Alexander Dunaev
Alexander Dunaev

Reputation: 990

Django: how to build relations and queries the best way?

I'm new to Django, and I'm having difficulties with related objects. What is so easy in terms of SQL seems to be quite tricky here.

I have a simple DB that looks like this:

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models

class Language(models.Model):
  code = models.CharField(max_length = 2)
  name = models.CharField(max_length = 20)

class Translation(models.Model):
  content_type = models.ForeignKey(ContentType)
  object_id = models.PositiveIntegerField()
  content_object = GenericForeignKey('content_type', 'object_id')

  language = models.ForeignKey(Language)
  title = models.TextField()
  text = models.TextField()

class Article(models.Model):
  pub_date = models.DateTimeField(verbose_name = "Publication date")
  visible = models.BooleanField(default = False)
  translations = GenericRelation(Translation)

This is essentially a follow-up to my previous question. Thanks to Andrea Corbellini, the above model looks very clear and simple.

Now I want to show the articles on the web page. There is a current language, and I need to select only articles that 1) are visible and 2) have a translation to the current language. In SQL this would mean joining several tables and filtering them in a single query.

How to do that in Django? This question actually includes two questions: how to make a fast query and how to pass the result to the template.

What I do now is first filtering by visible and ordering by pub_date, then iterating through the query set, picking the articles which have a proper translation, and finally copying their fields into the context because the template engine doesn't seem to understand how to access the related objects. Here is the code; it looks very awkward:

  articles = Article.objects.filter(visible = True).order_by('-pub_date')
  recent_news = []
  for article in articles:
    content = article.content(translation.get_language())
    if not content.exists():
      continue
    content = content[0]
    recent_news.append({
      'pub_date': article.pub_date,
      'title': content.title,
      'abstract': content.text,
      'text': content.text
    })
  context = { 'recent_news_list': recent_news }

What would help here is something like a 'meta model' or some middleware that would do some of this job and cache the result so requesting articles for some language would require a single query. But is that a good way?

Upvotes: 0

Views: 88

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 600059

Since all the information you need to send to the template is in the Translation model, I would do the query on that model, and send that to the template. The only thing you need that is in the Article model is the visible field, so you can filter on that:

article = ContentType.objects.get_for_model(Article)
article_ids = Article.objects.filter(visible=True).values_list('id')
translations = Translation.objects.filter(content_type=article, object_id__in=article_ids)
context = {'recent_news_list: translations}

This is just three queries: one to get the ContentType (although this will probably be cached automatically), one to get the translations, and one to get the articles (and Django may indeed do the third as a subquery of the second).

It's always going to be slightly complicated though; that's the price you pay for using generic relations. I know your other question does have multiple types related to Translation, but if you can get away without doing that things would indeed be much simpler.

Upvotes: 1

Related Questions