SEBA-248 Support for case-insensitive queries

Change-Id: Idb3f7bbd26e3b4190015f70f6b3cde486c39128f
diff --git a/VERSION b/VERSION
index 399088b..49c00f0 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1,2 @@
-2.1.6
+2.1.7
+
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index ae2102c..c394fc8 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -557,6 +557,8 @@
             q = Q(**{element.name + "__gt": value})
         elif element.operator == element.GREATER_THAN_OR_EQUAL:
             q = Q(**{element.name + "__gte": value})
+        elif element.operator == element.IEXACT:
+            q = Q(**{element.name + "__iexact": value})
         else:
             raise Exception("unknown operator")
 
diff --git a/xos/coreapi/protos/common.proto b/xos/coreapi/protos/common.proto
index f6b2940..5f82f7d 100644
--- a/xos/coreapi/protos/common.proto
+++ b/xos/coreapi/protos/common.proto
@@ -13,6 +13,7 @@
         LESS_THAN = 2;
         GREATER_THAN_OR_EQUAL = 3;
         LESS_THAN_OR_EQUAL = 4;
+        IEXACT = 5;
     }
     QueryOperator operator = 1;
     bool invert = 2;
diff --git a/xos/xos_client/xosapi/fake_stub.py b/xos/xos_client/xosapi/fake_stub.py
index 88a54b7..3cd8094 100644
--- a/xos/xos_client/xosapi/fake_stub.py
+++ b/xos/xos_client/xosapi/fake_stub.py
@@ -322,6 +322,7 @@
 
 class FakeElement(object):
     EQUAL="equal"
+    IEXACT="iexact"
 
     def __init__(self):
         pass
@@ -335,6 +336,12 @@
         self.items.append(el)
         return el
 
+    def __getitem__(self, index):
+        return self.items[index]
+
+    def __len__(self):
+        return len(self.items)
+
 class FakeQuery(object):
     DEFAULT="default"
 
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index 40af7dd..392dd84 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -469,6 +469,9 @@
             elif name.endswith("__lte"):
                 name = name[:-5]
                 el.operator = el.LESS_THAN_OR_EQUAL
+            elif name.endswith("__iexact"):
+                name = name[:-8]
+                el.operator = el.IEXACT
             else:
                 el.operator = el.EQUAL
 
diff --git a/xos/xos_client/xosapi/test_orm.py b/xos/xos_client/xosapi/test_orm.py
index 6006a52..ddeab89 100644
--- a/xos/xos_client/xosapi/test_orm.py
+++ b/xos/xos_client/xosapi/test_orm.py
@@ -21,6 +21,7 @@
 import string
 import sys
 import unittest
+from mock import patch
 
 # by default, use fake stub rather than real core
 USE_FAKE_STUB=True
@@ -454,6 +455,30 @@
             tm.intfile = None
         self.assertEqual(e.exception.message, "Setting a non-foreignkey field to None is not supported")
 
+    def test_query_iexact(self):
+        orm = self.make_coreapi()
+        with patch.object(orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+            orm.TestModel.objects.filter(name__iexact = "foo")
+            self.assertEqual(filter.call_count, 1)
+            q = filter.call_args[0][0]
+
+            self.assertEqual(q.kind, q.DEFAULT)
+            self.assertEqual(len(q.elements), 1)
+            self.assertEqual(q.elements[0].operator, q.elements[0].IEXACT)
+            self.assertEqual(q.elements[0].sValue, "foo")
+
+    def test_query_equal(self):
+        orm = self.make_coreapi()
+        with patch.object(orm.grpc_stub, "FilterTestModel", autospec=True) as filter:
+            orm.TestModel.objects.filter(name = "foo")
+            self.assertEqual(filter.call_count, 1)
+            q = filter.call_args[0][0]
+
+            self.assertEqual(q.kind, q.DEFAULT)
+            self.assertEqual(len(q.elements), 1)
+            self.assertEqual(q.elements[0].operator, q.elements[0].EQUAL)
+            self.assertEqual(q.elements[0].sValue, "foo")
+
 def main():
     global USE_FAKE_STUB
     global xos_grpc_client