Django Query API select_related в запросе с несколькими таблицами

Представьте, что я пытаюсь подсчитать количество сортов деревьев в определенном парке, а деревья сгруппированы в определенных полях на территории парка. Одна из загвоздок заключается в том, что деревья можно пересаживать, поэтому я хочу подсчитывать только существующие деревья и пустые места (removed IS NULL для деревьев, которые посажены в данный момент, и LEFT и RIGHT JOIN для мест, которые еще не посажены). После блуждания по множеству постов и документации Django select_related, я не могу понять, как сделать это через API запросов Django. У меня есть идентификатор парка, поэтому я буду делать это, начиная с модели Parks в views.py.

Я упускаю что-то очевидное (или неочевидное) в API запросов Django?

Следующий SQL делает то, что я хотел бы:

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;

Например:

+--------+-------+
| name   | count |
|--------+-------|
| <null> | 2     |
| HGG    | 2     |
| BOX    | 3     |
| GRV    | 1     |
+--------+-------+

Возможно 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( ... ) )

Германские части 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(…). Вы можете подсчитать Trees не удаленных для данного park_id с помощью:

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'))

У Cultivars, возникающего из этого QuerySet, будет дополнительный атрибут .tree_count с именем и количеством не удаленных Trees с my_park_id в качестве park_id.

Если вы также хотите получить Cultivars, для которых не было посажено ни одного дерева, мы можем работать с:

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),
        )
    )
)
Вернуться на верх