From 39e6a21403eaf8ebc0e1a7f66b3f180d0837ce7a Mon Sep 17 00:00:00 2001 From: David Gelvin Date: Fri, 17 Sep 2010 12:50:28 +0300 Subject: [PATCH] Added documentation and tests --- managers.py | 25 +++++++++++++++- tests/filters.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/managers.py b/managers.py index 18669a0..9aabf1c 100644 --- a/managers.py +++ b/managers.py @@ -5,16 +5,23 @@ from django.db import models from .models import EavAttribute, EavValue def eav_filter(func): + ''' + Decorator used to wrap filter and exlclude methods. Passes args through + expand_q_filters and kwargs through expand_eav_filter. Returns the + called function (filter or exclude) .distinct() + ''' @wraps(func) def wrapper(self, *args, **kwargs): new_args = [] for arg in args: if isinstance(arg, models.Q): # modify Q objects (warning: recursion ahead) - new_args.append(expand_q_filters(arg, self.model)) + arg = expand_q_filters(arg, self.model) + new_args.append(arg) new_kwargs = {} for key, value in kwargs.items(): + # modify kwargs (warning: recursion ahead) new_key, new_value = expand_eav_filter(self.model, key, value) new_kwargs.update({new_key: new_value}) @@ -23,6 +30,11 @@ def eav_filter(func): def expand_q_filters(q, root_cls): + ''' + Takes a Q object and a model class. + Recursivley passes each filter / value in the Q object tree leaf nodes + through expand_eav_filter + ''' new_children = [] for qi in q.children: if type(qi) is tuple: @@ -38,6 +50,17 @@ def expand_q_filters(q, root_cls): def expand_eav_filter(model_cls, key, value): + ''' + Accepts a model class and a key, value. + Recurisively replaces any eav filter with a subquery. + + For example: + key = 'eav__height' + value = 5 + Would return: + key = 'eav_values__in' + value = EavValues.objects.filter(value_int=5, attribute__slug='height') + ''' from .utils import EavRegistry fields = key.split('__') diff --git a/tests/filters.py b/tests/filters.py index d410bb0..7f5d3f2 100644 --- a/tests/filters.py +++ b/tests/filters.py @@ -161,10 +161,81 @@ class EavFilterTests(TestCase): self.assertEqual(User.objects.exclude(eav__city='Paris').count(), 2) - #TODO Exclude and EAV Q objects are broken! - #self.assertEqual(User.objects.exclude(Q(eav__city='Paris')).count(), 2) - self.assertEqual(User.objects.filter(Q(eav__city='Paris') & \ Q(username='Bob')).count(), 1) self.assertEqual(User.objects.filter(Q(eav__city='Paris', username='Jack')).count(), 0) + + def test_you_can_filter_entity_by_q_objects_with_lookups(self): + class UserEav(EavConfig): + manager_field_name = 'eav_objects' + EavRegistry.register(User, UserEav) + + EavAttribute.objects.create(datatype=EavAttribute.TYPE_INT, + name='Height') + EavAttribute.objects.create(datatype=EavAttribute.TYPE_FLOAT, + name='Weight') + u = User.objects.create(username='Bob') + u.eav.height = 10 + u.eav.weight = 20 + u.save() + u = User.objects.create(username='Jack') + u.eav.height = 20 + u.eav.weight = 10 + u.save() + u = User.objects.create(username='Fred') + u.eav.height = 15 + u.eav.weight = 15 + u.save() + ''' + This is what we have now: + + Username Hieght Weight + -------- ------ ------ + Bob 10 20 + Jack 20 10 + Fred 15 15 + ''' + + self.assertEqual(User.eav_objects.filter(eav__height__gt=12).count(), 2) + self.assertEqual(User.eav_objects.filter(Q(eav__height__gt=12)).count(), 2) + self.assertEqual(User.eav_objects.filter(eav__height__gte=20).count(), 1) + self.assertEqual(User.eav_objects.filter(Q(eav__height__gte=20)).count(), 1) + + self.assertEqual(User.eav_objects.filter(Q(eav__height__gte=20) & Q(username='Fred')).count(), 0) + self.assertEqual(User.eav_objects.filter(Q(eav__height=15) & Q(username='Fred')).count(), 1) + + self.assertEqual(User.eav_objects.filter(eav__height=20, eav__weight=10).count(), 1) + + self.assertEqual(User.eav_objects.filter(Q(eav__height=20) | Q(eav__weight=10) | Q(eav__weight=15)).count(), 2) + + def test_broken_eav_filters(self): + EavRegistry.register(User) + + EavAttribute.objects.create(datatype=EavAttribute.TYPE_INT, + name='Height') + EavAttribute.objects.create(datatype=EavAttribute.TYPE_FLOAT, + name='Weight') + u = User.objects.create(username='Bob') + u.eav.height = 10 + u.eav.weight = 20 + u.eav.city = 'Paris' + u.eav.country = 'France' + u.save() + u = User.objects.create(username='Jack') + u.eav.height = 20 + u.eav.weight = 10 + u.eav.city = 'New York' + u.eav.country = 'Paris' + u.save() + u = User.objects.create(username='Fred') + u.eav.height = 15 + u.eav.weight = 15 + u.eav.city = 'Georgetown' + u.eav.country = 'Guyana' + u.save() + + self.assertEqual(User.objects.exclude(Q(eav__city='Paris')).count(), 2) + self.assertEqual(User.objects.filter(Q(eav__height=20) & Q(eav__weight=10)).count(), 1) + +