Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django models - how to filter number of ForeignKey objects

I have a models A and B, that are like this:

class A(models.Model):   title = models.CharField(max_length=20)   (...)  class B(models.Model):   date = models.DateTimeField(auto_now_add=True)   (...)   a = models.ForeignKey(A) 

Now I have some A and B objects, and I'd like to get a query that selects all A objects that have less then 2 B pointing at them.

A is something like a pool thing, and users (the B) join pool. if there's only 1 or 0 joined, the pool shouldn't be displayed at all.

Is it possible with such model design? Or should I modify that a bit?

like image 811
kender Avatar asked Nov 03 '08 10:11

kender


People also ask

How can I filter a Django query with a list of values?

To filter a Python Django query with a list of values, we can use the filter method with in . to search Blog entries with pk set to 1,4 or 7 by calling Blog. objects. filter with the pk_in argument set to [1, 4, 7] .

Can you filter a QuerySet?

Yes, you can reuse existing querysets. This is not really making anything faster though, in fact, this code block won't even execute a query against the database because Django QuerySets are lazily evaluated. What I means is that it won't send the query to the database until you actually need the values.

How does Django define many-to-one relationships?

For example, a Menu model record can have many Item model records associated with it and yet an Item belongs to a single Menu record. To define a one to many relationship in Django models you use the ForeignKey data type on the model that has the many records (e.g. on the Item model).


2 Answers

The question and selected answer are from 2008 and since then this functionality has been integrated into the django framework. Since this is a top google hit for "django filter foreign key count" I'd like to add an easier solution with a recent django version using Aggregation.

from django.db.models import Count cats = A.objects.annotate(num_b=Count('b')).filter(num_b__lt=2) 

In my case I had to take this concept a step further. My "B" object had a boolean field called is_available, and I only wanted to return A objects who had more than 0 B objects with is_available set to True.

A.objects.filter(B__is_available=True).annotate(num_b=Count('b')).filter(num_b__gt=0).order_by('-num_items') 
like image 182
gravitron Avatar answered Sep 18 '22 01:09

gravitron


Sounds like a job for extra.

A.objects.extra(     select={         'b_count': 'SELECT COUNT(*) FROM yourapp_b WHERE yourapp_b.a_id = yourapp_a.id',     },     where=['b_count < 2'] ) 

If the B count is something you often need as a filtering or ordering criterion, or needs to be displayed on list views, you could consider denormalisation by adding a b_count field to your A model and using signals to update it when a B is added or deleted:

from django.db import connection, transaction from django.db.models.signals import post_delete, post_save  def update_b_count(instance, **kwargs):     """     Updates the B count for the A related to the given B.     """     if not kwargs.get('created', True) or kwargs.get('raw', False):         return     cursor = connection.cursor()     cursor.execute(         'UPDATE yourapp_a SET b_count = ('             'SELECT COUNT(*) FROM yourapp_b '             'WHERE yourapp_b.a_id = yourapp_a.id'         ') '         'WHERE id = %s', [instance.a_id])     transaction.commit_unless_managed()  post_save.connect(update_b_count, sender=B) post_delete.connect(update_b_count, sender=B) 

Another solution would be to manage a status flag on the A object when you're adding or removing a related B.

B.objects.create(a=some_a) if some_a.hidden and some_a.b_set.count() > 1:     A.objects.filter(id=some_a.id).update(hidden=False)  ...  some_a = b.a some_b.delete() if not some_a.hidden and some_a.b_set.count() < 2:     A.objects.filter(id=some_a.id).update(hidden=True) 
like image 21
Jonny Buchanan Avatar answered Sep 20 '22 01:09

Jonny Buchanan