From d6f59672ead17bc21d19d4f3f9a5658615dd26ec Mon Sep 17 00:00:00 2001 From: David McCann Date: Tue, 14 Sep 2010 10:10:20 +0300 Subject: [PATCH] refactored expand_filter_string from recursive to iterative --- managers.py | 98 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/managers.py b/managers.py index 987a7d8..5776cc2 100644 --- a/managers.py +++ b/managers.py @@ -2,51 +2,75 @@ from django.db import models #from django.db.models import Aggregate from .models import EavAttribute - -def expand_filter_string(model_cls, q_str, prefix='', extra_filters=None): +def expand_filter_string(q_str, root_cls): from .utils import EavRegistry - if not extra_filters: - extra_filters = {} - fields = q_str.split('__') + extra_filters = {} + # q_str is a filter argument, something like: + # zoo__animal__food__eav__vitamin_c__gt, where + # in this example 'food' would be registered as an entity, + # 'eav' is the proxy field name, and 'contains_vitamin_c' is the + # attribute. So once we split the above example would be a list with + # something we can easily process: + # ['zoo','animal','food','eav','vitamin_c', 'gt'] + filter_tokens = q_str.split('__') + current_cls = root_cls + upperbound = len(filter_tokens) - 1 + i = 0 + while i < upperbound: + current_cls_config = EavRegistry.get_config_cls_for_model(current_cls) + if current_cls_config and filter_tokens[i] == current_cls_config.proxy_field_name: + gr_field = current_cls_config.generic_relation_field_name + # this will always work, because we're iterating over the length of the tokens - 1. + # if someone just specifies 'zoo__animal_food__eav' as a filter, + # they'll get the appropriate error: column 'eav' doesn't existt (i.e. if i != 0), + # prepend all the + slug = filter_tokens[i + 1] + datatype = EavAttribute.objects.get(slug=slug).datatype + extra_filter_key = '%s__attribute__slug' % gr_field + # if we're somewhere in the middle of this filter argument + # joins up to this point + if i != 0: + extra_filter_key = '%s__%s' % ('__'.join(filter_tokens[0:i]), extra_filter_key) + extra_filters[extra_filter_key] = slug + # modify the filter argument in-place, expanding 'eav' into 'eav_values__value_' + filter_tokens = filter_tokens[0:i] + [gr_field, 'value_%s' % datatype] + filter_tokens[i + 2:] + # this involves a little indexing voodoo, because we inserted two elements in place of one + # original element + i += 1 + upperbound += 1 +# filter_tokens[0] = "%s__value_%s" % (gr_field, datatype) + else: + direct = False + # Is it not EAV, but also not another field? + try: + field_object, model, direct, m2m = current_cls._meta.get_field_by_name(filter_tokens[0]) + # If we've hit the end, i.e. a simple column attribute like IntegerField or Boolean Field, + # we're done modifying the tokens, so we can break out of the loop early + if direct: + return '__'.join(filter_tokens), extra_filters + else: + # It is a foreign key to some other model, so we need to keep iterating, looking for registered + # entity models to expand + current_cls = field_object.model + except models.FieldDoesNotExist: + # this is a bogus filter anyway on a non-existent attribute, let the call to the super filter throw the + # appropriate error + return '__'.join(filter_tokens), extra_filters - # Is it EAV? - config_cls = EavRegistry.get_config_cls_for_model(model_cls) - if len(fields) > 1 and config_cls and fields[0] == config_cls.proxy_field_name: - gr_field = config_cls.generic_relation_field_name - slug = fields[1] - datatype = EavAttribute.objects.get(slug=slug).datatype - extra_filter_key = '%s__attribute__slug' % gr_field - if prefix: - extra_filter_key = '%s__%s' % (prefix, extra_filter_key) - extra_filters[extra_filter_key] = slug - fields[0] = "%s__value_%s" % (gr_field, datatype) - fields.pop(1) - return '__'.join(fields), extra_filters - - - direct = False - # Is it not EAV, but also not another field? - try: - field_object, model, direct, m2m = model_cls._meta.get_field_by_name(fields[0]) - except models.FieldDoesNotExist: - return q_str, extra_filters - - # Is it a direct field? - if direct: - return q_str, extra_filters - else: - # It is a foreign key. - prefix = "%s__%s" % (prefix, fields[0]) if prefix else fields[0] - sub_q_str = '__'.join(fields[1:]) - retstring, dictionary = self.expand_filter_string(field_object.model, sub_q_str, prefix, extra_filters) - return ("%s__%s" % (fields[0], retstring), dictionary) + # regular loop forward + i += 1 + # at the end of the day, we return the modified keyword filter, and any additional filters needed to make this + # query work, for passing up to the super call to filter() + return '__'.join(filter_tokens), extra_filters class EntityManager(models.Manager): def filter(self, *args, **kwargs): qs = self.get_query_set().filter(*args) cls = self.model for lookup, value in kwargs.items(): - updated_lookup, extra_filters = expand_filter_string(cls, lookup) + print "In %s" % lookup + updated_lookup, extra_filters = expand_filter_string(lookup, cls) + print "Out %s %s" % (updated_lookup, extra_filters) extra_filters.update({updated_lookup: value}) qs = qs.filter(**extra_filters) return qs