Anonymous avatar Anonymous committed a42655b

[soc2010/query-refactor] Implemented not equal (exclude(foo=bar)) in the ORM for MongoDB, note that this doesn't actually work at the moment due to a bug in MongoDB.

Comments (0)

Files changed (3)

django/contrib/mongodb/compiler.py

         self.connection = connection
         self.using = using
     
-    def get_filters(self, where):
+    def get_filters(self, where, correct=False):
         assert where.connector == "AND"
-        assert not where.negated
         filters = {}
         for child in where.children:
             if isinstance(child, self.query.where_class):
-                # TODO: probably needs to check for dupe keys
-                filters.update(self.get_filters(child))
+                child_filters = self.get_filters(child)
+                for k, v in child_filters.iteritems():
+                    if k in filters:
+                        v = {"$and": [filters[k], v]}
+                    if where.negated:
+                        v = {"$not": v}
+                    filters[k] = v
             else:
-                field, val = self.make_atom(*child)
+                field, val = self.make_atom(*child, **{"negated": where.negated})
                 filters[field] = val
+        if correct:
+            self.correct_filters(filters)
         return filters
     
-    def make_atom(self, lhs, lookup_type, value_annotation, params_or_value):
-        assert lookup_type == "exact"
+    def make_atom(self, lhs, lookup_type, value_annotation, params_or_value, negated):
+        assert lookup_type in ["exact", "isnull"], lookup_type
         if hasattr(lhs, "process"):
             lhs, params = lhs.process(lookup_type, params_or_value, self.connection)
         else:
         assert table == self.query.model._meta.db_table
         if column == self.query.model._meta.pk.column:
             column = "_id"
-        return column, params[0]
+        
+        if lookup_type == "exact":
+            val = params[0]
+            if negated:
+                val = {"$ne": val}
+            return column, val
+        elif lookup_type == "isnull":
+            val = None
+            if value_annotation == negated:
+                val = {"$not": val}
+            return column, val
+    
+    def correct_filters(self, filters):
+        for k, v in filters.items():
+            if isinstance(v, dict) and v.keys() == ["$not"]:
+                if isinstance(v["$not"], dict) and v["$not"].keys() == ["$and"]:
+                    del filters[k]
+                    or_vals = [self.negate(k, v) for v in v["$not"]["$and"]]
+                    assert "$or" not in filters
+                    filters["$or"] = or_vals
+    
+    def negate(self, k, v):
+        if isinstance(v, dict):
+            if v.keys() == ["$not"]:
+                return {k: v["$not"]}
+            return {k: {"$not": v}}
+        return {k: {"$ne": v}}
     
     def build_query(self, aggregates=False):
         assert len([a for a in self.query.alias_map if self.query.alias_refcount[a]]) <= 1
         assert self.query.high_mark is None
         assert not self.query.order_by
         
-        filters = self.get_filters(self.query.where)
+        filters = self.get_filters(self.query.where, correct=True)
         return self.connection.db[self.query.model._meta.db_table].find(filters)
     
     def results_iter(self):

tests/regressiontests/mongodb/models.py

 class Group(models.Model):
     id = models.NativeAutoField(primary_key=True)
     name = models.CharField(max_length=255)
+    year_formed = models.IntegerField(null=True)
+

tests/regressiontests/mongodb/tests.py

         self.assertEqual(b.current_group_id, e.pk)
         self.assertFalse(hasattr(b, "_current_group_cache"))
         self.assertEqual(b.current_group, e)
+    
+    def test_lookup(self):
+        q = Group.objects.create(name="Queen", year_formed=1971)
+        e = Group.objects.create(name="The E Street Band", year_formed=1972)
+        
+        qs = Group.objects.exclude(year_formed=1972)
+        v = qs.query.get_compiler(qs.db).get_filters(qs.query.where, correct=True)
+        self.assertEqual(v, {
+            "$or": [
+                {"year_formed": {"$ne": 1972}},
+                {"year_formed": None},
+            ]
+        })
+        # A bug in MongoDB prevents this query from actually working, but test
+        # that we're at least generating the right query.
+        return
+        
+        self.assertQuerysetEqual(
+            qs, [
+                "Queen",
+            ],
+            lambda g: g.name,
+        )
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.