1

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 )

1
  • I don't see how you need .select_related(..) here. Commented Dec 2, 2024 at 22:18

1 Answer 1

0

I don't see how you need .select_related(…) here. You could count the Trees not removed for a given park_id with:

from django.db.models import Count

Cultivars.objects.filter(
    tree__removed=None, tree_location__field__park_id=my_park_id
).annotate(tree_count=Count('tree'))

The Cultivars arising from this QuerySet will have an extra attribute named .tree_count with the number of not-removed Trees with my_park_id as park_id.

If you also want to retrieve Cultivarss for which no tree was planted, we can work with:

from django.db.models import Case, Count, F, Value, When

Cultivars.objects.annotate(
    tree_count=Count(
        Case(
            When(
                tree__removed=None,
                tree_location__field__park_id=my_park_id,
                then=F('tree'),
            ),
            default=Value(None),
        )
    )
)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.