Как аннотировать поле M2M модели Django и получить список отдельных экземпляров?

У меня есть две модели Django Profile и Device с отношениями ManyToMany друг с другом следующим образом:

class Profile(models.Model):
    devices = models.ManyToManyField(Device, related_name='profiles')

Я пытаюсь использовать annotate() и Count() для запроса всех профилей, имеющих 1 или более устройств, следующим образом:

profiles = Profile.objects.annotate(dev_count=Count('devices')).filter(dev_count__gt=1)

Это здорово, это дает мне QuerySet со всеми профилями (4500+) с одним или несколькими устройствами, как и ожидалось.

Далее, из-за связи M2M, я хотел бы получить список всех отдельных устройств среди всех профилей из предыдущего набора запросов. Все мои неудачные попытки ниже возвращают пустой набор запросов. Я прочитал документацию по values, values_list и annotate, но все еще не могу понять, как сделать правильный запрос здесь.

devices = profiles.values('devices').distinct()
devices = profiles.values_list('devices', flat=True).distinct()

Я также пытался сделать это за один раз:

devices = (
    Profile.objects.values_list('devices', flat=True)
                .annotate(dev_count=Count('devices'))
                .filter(dev_count__gt=1)
                .distinct()
)

Вы не можете работать с .values(), так как этот элемент появляется и в пункте SELECT, и в пункте GROUP BY, поэтому тогда вы начинаете упоминать поле, и, следовательно, COUNT(devices) вернет 1 для каждой группы.

Вы можете отфильтровать Device, которые связаны хотя бы с одним из этих Profile с помощью:

profiles = Profile.objects.annotate(
    dev_count=Count('devices')
).filter(dev_count__gt=1)

devices = Device.objects.filter(profile__in=profiles).distinct()

Для некоторых диалектов SQL, обычно MySQL, лучше сначала материализовать список profile и не работать с подзапросом, поэтому:

profiles = Profile.objects.annotate(
    dev_count=Count('devices')
).filter(dev_count__gt=1)
profiles_list = list(profiles)

devices = Device.objects.filter(profile__in=profiles_list).distinct()
Вернуться на верх