From 9662193aea2ee982bc8e553c62499aca5e606755 Mon Sep 17 00:00:00 2001 From: Keryn Knight Date: Wed, 28 Jul 2021 09:16:06 +0200 Subject: [PATCH] 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. --- django/contrib/admin/filters.py | 7 ++++--- django/contrib/admin/options.py | 11 ++++++----- django/contrib/contenttypes/fields.py | 11 +++++------ django/db/models/base.py | 4 ++-- django/db/models/deletion.py | 14 ++++++++------ django/db/models/fields/related.py | 8 ++++---- django/db/models/fields/related_descriptors.py | 9 ++++----- 7 files changed, 33 insertions(+), 31 deletions(-) diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 7f4690806bf..82dabe4d2c3 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -451,11 +451,12 @@ class EmptyFieldListFilter(FieldListFilter): if self.lookup_val not in ('0', '1'): raise IncorrectLookupParameters - lookup_condition = models.Q() + lookup_conditions = [] if self.field.empty_strings_allowed: - lookup_condition |= models.Q(**{self.field_path: ''}) + lookup_conditions.append((self.field_path, '')) 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': return queryset.filter(lookup_condition) return queryset.exclude(lookup_condition) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 7a5551f6504..dadd4acfa1f 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1,8 +1,7 @@ import copy import json -import operator import re -from functools import partial, reduce, update_wrapper +from functools import partial, update_wrapper from urllib.parse import quote as urlquote from django import forms @@ -1035,9 +1034,11 @@ class ModelAdmin(BaseModelAdmin): for bit in smart_split(search_term): if bit.startswith(('"', "'")) and bit[0] == bit[-1]: bit = unescape_string_literal(bit) - or_queries = [models.Q(**{orm_lookup: bit}) - for orm_lookup in orm_lookups] - queryset = queryset.filter(reduce(operator.or_, or_queries)) + or_queries = models.Q( + *((orm_lookup, bit) for orm_lookup in orm_lookups), + _connector=models.Q.OR, + ) + queryset = queryset.filter(or_queries) may_have_duplicates |= any( lookup_spawns_duplicates(self.opts, search_spec) for search_spec in orm_lookups diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index 4346e017859..fbd55606ae3 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -1,6 +1,5 @@ import functools import itertools -import operator from collections import defaultdict 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) # Group instances by content types. content_type_queries = ( - models.Q(**{ - '%s__pk' % self.content_type_field_name: content_type_id, - '%s__in' % self.object_id_field_name: {obj.pk for obj in objs} - }) + models.Q( + (f'{self.content_type_field_name}__pk', content_type_id), + (f'{self.object_id_field_name}__in', {obj.pk for obj in objs}), + ) for content_type_id, objs in itertools.groupby( sorted(instances, key=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 # instances' PK in order to match up instances: object_id_converter = instances[0]._meta.pk.to_python diff --git a/django/db/models/base.py b/django/db/models/base.py index f208067aae1..3c46afcf9ff 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -971,8 +971,8 @@ class Model(metaclass=ModelBase): op = 'gt' if is_next else 'lt' order = '' if is_next else '-' param = getattr(self, field.attname) - q = Q(**{'%s__%s' % (field.name, op): param}) - q = q | Q(**{field.name: param, 'pk__%s' % op: self.pk}) + q = Q((field.name, param), (f'pk__{op}', self.pk), _connector=Q.AND) + 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( '%s%s' % (order, field.name), '%spk' % order ) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 0493b0e1e20..d8d8b259903 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -1,6 +1,5 @@ -import operator from collections import Counter, defaultdict -from functools import partial, reduce +from functools import partial from itertools import chain from operator import attrgetter @@ -347,10 +346,13 @@ class Collector: """ Get a QuerySet of the related model to objs via related fields. """ - predicate = reduce(operator.or_, ( - query_utils.Q(**{'%s__in' % related_field.name: objs}) - for related_field in related_fields - )) + predicate = query_utils.Q( + *( + (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) def instances_with_model(self): diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index ac45127fde1..febaadbb5bd 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -359,12 +359,12 @@ class RelatedField(FieldCacheMixin, Field): select all instances of self.related_field.model related through this field to obj. obj is an instance of self.model. """ - base_filter = { - rh_field.attname: getattr(obj, lh_field.attname) + base_filter = ( + (rh_field.attname, getattr(obj, lh_field.attname)) for lh_field, rh_field in self.related_fields - } + ) descriptor_filter = self.get_extra_descriptor_filter(obj) - base_q = Q(**base_filter) + base_q = Q(*base_filter) if isinstance(descriptor_filter, dict): return base_q & Q(**descriptor_filter) elif descriptor_filter: diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 872a4c98dc6..dba9e14c868 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -866,18 +866,17 @@ def create_forward_many_to_many_manager(superclass, rel, reverse): do_not_call_in_templates = True 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 # filters. removed_vals_filters = (not isinstance(removed_vals, QuerySet) or removed_vals._has_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: - symmetrical_filters = Q(**{self.target_field_name: self.related_val}) + symmetrical_filters = Q((self.target_field_name, self.related_val)) if removed_vals_filters: - symmetrical_filters &= Q( - **{'%s__in' % self.source_field_name: removed_vals}) + symmetrical_filters &= Q((f'{self.source_field_name}__in', removed_vals)) filters |= symmetrical_filters return filters