Imagine I'm trying to count the number of tree cultivars in a certain park, and the trees are grouped in specific fields within the park. One catch is that trees can be replanted, so I want to count only existing trees and empty locations (removed IS NULL for trees currently planted, and the LEFT and RIGHT JOIN for locations not yet planted). After wandering through lots of Django select_related posts and documentation, I cannot see how to do this through Django's Query API. I have the park ID, so I'd be doing this starting with the Parks model in views.py.
Am I missing something obvious (or not obvious) in Django's Query API?
The following SQL does what I'd like:
WITH p AS (
SELECT t.cultivar, l.id AS loc
FROM trees t
JOIN locations l ON t.location = l.id
JOIN fields d ON l.field = d.id
WHERE d.park = 'SOME_PARK_ID'
AND t.removed IS NULL
)
SELECT c.name, count(*)
FROM p
LEFT JOIN cultivars c ON p.cultivar = c.id
RIGHT JOIN locations l ON p.loc = l.id
GROUP BY name;
For example:
+--------+-------+
| name | count |
|--------+-------|
| <null> | 2 |
| HGG | 2 |
| BOX | 3 |
| GRV | 1 |
+--------+-------+
Possible views.py:
class ParkDetail( DetailView ):
model = Parks
# Would like to get a count of cultivars here
def get_context_data( self, **kwargs ): # ... Lost here
qry = Parks.objects.select_related( 'tree__location__field' ).filter( tree.removed is None )
tree_count = qry.annotate( Count( ... ) )
Germane parts of models.py:
class Parks( models.Model ):
name = models.CharField( max_length = 64, blank = False )
class Fields( models.Model ):
park = models.ForeignKey( Parks, on_delete = models.CASCADE )
class Locations( models.Model ):
field = models.ForeignKey( Fields, on_delete = models.CASCADE )
class Cultivars( models.Model ):
name = models.CharField( max_length = 64, blank = False )
class Trees( models.Model ):
location = models.ForeignKey( Locations, on_delete = models.CASCADE )
cultivar = models.ForeignKey( Cultivars, on_delete = models.PROTECT )
planted = models.DateField( blank = False )
removed = models.DateField( blank = True, null = True )
.select_related(..)here.