From 74fe4428e51030cbed768083489f0497f4453c17 Mon Sep 17 00:00:00 2001
From: Marc Tamlyn <marc.tamlyn@gmail.com>
Date: Sat, 30 May 2015 21:22:36 +0100
Subject: [PATCH] Add HasAnyKeys lookup for HStoreField.

---
 django/contrib/postgres/fields/hstore.py | 15 +++------------
 django/contrib/postgres/lookups.py       | 15 +++++++++++++++
 docs/ref/contrib/postgres/fields.txt     | 17 +++++++++++++++++
 tests/postgres_tests/test_hstore.py      |  6 ++++++
 4 files changed, 41 insertions(+), 12 deletions(-)

diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py
index b8e47edf17..c87eef3bef 100644
--- a/django/contrib/postgres/fields/hstore.py
+++ b/django/contrib/postgres/fields/hstore.py
@@ -55,18 +55,9 @@ class HStoreField(Field):
 
 HStoreField.register_lookup(lookups.DataContains)
 HStoreField.register_lookup(lookups.ContainedBy)
-
-
-@HStoreField.register_lookup
-class HasKeyLookup(lookups.PostgresSimpleLookup):
-    lookup_name = 'has_key'
-    operator = '?'
-
-
-@HStoreField.register_lookup
-class HasKeysLookup(lookups.PostgresSimpleLookup):
-    lookup_name = 'has_keys'
-    operator = '?&'
+HStoreField.register_lookup(lookups.HasKey)
+HStoreField.register_lookup(lookups.HasKeys)
+HStoreField.register_lookup(lookups.HasAnyKeys)
 
 
 class KeyTransform(Transform):
diff --git a/django/contrib/postgres/lookups.py b/django/contrib/postgres/lookups.py
index eb7cfd8359..887337861a 100644
--- a/django/contrib/postgres/lookups.py
+++ b/django/contrib/postgres/lookups.py
@@ -30,6 +30,21 @@ class Overlap(PostgresSimpleLookup):
     operator = '&&'
 
 
+class HasKey(PostgresSimpleLookup):
+    lookup_name = 'has_key'
+    operator = '?'
+
+
+class HasKeys(PostgresSimpleLookup):
+    lookup_name = 'has_keys'
+    operator = '?&'
+
+
+class HasAnyKeys(PostgresSimpleLookup):
+    lookup_name = 'has_any_keys'
+    operator = '?|'
+
+
 class Unaccent(FunctionTransform):
     bilateral = True
     lookup_name = 'unaccent'
diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt
index 235dbc9079..5313ac4092 100644
--- a/docs/ref/contrib/postgres/fields.txt
+++ b/docs/ref/contrib/postgres/fields.txt
@@ -385,6 +385,23 @@ Returns objects where the given key is in the data. Uses the SQL operator
     >>> Dog.objects.filter(data__has_key='owner')
     [<Dog: Meg>]
 
+.. fieldlookup:: hstorefield.has_any_keys
+
+has_any_keys
+~~~~~~~~~~~~
+
+.. versionadded:: 1.9
+
+Returns objects where any of the given keys are in the data. Uses the SQL
+operator ``?|``. For example::
+
+    >>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
+    >>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
+    >>> Dog.objects.create(name='Fred', data={})
+
+    >>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
+    [<Dog: Rufus>, <Dog: Meg>]
+
 .. fieldlookup:: hstorefield.has_keys
 
 has_keys
diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py
index 470ede9d22..50a4702716 100644
--- a/tests/postgres_tests/test_hstore.py
+++ b/tests/postgres_tests/test_hstore.py
@@ -79,6 +79,12 @@ class TestQuerying(PostgresSQLTestCase):
             self.objs[1:2]
         )
 
+    def test_has_any_keys(self):
+        self.assertSequenceEqual(
+            HStoreModel.objects.filter(field__has_any_keys=['a', 'c']),
+            self.objs[:3]
+        )
+
     def test_key_transform(self):
         self.assertSequenceEqual(
             HStoreModel.objects.filter(field__a='b'),