Динамический подсчет объектов в совокупности Джанго - PullRequest
0 голосов
/ 09 июля 2019

У меня довольно сложный запрос, и я бьюсь головой об этом.

Позвольте мне объяснить сценарий.

Я хочу вернуть набор запросов, помеченный количеством дел, которые user завершил, когда они сертифицированы.

Представь, что у моего пользователя три роли. a_delegate, b_certified и c_accredited.

  • a_delegate говорит нам, что user готовится к курсу a, мы не хотим считать завершенные дела по курсу a.

  • b_certified сообщает нам, что user прошел обучение по курсу b и сертифицирован. Нам нужно посчитать эти завершенные дела

  • c_accredited говорит нам, что user продвинулся дальше своего обучения для курса b. Нам нужно посчитать эти завершенные дела

Я использую встроенную модель User, которую поставляет Django, ничего особенного. Роли - это просто название встроенной модели Group, которую поставляет Django снова.

Теперь я знаю, что это, вероятно, получит "просто написать raw sql" комментарии, и это нормально. Я не отказываюсь от возможности использования сырых SQL здесь. Я просто хочу знать, есть ли способ сделать это сначала с ORM.

У меня есть эта функция, которая отображает входные данные для соответствующих ролей.

def convert_filter_str(str: str) -> Tuple:
    """
    Converts expected filters into course names and returns them
    and the relevant roles as a Tuple
    """

    APPLIANCES: Dict = {
        'a': 'Type A',
        'b': 'Type B',
        'c': 'Type C',
    }

    ROLES: Dict = {
        'a': ['a_certified', 'a_accredited'],
        'b': ['b_certified', 'b_accredited'],
        'c': ['c_certified', 'c_accredited'],
    }

    filters: List = str.split(',')
    converted_filters: List = []
    converted_roles: List = []

    for filter in filters:
        filter = filter.strip()
        converted_item = APPLIANCES[filter]
        converted_role = ROLES[filter]
        converted_filters.append(converted_item)
        converted_roles.append(converted_role)

    return converted_filters, converted_roles

Итак, если пользователь ввел фильтр как a,b, тогда:

  • converted_filters должен вернуться ['Type A', 'Type B']
  • converted_roles должен вернуться [['a_certified', 'a_accredited'], ['b_certified', 'b_accredited']]

Если мы рассмотрим то, что я упоминал ранее, User имеет три роли. a_delegate, b_certified и c_accredited, поэтому в соответствии с фильтрами, приведенными выше, мы должны смотреть только на возврат подсчета для случаев для Type B.

Для краткости у меня уже есть Queryset с этим пользователем.

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

Я думал об использовании Суммы со списком агрегатов, но это выдает django.db.utils.ProgrammingError: can't adapt type 'Count'

final_qs: User = user.annotate(
    completed_cases=(Sum(
        [Count(
            'patientcase',
            filter=Q(
                groups__name__in=role_filter[i]
            )
        ) for i in range(len(role_filter))],
        output_field=IntegerField()
    ))
)

Я также подумал об использовании Sum с генератором агрегатов количества, но это выдает psycopg2.ProgrammingError: can't adapt type 'generator'

final_qs: User = user.annotate(
    completed_cases=(Sum(
        (Count(
            'patientcase',
            filter=Q(
                groups__name__in=role_filter[i]
            )
        ) for i in range(len(role_filter))),
        output_field=IntegerField()
    ))
)

Есть ли способ заставить это работать через ORM?

1 Ответ

0 голосов
/ 09 июля 2019

Решение, к которому я пришел, создает выражение, которое затем может быть передано для аннотирования.

def build_filtered_count(appliance_filter, role_filter):
    """
    Dynamically builds count exprerssions based on the filters
    passed to the function
    """

    counts = [Count(
        'patientcase',
        filter=Q(
            groups__name__in=role_filter[i]
        ), distinct=True
    ) for i in range(len(role_filter))]

    return sum(counts)
...