Задать вопрос
taxicab33
@taxicab33
Python разработчик | Django

Почему Count выдаёт неверные результаты при подсчёт M2M поля?

У меня есть менеджер, который считает все освоенные навыки, за счёт подсчёта сессий по учебным материалам, прикреплённым к данному навыку.
class Trajectory(CommonSystemObjectModel):
    actives = models.ManyToManyField(
        'actives.Active',
        verbose_name='Цели профиля',
        through='actives.TrajectoryActive',
        blank=True
    )
    is_active = models.BooleanField(
        default=True,
        verbose_name='Активен ли профиль в данный момент'
    )

class TrajectoryActive(CommonPriorityDataAbstractModel):
    trajectory = models.ForeignKey(
        to='trajectories.Trajectory',
        on_delete=models.CASCADE,
        verbose_name='Профиль',
        related_name='trajectory_actives',
        db_index=True
    )
    active: Active = models.ForeignKey(
        to=Active,
        on_delete=models.CASCADE,
        verbose_name='Цель',
        related_name='active_trajectories',
        db_index=True
    )
    threats = models.ManyToManyField(
        to='threats.Threat',
        verbose_name='Навыки',
        through='threats.ActiveThreat'
    )

Модель навыка является расширение для m2m поля threats для модели TrajectoryActive, которая в свою очередь является расширением m2m поля actives для модели Trajectory (возможно из-за этого django сбоит)
class ActiveThreat(CommonPriorityDataAbstractModel):
    """Модель навыка"""
    threat: Threat = models.ForeignKey(
        to=Threat,
        on_delete=models.CASCADE,
        verbose_name='Навык',
        db_index=True,
        related_name='threat_actives'
    )
    trajectory_active = models.ForeignKey(
        to='actives.TrajectoryActive',
        on_delete=models.CASCADE,
        db_index=True,
        verbose_name='Цель, прикрепленная к профилю',
        related_name='active_threats'
    )
    content = models.ManyToManyField(
        CommonContentDataModel,
        verbose_name='УМы, которые прикреплены к навыку',
        blank=True,
        db_index=True,
        related_name='content_threats'
    )

    objects = ActiveThreatManager()


Менеджер
class ActiveThreatManager(CommonObjectContentTypeManager):

    @staticmethod
    def _get_is_completed_case() -> Case:
        """Навык является освоенным,
        если кол-во УМов больше 0 и кол-во юзеров больше 0 и
        кол-во сессий по УМам навыка >= кол-ву юзеров * кол-во УМов"""
        return Case(
            When(
                Q(users_count__gt=0) &
                Q(content_count__gt=0) &
                Q(content__isnull=False) &
                Q(completed_sessions_count__gte=F('users_count') * F('content_count')),
                then=True,
            ),
            default=False,
            output_field=models.BooleanField()
        )

    def default_metrics(self, is_completed: bool = None, **kwargs) -> QuerySet:
        """Получаем метрику навыков цели для подсчёта метрики целей профиля"""
        qs = super().get_queryset().filter(**kwargs).annotate(
            content_count=Count('content', distinct=True),  # выдаёт неверные результаты, независимо от distinct( без disitinct больше)
            completed_sessions_count=Count(  # также выдаёт неверные результаты,  т.к. ссылается на m2m поле content
                'content__sessions',
                filter=Q(
                    content__sessions__completion_status=COMPLETED_KEY,
                    content__sessions__user=F('trajectory_active__trajectory__users'),
                    content__sessions__completion_datetime__isnull=False
                ),
                distinct=True
            ),  
            users_count=Count('trajectory_active__trajectory__users', distinct=True)
        ).annotate(
            is_completed=self._get_is_completed_case()
        )
        return qs.filter(is_completed=is_completed) if is_completed else qs


# У каждого навыка аннотируется поле content_count
skill.content.add(conten1, content2)
skill.content.count()  # выдаёт 2
skill = ActiveThreat.objects.default_metrics(pk=skill.pk).first()  # получим аннотированную метрику
skill.content_count  # выдаёт 1
skill.content.count()  # всё так же выдаёт 2

Почему так происходит и как это решить, не прибегая к raw sql?
  • Вопрос задан
  • 75 просмотров
Подписаться 1 Сложный Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы