Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I write a Django ORM query for the reverse relationship in a one-to-many relationship?

I'm using Django and Python 3.7. I have the following two models in my models.py file ...

class Article(models.Model):
    created_on = models.DateTimeField(default=datetime.now)
...


class ArticleStat(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, )
    elapsed_time_in_seconds = models.IntegerField(default=0, null=False)

I would like to write a Django ORM query where I select articles have a stat that's at least 5 minutes (300 seconds) old. However, I don't know how to reference the ArticleStat object from the Article object. Unsurprisingly, this

Article.objects.filter(articlestat.elapsed_time_in_seconds.lte==300)

produces a

NameError: name 'articlestat' is not defined

error.

Edit: Per the answer, I changed my ArticleStat model to

class ArticleStat(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlestats')

and then I ran the below query getting the error displayed

Article.objects.filter(articlestat_set__elapsed_time_in_seconds__lte==300)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'articlestat_set__elapsed_time_in_seconds__lte' is not defined

Thought maybe there was an instance with plurality, so I tried an "s", but got an error ..

Article.objects.filter(articlestats_set__elapsed_time_in_seconds__lte==300)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'articlestats_set__elapsed_time_in_seconds__lte' is not defined
like image 207
Dave Avatar asked Jan 21 '19 15:01

Dave


1 Answers

The core issue here is the NameError for articlestat so I will address that first.

As explained in the django documentation your backward relation name by default is defined as FOO_set which in your case means articlestat_set.

If a model has a ForeignKey, instances of the foreign-key model will have access to a Manager that returns all instances of the first model. By default, this Manager is named FOO_set, where FOO is the source model name, lowercased.

If you prefer a different name you can do so by specifying a related_name in your ForeignKey definition e.g.

class ArticleStat(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlestats')
    elapsed_time_in_seconds = models.IntegerField(default=0, null=False)

The second issue is how to properly follow relations which is explained quite extensively here which is why I will not go into detail about it in this answer. The gist is that instead of the . operator you want to use __ (double underscore). The same goes for field lookups which you need for comparison in this query.

With both these issues fixed your query should look like this:

Article.objects.filter(articlestat_set__elapsed_time_in_seconds__lte=300)

or with a custom related_name e.g. related_name='articlestats':

Article.objects.filter(articlestats__elapsed_time_in_seconds__lte=300)
like image 105
Fynn Becker Avatar answered Sep 25 '22 14:09

Fynn Becker