Refs #32946 -- Changed internal usage of dynamic Q() objects construction to use non-kwargs initialization.

This prefers non-kwargs construction of dynamically generated Q()
objects to create a single Q() object instead of many and then
combining them, where possible.
This commit is contained in:
Keryn Knight 2021-07-28 09:16:06 +02:00 committed by Mariusz Felisiak
parent 5b8ef8aa5b
commit 9662193aea
7 changed files with 33 additions and 31 deletions

View File

@ -451,11 +451,12 @@ class EmptyFieldListFilter(FieldListFilter):
if self.lookup_val not in ('0', '1'): if self.lookup_val not in ('0', '1'):
raise IncorrectLookupParameters raise IncorrectLookupParameters
lookup_condition = models.Q() lookup_conditions = []
if self.field.empty_strings_allowed: if self.field.empty_strings_allowed:
lookup_condition |= models.Q(**{self.field_path: ''}) lookup_conditions.append((self.field_path, ''))
if self.field.null: if self.field.null:
lookup_condition |= models.Q(**{'%s__isnull' % self.field_path: True}) lookup_conditions.append((f'{self.field_path}__isnull', True))
lookup_condition = models.Q(*lookup_conditions, _connector=models.Q.OR)
if self.lookup_val == '1': if self.lookup_val == '1':
return queryset.filter(lookup_condition) return queryset.filter(lookup_condition)
return queryset.exclude(lookup_condition) return queryset.exclude(lookup_condition)

View File

@ -1,8 +1,7 @@
import copy import copy
import json import json
import operator
import re import re
from functools import partial, reduce, update_wrapper from functools import partial, update_wrapper
from urllib.parse import quote as urlquote from urllib.parse import quote as urlquote
from django import forms from django import forms
@ -1035,9 +1034,11 @@ class ModelAdmin(BaseModelAdmin):
for bit in smart_split(search_term): for bit in smart_split(search_term):
if bit.startswith(('"', "'")) and bit[0] == bit[-1]: if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
bit = unescape_string_literal(bit) bit = unescape_string_literal(bit)
or_queries = [models.Q(**{orm_lookup: bit}) or_queries = models.Q(
for orm_lookup in orm_lookups] *((orm_lookup, bit) for orm_lookup in orm_lookups),
queryset = queryset.filter(reduce(operator.or_, or_queries)) _connector=models.Q.OR,
)
queryset = queryset.filter(or_queries)
may_have_duplicates |= any( may_have_duplicates |= any(
lookup_spawns_duplicates(self.opts, search_spec) lookup_spawns_duplicates(self.opts, search_spec)
for search_spec in orm_lookups for search_spec in orm_lookups

View File

@ -1,6 +1,5 @@
import functools import functools
import itertools import itertools
import operator
from collections import defaultdict from collections import defaultdict
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -571,16 +570,16 @@ def create_generic_related_manager(superclass, rel):
queryset = queryset.using(queryset._db or self._db) queryset = queryset.using(queryset._db or self._db)
# Group instances by content types. # Group instances by content types.
content_type_queries = ( content_type_queries = (
models.Q(**{ models.Q(
'%s__pk' % self.content_type_field_name: content_type_id, (f'{self.content_type_field_name}__pk', content_type_id),
'%s__in' % self.object_id_field_name: {obj.pk for obj in objs} (f'{self.object_id_field_name}__in', {obj.pk for obj in objs}),
}) )
for content_type_id, objs in itertools.groupby( for content_type_id, objs in itertools.groupby(
sorted(instances, key=lambda obj: self.get_content_type(obj).pk), sorted(instances, key=lambda obj: self.get_content_type(obj).pk),
lambda obj: self.get_content_type(obj).pk, lambda obj: self.get_content_type(obj).pk,
) )
) )
query = functools.reduce(operator.or_, content_type_queries) query = models.Q(*content_type_queries, _connector=models.Q.OR)
# We (possibly) need to convert object IDs to the type of the # We (possibly) need to convert object IDs to the type of the
# instances' PK in order to match up instances: # instances' PK in order to match up instances:
object_id_converter = instances[0]._meta.pk.to_python object_id_converter = instances[0]._meta.pk.to_python

View File

@ -971,8 +971,8 @@ class Model(metaclass=ModelBase):
op = 'gt' if is_next else 'lt' op = 'gt' if is_next else 'lt'
order = '' if is_next else '-' order = '' if is_next else '-'
param = getattr(self, field.attname) param = getattr(self, field.attname)
q = Q(**{'%s__%s' % (field.name, op): param}) q = Q((field.name, param), (f'pk__{op}', self.pk), _connector=Q.AND)
q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk}) q = Q(q, (f'{field.name}__{op}', param), _connector=Q.OR)
qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by( qs = self.__class__._default_manager.using(self._state.db).filter(**kwargs).filter(q).order_by(
'%s%s' % (order, field.name), '%spk' % order '%s%s' % (order, field.name), '%spk' % order
) )

View File

@ -1,6 +1,5 @@
import operator
from collections import Counter, defaultdict from collections import Counter, defaultdict
from functools import partial, reduce from functools import partial
from itertools import chain from itertools import chain
from operator import attrgetter from operator import attrgetter
@ -347,10 +346,13 @@ class Collector:
""" """
Get a QuerySet of the related model to objs via related fields. Get a QuerySet of the related model to objs via related fields.
""" """
predicate = reduce(operator.or_, ( predicate = query_utils.Q(
query_utils.Q(**{'%s__in' % related_field.name: objs}) *(
for related_field in related_fields (f'{related_field.name}__in', objs)
)) for related_field in related_fields
),
_connector=query_utils.Q.OR,
)
return related_model._base_manager.using(self.using).filter(predicate) return related_model._base_manager.using(self.using).filter(predicate)
def instances_with_model(self): def instances_with_model(self):

View File

@ -359,12 +359,12 @@ class RelatedField(FieldCacheMixin, Field):
select all instances of self.related_field.model related through select all instances of self.related_field.model related through
this field to obj. obj is an instance of self.model. this field to obj. obj is an instance of self.model.
""" """
base_filter = { base_filter = (
rh_field.attname: getattr(obj, lh_field.attname) (rh_field.attname, getattr(obj, lh_field.attname))
for lh_field, rh_field in self.related_fields for lh_field, rh_field in self.related_fields
} )
descriptor_filter = self.get_extra_descriptor_filter(obj) descriptor_filter = self.get_extra_descriptor_filter(obj)
base_q = Q(**base_filter) base_q = Q(*base_filter)
if isinstance(descriptor_filter, dict): if isinstance(descriptor_filter, dict):
return base_q & Q(**descriptor_filter) return base_q & Q(**descriptor_filter)
elif descriptor_filter: elif descriptor_filter:

View File

@ -866,18 +866,17 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
do_not_call_in_templates = True do_not_call_in_templates = True
def _build_remove_filters(self, removed_vals): def _build_remove_filters(self, removed_vals):
filters = Q(**{self.source_field_name: self.related_val}) filters = Q((self.source_field_name, self.related_val))
# No need to add a subquery condition if removed_vals is a QuerySet without # No need to add a subquery condition if removed_vals is a QuerySet without
# filters. # filters.
removed_vals_filters = (not isinstance(removed_vals, QuerySet) or removed_vals_filters = (not isinstance(removed_vals, QuerySet) or
removed_vals._has_filters()) removed_vals._has_filters())
if removed_vals_filters: if removed_vals_filters:
filters &= Q(**{'%s__in' % self.target_field_name: removed_vals}) filters &= Q((f'{self.target_field_name}__in', removed_vals))
if self.symmetrical: if self.symmetrical:
symmetrical_filters = Q(**{self.target_field_name: self.related_val}) symmetrical_filters = Q((self.target_field_name, self.related_val))
if removed_vals_filters: if removed_vals_filters:
symmetrical_filters &= Q( symmetrical_filters &= Q((f'{self.source_field_name}__in', removed_vals))
**{'%s__in' % self.source_field_name: removed_vals})
filters |= symmetrical_filters filters |= symmetrical_filters
return filters return filters