Replace instance_of OR query with IN statement

fix_request_path_info
Diederik van der Boor 2018-08-24 11:16:30 +02:00
parent 3d014a482c
commit 9042fdd689
No known key found for this signature in database
GPG Key ID: 4FA014E0305E73C1
3 changed files with 35 additions and 21 deletions

View File

@ -5,11 +5,11 @@ PolymorphicQuerySet support functions
from __future__ import absolute_import from __future__ import absolute_import
import copy import copy
from functools import reduce
from django.apps import apps from django.apps import apps
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models import Q
from django.db.models.fields.related import ForeignObjectRel, RelatedField from django.db.models.fields.related import ForeignObjectRel, RelatedField
from django.db.utils import DEFAULT_DB_ALIAS from django.db.utils import DEFAULT_DB_ALIAS
from django.utils import six from django.utils import six
@ -105,9 +105,9 @@ def _translate_polymorphic_filter_definition(queryset_model, field_path, field_v
# handle instance_of expressions or alternatively, # handle instance_of expressions or alternatively,
# if this is a normal Django filter expression, return None # if this is a normal Django filter expression, return None
if field_path == 'instance_of': if field_path == 'instance_of':
return _create_model_filter_Q(field_val, using=using) return create_instanceof_q(field_val, using=using)
elif field_path == 'not_instance_of': elif field_path == 'not_instance_of':
return _create_model_filter_Q(field_val, not_instance_of=True, using=using) return create_instanceof_q(field_val, not_instance_of=True, using=using)
elif '___' not in field_path: elif '___' not in field_path:
return None # no change return None # no change
@ -214,7 +214,7 @@ def translate_polymorphic_field_path(queryset_model, field_path):
return newpath return newpath
def _create_model_filter_Q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS): def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS):
""" """
Helper function for instance_of / not_instance_of Helper function for instance_of / not_instance_of
Creates and returns a Q object that filters for the models in modellist, Creates and returns a Q object that filters for the models in modellist,
@ -226,27 +226,32 @@ def _create_model_filter_Q(modellist, not_instance_of=False, using=DEFAULT_DB_AL
efficiently however (regarding the resulting sql), should an optimization efficiently however (regarding the resulting sql), should an optimization
be needed. be needed.
""" """
if not modellist: if not modellist:
return None return None
if not isinstance(modellist, (list, tuple)):
from .models import PolymorphicModel from .models import PolymorphicModel
if type(modellist) != list and type(modellist) != tuple:
if issubclass(modellist, PolymorphicModel): if issubclass(modellist, PolymorphicModel):
modellist = [modellist] modellist = [modellist]
else: else:
assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model' raise TypeError(
'PolymorphicModel: instance_of expects a list of (polymorphic) '
'models or a single (polymorphic) model'
)
def q_class_with_subclasses(model): contenttype_ids = _get_mro_content_type_ids(modellist, using)
q = models.Q(polymorphic_ctype=ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False)) q = Q(polymorphic_ctype__in=sorted(contenttype_ids))
for subclass in model.__subclasses__(): if not_instance_of:
q = q | q_class_with_subclasses(subclass) q = ~q
return q return q
qlist = [q_class_with_subclasses(m) for m in modellist]
q_ored = reduce(lambda a, b: a | b, qlist) def _get_mro_content_type_ids(models, using):
if not_instance_of: contenttype_ids = set()
q_ored = ~q_ored for model in models:
return q_ored ct = ContentType.objects.db_manager(using).get_for_model(model, for_concrete_model=False)
contenttype_ids.add(ct.pk)
subclasses = model.__subclasses__()
if subclasses:
contenttype_ids.update(_get_mro_content_type_ids(subclasses, using))
return contenttype_ids

View File

@ -1,12 +1,13 @@
import re import re
import uuid import uuid
import django from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models import Case, Count, Q, When from django.db.models import Case, Count, Q, When
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
from django.utils import six from django.utils import six
from polymorphic import query_translate
from polymorphic.managers import PolymorphicManager from polymorphic.managers import PolymorphicManager
from polymorphic.models import PolymorphicTypeUndefined from polymorphic.models import PolymorphicTypeUndefined
from polymorphic.tests.models import ( from polymorphic.tests.models import (
@ -343,6 +344,14 @@ class PolymorphicTests(TransactionTestCase):
ordered=False, ordered=False,
) )
def test_create_instanceof_q(self):
q = query_translate.create_instanceof_q([Model2B])
expected = sorted([
ContentType.objects.get_for_model(m).pk
for m in [Model2B, Model2C, Model2D]
])
self.assertEqual(dict(q.children), dict(polymorphic_ctype__in=expected))
def test_base_manager(self): def test_base_manager(self):
def base_manager(model): def base_manager(model):
return ( return (