Anonymous avatar Anonymous committed 1e23dbf

added support for standard lookup types for JOINs, added unit tests for this case

Comments (0)

Files changed (3)

dbindexer/backends.py

 from django.db.models.sql.constants import JOIN_TYPE, LHS_ALIAS, LHS_JOIN_COL, \
     TABLE_NAME, RHS_JOIN_COL
 from djangotoolbox.fields import ListField
-from copy import deepcopy
 
 class BaseResolver(object):
     def __init__(self):
         if not field_to_index:
             return 
         
-        index_field = deepcopy(lookup.field_to_add)        
+        index_field = lookup.get_field_to_add(field_to_index)        
         config_field = index_field.item_field if \
             isinstance(index_field, ListField) else index_field  
         if hasattr(field_to_index, 'max_length') and \
     
     def _convert_filter(self, lookup, query, filters, child, index):
         constraint, lookup_type, annotation, value = child
-        lookup_type, value = lookup.convert_lookup(value, annotation)
+        lookup_type, value = lookup.convert_lookup(value, lookup_type)
         constraint.field = query.get_meta().get_field(lookup.index_name)
         constraint.col = constraint.field.column
         child = constraint, lookup_type, annotation, value
         
         column_index = self.get_column_index(query, constraint)
         field_name = self.column_to_name.get(column_index)
-        
+
         if field_name is None:
             return
 

dbindexer/lookups.py

             new_cls.lookup_types = (new_cls.lookup_types, )
         return new_cls 
 
-
 class ExtraFieldLookup(object):
     '''Default is to behave like an exact filter on an ExtraField.'''
     __metaclass__ = LookupBase
     def index_name(self):
         return 'idxf_%s_l_%s' % (self.field_name, self.lookup_types[0])
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'exact', value
-    
-    def convert_value(self, value):
-        return value
         
     def matches_filter(self, model, field_name, lookup_type, value):
         return self.model == model and lookup_type in self.lookup_types \
         if lookup_def in cls.lookup_types:
             return True
         return False
+    
+    def get_field_to_add(self, field_to_index):
+        # return a deepcopy!
+        return deepcopy(self.field_to_add)
 
 class DateLookup(ExtraFieldLookup):
     def __init__(self, *args, **kwargs):
         defaults.update(kwargs)
         ExtraFieldLookup.__init__(self, *args, **defaults)
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'exact', value
 
 class Day(DateLookup):
         defaults.update(kwargs)
         ExtraFieldLookup.__init__(self, *args, **defaults)
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'startswith', value
 
     def convert_value(self, value):
 class Icontains(Contains):
     lookup_types = 'icontains'
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'startswith', value.lower()
 
     def convert_value(self, value):
 class Iexact(ExtraFieldLookup):
     lookup_types = 'iexact'
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'exact', value.lower()
     
     def convert_value(self, value):
 class Istartswith(ExtraFieldLookup):
     lookup_types = 'istartswith'
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'startswith', value.lower()
 
     def convert_value(self, value):
 class Endswith(ExtraFieldLookup):
     lookup_types = 'endswith'
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'startswith', value[::-1]
 
     def convert_value(self, value):
 class Iendswith(ExtraFieldLookup):
     lookup_types = 'iendswith'
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'startswith', value[::-1].lower()
 
     def convert_value(self, value):
     def is_icase(self):
         return self.lookup_def.flags & re.I
     
-    def convert_lookup(self, value, annotation):
+    def convert_lookup(self, value, lookup_type):
         return 'exact', True
 
     def convert_value(self, value):
             return True
         return False 
 
-# used for JOINs
-#class StandardLookup(ExtraFieldLookup):
-#    def __init__(self):
-#        # to get the field type for a JOIN definition
-#        self.join_resolver = JOINResolver
+class StandardLookup(ExtraFieldLookup):
+    ''' Creates a copy of the field_to_index in order to allow querying for 
+        standard lookup_types on a JOINed property. '''
+    # TODO: database backend can specify standardLookups
+    lookup_types = ('exact', 'gt', 'gte', 'lt', 'lte', 'in', 'range', 'isnull')
+    
+    @property
+    def index_name(self):
+        return 'idxf_%s_l_%s' % (self.field_name, 'standard')
+    
+    def convert_lookup(self, value, lookup_type):
+        # don't change the lookup_type
+        return lookup_type, value
+    
+    def get_field_to_add(self, field_to_index):
+        field_to_add = deepcopy(field_to_index)
+        if isinstance(field_to_add, (models.DateTimeField,
+                                    models.DateField, models.TimeField)):
+            field_to_add.auto_now_add = field_to_add.auto_now = False
+        return field_to_add

dbindexer/tests.py

-from dbindexer.api import register_index
 from django.db import models, DatabaseError
 from django.test import TestCase
+from dbindexer.api import register_index
+from dbindexer.lookups import StandardLookup
 from datetime import datetime
 import re
 
 class ForeignIndexed2(models.Model):
     name_fi2 = models.CharField(max_length=500)
+    age = models.IntegerField()
     
 class ForeignIndexed(models.Model):
     title = models.CharField(max_length=500)
     'foreignkey__title': 'iexact',
     'foreignkey__name_fi': 'iexact',
     'foreignkey__fk__name_fi2': 'iexact',
-#    'foreignkey2__name_fi2': '$default'
+    'foreignkey2__name_fi2': (StandardLookup(), ),
+    'foreignkey2__age': (StandardLookup(), )
 })
 
 class TestIndexed(TestCase):
     def setUp(self):
-        juubi = ForeignIndexed2(name_fi2='Juubi')
+        juubi = ForeignIndexed2(name_fi2='Juubi', age=2)
         juubi.save()
         kyuubi = ForeignIndexed(name_fi='Kyuubi', title='Bijuu', fk=juubi)
         kyuubi.save()
             foreignkey__title__iexact='biJuu', name__iendswith='iMe')))
         self.assertEqual(3, len(Indexed.objects.all().filter(
             foreignkey__fk__name_fi2__iexact='juuBi')))
+        self.assertEqual(3, len(Indexed.objects.all().filter(
+            foreignkey2__name_fi2='Juubi')))
+        
+        # test multiple standard lookups
+        self.assertEqual(3, len(Indexed.objects.all().filter(
+            foreignkey2__age=2)))
+        self.assertEqual(3, len(Indexed.objects.all().filter(
+            foreignkey2__age__lt=3)))
 
     def test_fix_fk_isnull(self):
         self.assertEqual(0, len(Indexed.objects.filter(foreignkey=None)))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.