Django - How to get count of all sub-fields connected with object

Viewed 52

I am trying to make some kind of points system that would gather count of segments

Here's how the models are structured:

from django.db import models

# Create your models here.
class Person(models.Model):
    name = models.CharField(max_length=64)


class Event(models.Model):
    name = models.CharField(max_length=64)


class Segment(models.Model):
    name = models.CharField(max_length=64)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)


class Presence(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    segments = models.ManyToManyField(Segment)

Basically I'm looking for the best way to get the count of segments in which Person has taken part in, f.e.:

John = Person Instance()

Event1 = Event Instance()
Event2 = Event Instance()

Segment1 = Segment Instance(event=Event1)
Segment2 = Segment Instance(event=Event1)
Segment3 = Segment Instance(event=Event2)
Segment4 = Segment Instance(event=Event2)
Segment5 = Segment Instance(event=Event2)

Presence1 = Presence Instance(
    person = John,
    segments = [
        Segment1,
        Segment4,
        Segment5,
    ]
)

Presence2 = Presence Instance(
    person = John,
    segments = [
        Segment2,
        Segment3,
    ]
) 

What I'm looking for is the way to count that John has taken part in 5 segments

If this could be helpful, I will be using it in django-rest-framework so if it's easier to do it directly in there I'm open to any suggestions

Also is there a way to connect ManyToManyField choice with Foreign Key like that:

class Event(models.Model):
    name = models.CharField(max_length=64)


class Segment(models.Model):
    name = models.CharField(max_length=64)
    event = models.ForeignKey(Event, on_delete=models.CASCADE)


class Presence(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    particular_event = models.ForeignKey(Event, on_delete=models.CASCADE)
    segments = models.ManyToManyField(Segment, event=particular_event)  # Like this

So that when I will choose the segments only ones that occur in particular event will be available to choose

Thanks in advance to anyone who will try to help!

1 Answers

Use annotate and aggregate for getting manyToMany count:

Presence.objects.filter(person=person1).annotate(segments_count=Count('segments')).aggregate(
                total_participation=Sum('segments_count'))['total_participation']

This will count correctly even when the person is not participated in any segment:

from django.db.models import Count, Sum
from django.test import TestCase

from events.models import Person, Event, Segment, Presence


class EventsTestCase(TestCase):

    def test_presence_count_with_active_participation(self):
        person1 = Person.objects.create(name='person1')

        event1 = Event.objects.create(name='event1')
        event2 = Event.objects.create(name='event2')

        segment1 = Segment.objects.create(name='segment1', event=event1)
        segment2 = Segment.objects.create(name='segment2', event=event1)
        segment3 = Segment.objects.create(name='segment3', event=event2)
        segment4 = Segment.objects.create(name='segment4', event=event2)
        segment5 = Segment.objects.create(name='segment5', event=event2)

        presence1 = Presence.objects.create(person=person1)
        presence1.segments.set([segment1, segment4, segment5])

        presence2 = Presence.objects.create(person=person1)
        presence2.segments.set([segment2, segment3])

        self.assertEqual(
            Presence.objects.filter(person=person1).annotate(segments_count=Count('segments')).aggregate(
                total_participation=Sum('segments_count'))['total_participation'],
            5
        )

    def test_presence_count_with_no_participation(self):
        person1 = Person.objects.create(name='person1')

        event1 = Event.objects.create(name='event1')
        event2 = Event.objects.create(name='event2')

        segment1 = Segment.objects.create(name='segment1', event=event1)
        segment2 = Segment.objects.create(name='segment2', event=event1)
        segment3 = Segment.objects.create(name='segment3', event=event2)
        segment4 = Segment.objects.create(name='segment4', event=event2)
        segment5 = Segment.objects.create(name='segment5', event=event2)

        presence1 = Presence.objects.create(person=person1)
        presence2 = Presence.objects.create(person=person1)

        self.assertEqual(
            Presence.objects.filter(person=person1).annotate(segments_count=Count('segments')).aggregate(
                total_participation=Sum('segments_count'))['total_participation'],
            0
        )
Related