简体   繁体   中英

Dynamically counting objects in an aggregate Django

I have a relatively complex query to handle and I'm bashing my head against it.

Allow me to explain the scenario.

I want to return a queryset annotated with the number of cases a user has completed where they are certified.

Imagine my user has three roles. a_delegate , b_certified and c_accredited .

  • a_delegate tells us that the user is in training for course a , we don't want to count any completed cases for course a .

  • b_certified tells us that the user has completed training for course b and is certified. We need to count these completed cases

  • c_accredited tells us that the user has advanced past their training for course b . We need to count these completed cases

I am using the built-in User model that Django supplies, nothing special there. Roles are just the name of the built-in Group model that Django supplies again.

Now, I know this is likely to get "just write raw sql" comments, and that's fine. I'm not dismissing the possibility of using raw sql here. I just want to know if there is a way of doing this with the ORM first.

I have this function, which maps input to the relevant roles.

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

So, if the user has input the filter as a,b then:

  • converted_filters should return ['Type A', 'Type B']
  • converted_roles should return [['a_certified', 'a_accredited'], ['b_certified', 'b_accredited']]

If we consider what I mentioned earlier, User has three roles. a_delegate , b_certified and c_accredited so according to the filters above, we should only be looking at returning the count for cases for Type B .

For brevity I already have a Queryset with this user in it.

I need to filter this dependant on the input of the user so the more filters they apply the more counts are added.

I thought of using Sum, with a list of count aggregates within, but that throws 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()
    ))
)

I also thought of using Sum, with a generator of count aggregates within, but that throws 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()
    ))
)

Is there a way to make this work via the ORM?

The solution that I have come to creates an expression that can then be passed to annotate.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM