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.