From 17e41c4f7f257f35d3499b0f55f7f6a322ba22f7 Mon Sep 17 00:00:00 2001 From: Diederik van der Boor Date: Fri, 12 Jul 2019 11:37:13 +0200 Subject: [PATCH] Rewrite translate_polymorphic_field_path() avoid closures Inspired by PR #259 to look at this --- polymorphic/query_translate.py | 79 ++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/polymorphic/query_translate.py b/polymorphic/query_translate.py index 29513b9..5c8f773 100644 --- a/polymorphic/query_translate.py +++ b/polymorphic/query_translate.py @@ -5,9 +5,11 @@ PolymorphicQuerySet support functions from __future__ import absolute_import import copy +from collections import deque from django.apps import apps from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import FieldError from django.db import models from django.db.models import Q from django.db.models.fields.related import ForeignObjectRel, RelatedField @@ -166,41 +168,10 @@ def translate_polymorphic_field_path(queryset_model, field_path): except models.FieldDoesNotExist: pass - # function to collect all sub-models, this should be optimized (cached) - def add_all_sub_models(model, result): - if issubclass(model, models.Model) and model != models.Model: - # model name is occurring twice in submodel inheritance tree => Error - if model.__name__ in result and model != result[model.__name__]: - e = 'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s!\n' - e += 'In this case, please use the syntax: applabel__ModelName___field' - assert model, e % ( - model._meta.app_label, model.__name__, - result[model.__name__]._meta.app_label, result[model.__name__].__name__) - - result[model.__name__] = model - - for b in model.__subclasses__(): - add_all_sub_models(b, result) - - submodels = {} - add_all_sub_models(queryset_model, submodels) + submodels = _get_all_sub_models(queryset_model) model = submodels.get(classname, None) assert model, 'PolymorphicModel: model %s not found (not a subclass of %s)!' % (classname, queryset_model.__name__) - # create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC - # 'modelb__modelc" is returned - def _create_base_path(baseclass, myclass): - bases = myclass.__bases__ - for b in bases: - if b == baseclass: - return myclass.__name__.lower() - path = _create_base_path(baseclass, b) - if path: - if b._meta.abstract or b._meta.proxy: - return myclass.__name__.lower() - return path + '__' + myclass.__name__.lower() - return '' - basepath = _create_base_path(queryset_model, model) if negated: @@ -216,6 +187,50 @@ def translate_polymorphic_field_path(queryset_model, field_path): return newpath +def _get_all_sub_models(base_model): + """#Collect all sub-models, this should be optimized (cached)""" + result = {} + queue = deque([base_model]) + + while queue: + model = queue.popleft() + if issubclass(model, models.Model) and model != models.Model: + # model name is occurring twice in submodel inheritance tree => Error + if model.__name__ in result and model != result[model.__name__]: + raise FieldError( + 'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n' + 'In this case, please use the syntax: applabel__ModelName___field' % ( + model._meta.app_label, model.__name__, + result[model.__name__]._meta.app_label, + result[model.__name__].__name__ + ) + ) + + result[model.__name__] = model + queue.extend(model.__subclasses__()) + + return result + + +def _create_base_path(baseclass, myclass): + # create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC + # 'modelb__modelc" is returned + bases = myclass.__bases__ + for b in bases: + if b == baseclass: + return _get_query_related_name(myclass) + path = _create_base_path(baseclass, b) + if path: + if b._meta.abstract or b._meta.proxy: + return myclass.__name__.lower() + return path + '__' + _get_query_related_name(myclass) + return '' + + +def _get_query_related_name(myclass): + return myclass.__name__.lower() + + def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS): """ Helper function for instance_of / not_instance_of