Django отчетливый связанный запрос - PullRequest
1 голос
/ 26 сентября 2019

У меня есть две модели:

Модель A - это модель AbstractUserModel, а модель B

class ModelB:
    user = ForeignKey(User, related_name='modelsb')
    timestamp = DateTimeField(auto_now_add=True)

Я хочу узнать, сколько пользователей имеют хотя бы один объект ModelB, созданный хотя бы за 3 из 7 прошедших дней .

Пока что я нашел способ сделать это, но я точно знаю, что естьлучше, и именно поэтому я отправляю этот вопрос.

Я в основном разделил запрос на 2 части.

Part1:
Я добавил метод foo в модель пользователя, который проверяет, соответствует ли пользователь указанным выше условиям

def foo(self):
    past_limit = starting_date - timedelta(days=7)
    return self.modelsb.filter(timestamp__gte=past_limit).order_by('timestamp__day').distinct('timestamp__day').count() > 2

Часть 2 :
В Диспетчере нестандартных пользователей я нахожу пользователей, которые имеют более 2 объектов modelsb за последние 7 дней, и перебираю их, применяя метод foo для каждого из них.
Делая это, я сужаю итерации необходимого цикла for.(в основном это функция фильтра, но вы понимаете, в чем дело)

def boo(self):
    past_limit = timezone.now() - timedelta(days=7)
    candidates = super().get_queryset().annotate(rc=Count('modelsb', filter=Q(modelsb__timestamp__gte=past_limit))).filter(rc__gt=2)
    return list(filter(lambda x: x.foo(), candidates))

Тем не менее, я хочу знать, есть ли более эффективный способ сделать это, без цикла for.

1 Ответ

2 голосов
/ 26 сентября 2019

Вы можете использовать условную аннотацию.

Я не смог протестировать этот запрос, но что-то вроде этого должно работать:

from django.db.models import Q, Count

past_limit = starting_date - timedelta(days=7)
users = User.objects.annotate(
                    modelsb_in_last_seven_days=Count('modelsb__timestap__day', 
                        filter=Q(modelsb__timestamp__gte=past_limit), 
                        distinct=True))
            .filter(modelsb_in_last_seven_days__gte = 3)

РЕДАКТИРОВАТЬ:

Это решение не сработало, потому что опция отдельный указывает, какое поле делает запись отличной.

Я провел некоторые эксперименты на своем собственном экземпляре Django и нашел способ сделать эту работу, используя SubQuery.Это работает так, что мы генерируем подзапрос, в котором мы делаем различие самостоятельно.

counted_modelb = ModelB.objects
    .filter(user=OuterRef('pk'), timestamp__gte=past_limit)
    .values('timestamp__day')
    .distinct()
    .annotate(count=Count('timestamp__day'))
    .values('count')

query = User.objects
    .annotate(modelsb_in_last_seven_days=Subquery(counted_modelb, output_field=IntegerField()))
    .filter(modelsb_in_last_seven_days__gt = 2)

Это аннотирует каждую строку в наборе запросов с количеством всех различных дней в modelb для пользователя, с датой, большейчем выбранный день.

В подзапросе я использую values('timestamp__day'), чтобы убедиться, что могу сделать distinct() (поскольку комбинация distinct('timestamp__day') и annotate() не поддерживается.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...