From 54827828b999a81c3a67bf37418fd6c68db06621 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Mon, 25 Nov 2013 23:37:10 -0800 Subject: [PATCH 01/15] First pass at a DictField query --- django_mongodb_engine/contrib/__init__.py | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index acd3223b..ee81af1b 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -1,11 +1,18 @@ import sys +import re from django.db import models, connections from django.db.models.query import QuerySet from django.db.models.sql.query import Query as SQLQuery +from django.db.models.query_utils import Q +from django.db.models.constants import LOOKUP_SEP +from django_mongodb_engine.compiler import OPERATORS_MAP, NEGATED_OPERATORS_MAP +from djangotoolbox.fields import AbstractIterableField ON_PYPY = hasattr(sys, 'pypy_version_info') +ALL_OPERATORS = dict(list(OPERATORS_MAP.items() + NEGATED_OPERATORS_MAP.items())).keys() +MONGO_DOT_FIELDS = ('DictField',) def _compiler_for_queryset(qs, which='SQLCompiler'): @@ -84,6 +91,40 @@ def __repr__(self): class MongoDBQuerySet(QuerySet): + def _filter_or_exclude(self, negate, *args, **kwargs): + if args or kwargs: + assert self.query.can_filter(), \ + "Cannot filter a query once a slice has been taken." + + clone = self._clone() + + all_field_names = self.model._meta.get_all_field_names() + base_field_names = [] + + for f in all_field_names: + field = self.model._meta.get_field_by_name(f)[0] + if '.' not in f and field.get_internal_type() in MONGO_DOT_FIELDS: + base_field_names.append(f) + + for k, v in kwargs.items(): + if LOOKUP_SEP in k and k.split(LOOKUP_SEP)[0] in base_field_names: + del kwargs[k] + for s in ALL_OPERATORS: + if k.endswith(s): + k = re.sub(LOOKUP_SEP + s + '$', '#' + s, k) + break + k = k.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) + kwargs[k] = v + f = k.split(LOOKUP_SEP)[0] + if '.' in f and f not in all_field_names: + field = AbstractIterableField(blank=True, null=True, editable=False) + field.contribute_to_class(self.model, f) + + if negate: + clone.query.add_q(~Q(*args, **kwargs)) + else: + clone.query.add_q(Q(*args, **kwargs)) + return clone def map_reduce(self, *args, **kwargs): """ From 54cb134f5d8f4dca09723058a9ac75c853d80976 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Mon, 25 Nov 2013 23:53:49 -0800 Subject: [PATCH 02/15] Fixed saving after a DictField query --- django_mongodb_engine/compiler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django_mongodb_engine/compiler.py b/django_mongodb_engine/compiler.py index a26c0af1..b4fe2f06 100644 --- a/django_mongodb_engine/compiler.py +++ b/django_mongodb_engine/compiler.py @@ -383,6 +383,9 @@ def insert(self, docs, return_id=False): doc.clear() else: raise DatabaseError("Can't save entity with _id set to None") + for d in doc.keys(): + if '.' in d: + del doc[d] collection = self.get_collection() options = self.connection.operation_flags.get('save', {}) From 13712de0b78dd8a9311d1483d135de708442737f Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Tue, 26 Nov 2013 19:52:50 -0800 Subject: [PATCH 03/15] Added additional field types to dot lookup --- django_mongodb_engine/contrib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index ee81af1b..24dabce4 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -12,7 +12,7 @@ ON_PYPY = hasattr(sys, 'pypy_version_info') ALL_OPERATORS = dict(list(OPERATORS_MAP.items() + NEGATED_OPERATORS_MAP.items())).keys() -MONGO_DOT_FIELDS = ('DictField',) +MONGO_DOT_FIELDS = ('DictField', 'ListField', 'SetField', 'EmbeddedModelField') def _compiler_for_queryset(qs, which='SQLCompiler'): From d1ec47446362be33ff168f736f31628f30187db1 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Tue, 26 Nov 2013 20:06:35 -0800 Subject: [PATCH 04/15] Updated authors --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 7a3bd943..aad296d0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -21,6 +21,7 @@ Contributions by * Sabin Iacob (https://github.com/m0n5t3r) * kryton (https://github.com/kryton) * Brandon Pedersen (https://github.com/bpedman) +* Brian Gontowski (https://github.com/Molanda) (For an up-to-date list of contributors, see https://github.com/django-mongodb-engine/mongodb-engine/contributors.) From 2f12db1a30aa8070cea10b5aa601f1ecb1d28b7f Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Wed, 27 Nov 2013 07:34:43 -0800 Subject: [PATCH 05/15] Added tests for dot queries --- django_mongodb_engine/contrib/__init__.py | 43 +++++++----- tests/dotquery/__init__.py | 0 tests/dotquery/models.py | 17 +++++ tests/dotquery/tests.py | 81 +++++++++++++++++++++++ tests/dotquery/utils.py | 35 ++++++++++ tests/settings/__init__.py | 1 + 6 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 tests/dotquery/__init__.py create mode 100644 tests/dotquery/models.py create mode 100644 tests/dotquery/tests.py create mode 100644 tests/dotquery/utils.py diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 24dabce4..67b0dd65 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -101,24 +101,33 @@ def _filter_or_exclude(self, negate, *args, **kwargs): all_field_names = self.model._meta.get_all_field_names() base_field_names = [] - for f in all_field_names: - field = self.model._meta.get_field_by_name(f)[0] - if '.' not in f and field.get_internal_type() in MONGO_DOT_FIELDS: - base_field_names.append(f) - - for k, v in kwargs.items(): - if LOOKUP_SEP in k and k.split(LOOKUP_SEP)[0] in base_field_names: - del kwargs[k] - for s in ALL_OPERATORS: - if k.endswith(s): - k = re.sub(LOOKUP_SEP + s + '$', '#' + s, k) + for name in all_field_names: + field = self.model._meta.get_field_by_name(name)[0] + if '.' not in name and field.get_internal_type() in MONGO_DOT_FIELDS: + base_field_names.append(name) + + for key, val in kwargs.items(): + if LOOKUP_SEP in key and key.split(LOOKUP_SEP)[0] in base_field_names: + del kwargs[key] + for op in ALL_OPERATORS: + if key.endswith(op): + key = re.sub(LOOKUP_SEP + op + '$', '#' + op, key) break - k = k.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) - kwargs[k] = v - f = k.split(LOOKUP_SEP)[0] - if '.' in f and f not in all_field_names: - field = AbstractIterableField(blank=True, null=True, editable=False) - field.contribute_to_class(self.model, f) + key = key.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) + kwargs[key] = val + name = key.split(LOOKUP_SEP)[0] + if '.' in name and name not in all_field_names: + parts = name.split('.') + column = self.model._meta.get_field_by_name(parts[0])[0].db_column + if column: + parts[0] = column + field = AbstractIterableField( + db_column = '.'.join(parts), + blank=True, + null=True, + editable=False, + ) + field.contribute_to_class(self.model, name) if negate: clone.query.add_q(~Q(*args, **kwargs)) diff --git a/tests/dotquery/__init__.py b/tests/dotquery/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/dotquery/models.py b/tests/dotquery/models.py new file mode 100644 index 00000000..980a2f31 --- /dev/null +++ b/tests/dotquery/models.py @@ -0,0 +1,17 @@ +from django.db import models +from djangotoolbox.fields import ListField, DictField, EmbeddedModelField +from django_mongodb_engine.contrib import MongoDBManager + + +class DotQueryEmbeddedModel(models.Model): + f_int = models.IntegerField() + + +class DotQueryTestModel(models.Model): + objects = MongoDBManager() + + f_id = models.IntegerField() + f_dict = DictField(db_column='test_dict') + f_list = ListField() + f_embedded = EmbeddedModelField(DotQueryEmbeddedModel) + f_embedded_list = ListField(EmbeddedModelField(DotQueryEmbeddedModel)) diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py new file mode 100644 index 00000000..5f4d72de --- /dev/null +++ b/tests/dotquery/tests.py @@ -0,0 +1,81 @@ +from __future__ import with_statement +from models import * +from utils import * + + +class DotQueryTests(TestCase): + """Tests for querying on foo.bar using join syntax.""" + + def setUp(self): + DotQueryTestModel.objects.create( + f_id=51, + f_dict={'numbers': [1, 2, 3], 'letters': 'abc'}, + f_list=[{'color': 'red'}, {'color': 'blue'}], + f_embedded=DotQueryEmbeddedModel(f_int=10), + f_embedded_list=[ + DotQueryEmbeddedModel(f_int=100), + DotQueryEmbeddedModel(f_int=101), + ], + ) + DotQueryTestModel.objects.create( + f_id=52, + f_dict={'numbers': [2, 3], 'letters': 'bc'}, + f_list=[{'color': 'red'}, {'color': 'green'}], + f_embedded=DotQueryEmbeddedModel(f_int=11), + f_embedded_list=[ + DotQueryEmbeddedModel(f_int=110), + DotQueryEmbeddedModel(f_int=111), + ], + ) + DotQueryTestModel.objects.create( + f_id=53, + f_dict={'numbers': [3, 4], 'letters': 'cd'}, + f_list=[{'color': 'yellow'}, {'color': 'orange'}], + f_embedded=DotQueryEmbeddedModel(f_int=12), + f_embedded_list=[ + DotQueryEmbeddedModel(f_int=120), + DotQueryEmbeddedModel(f_int=121), + ], + ) + + def tearDown(self): + DotQueryTestModel.objects.all().delete() + + def test_dict_queries(self): + q = DotQueryTestModel.objects.filter(f_dict__numbers=2) + self.assertEqual(q.count(), 2) + q = DotQueryTestModel.objects.filter(f_dict__letters__contains='b') + self.assertEqual(q.count(), 2) + q = DotQueryTestModel.objects.exclude(f_dict__letters__contains='b') + self.assertEqual(q.count(), 1) + self.assertEqual(q[0].f_id, 53) + + def test_list_queries(self): + q = DotQueryTestModel.objects.filter(f_list__color='red') + q = q.exclude(f_list__color='green') + q = q.exclude(f_list__color='purple') + self.assertEqual(q.count(), 1) + self.assertEqual(q[0].f_id, 51) + + def test_embedded_queries(self): + q = DotQueryTestModel.objects.exclude(f_embedded__f_int__in=[10, 12]) + self.assertEqual(q.count(), 1) + self.assertEqual(q[0].f_id, 52) + + def test_embedded_list_queries(self): + q = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) + self.assertEqual(q.f_id, 53) + + def test_save_after_query(self): + q = DotQueryTestModel.objects.get(f_dict__letters='cd') + self.assertEqual(q.f_id, 53) + q.f_id = 1053 + q.clean() + q.save() + q = DotQueryTestModel.objects.get(f_dict__letters='cd') + self.assertEqual(q.f_id, 1053) + q.f_id = 53 + q.clean() + q.save() + q = DotQueryTestModel.objects.get(f_dict__letters='cd') + self.assertEqual(q.f_id, 53) diff --git a/tests/dotquery/utils.py b/tests/dotquery/utils.py new file mode 100644 index 00000000..fada71c8 --- /dev/null +++ b/tests/dotquery/utils.py @@ -0,0 +1,35 @@ +from django.conf import settings +from django.db import connections +from django.db.models import Model +from django.test import TestCase +from django.utils.unittest import skip + + +class TestCase(TestCase): + + def setUp(self): + super(TestCase, self).setUp() + if getattr(settings, 'TEST_DEBUG', False): + settings.DEBUG = True + + def assertEqualLists(self, a, b): + self.assertEqual(list(a), list(b)) + + +def skip_all_except(*tests): + + class meta(type): + + def __new__(cls, name, bases, dict): + for attr in dict.keys(): + if attr.startswith('test_') and attr not in tests: + del dict[attr] + return type.__new__(cls, name, bases, dict) + + return meta + + +def get_collection(model_or_name): + if isinstance(model_or_name, type) and issubclass(model_or_name, Model): + model_or_name = model_or_name._meta.db_table + return connections['default'].get_collection(model_or_name) diff --git a/tests/settings/__init__.py b/tests/settings/__init__.py index eb9f2579..b3e4c323 100644 --- a/tests/settings/__init__.py +++ b/tests/settings/__init__.py @@ -17,4 +17,5 @@ 'aggregations', 'contrib', 'storage', + 'dotquery', ] From 88978320798d8bdce2eae942af0c117a6bb41963 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Mon, 2 Dec 2013 06:29:46 -0800 Subject: [PATCH 06/15] Added comments about Q and EmbeddedModelField --- django_mongodb_engine/contrib/__init__.py | 1 + tests/dotquery/tests.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 67b0dd65..a60cefc1 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -118,6 +118,7 @@ def _filter_or_exclude(self, negate, *args, **kwargs): name = key.split(LOOKUP_SEP)[0] if '.' in name and name not in all_field_names: parts = name.split('.') + # FIXME: Need to recursively follow EmbeddedModelFields for any db_columns column = self.model._meta.get_field_by_name(parts[0])[0].db_column if column: parts[0] = column diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 5f4d72de..34ff5cac 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -1,8 +1,8 @@ from __future__ import with_statement +from django.db.models import Q from models import * from utils import * - class DotQueryTests(TestCase): """Tests for querying on foo.bar using join syntax.""" @@ -44,8 +44,12 @@ def tearDown(self): def test_dict_queries(self): q = DotQueryTestModel.objects.filter(f_dict__numbers=2) self.assertEqual(q.count(), 2) + self.assertEqual(q[0].f_id, 51) + self.assertEqual(q[1].f_id, 52) q = DotQueryTestModel.objects.filter(f_dict__letters__contains='b') self.assertEqual(q.count(), 2) + self.assertEqual(q[0].f_id, 51) + self.assertEqual(q[1].f_id, 52) q = DotQueryTestModel.objects.exclude(f_dict__letters__contains='b') self.assertEqual(q.count(), 1) self.assertEqual(q[0].f_id, 53) @@ -66,6 +70,13 @@ def test_embedded_list_queries(self): q = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) self.assertEqual(q.f_id, 53) +# FIXME: Need to implement Q on dot fields +# def test_q_queries(self): +# q = DotQueryTestModel.objects.filter(Q(f_dict__numbers=1)|Q(f_dict__numbers=4)) +# self.assertEqual(q.count(), 2) +# self.assertEqual(q[0].f_id, 51) +# self.assertEqual(q[1].f_id, 53) + def test_save_after_query(self): q = DotQueryTestModel.objects.get(f_dict__letters='cd') self.assertEqual(q.f_id, 53) From 5db46cb683a6293c8313893ddb4d0f65527350b4 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Tue, 3 Dec 2013 20:26:42 -0800 Subject: [PATCH 07/15] Added join style query support for Q() filters --- django_mongodb_engine/contrib/__init__.py | 94 +++++++++++++++-------- tests/dotquery/tests.py | 79 +++++++++---------- 2 files changed, 103 insertions(+), 70 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index a60cefc1..0615f22b 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -91,6 +91,7 @@ def __repr__(self): class MongoDBQuerySet(QuerySet): + def _filter_or_exclude(self, negate, *args, **kwargs): if args or kwargs: assert self.query.can_filter(), \ @@ -98,37 +99,7 @@ def _filter_or_exclude(self, negate, *args, **kwargs): clone = self._clone() - all_field_names = self.model._meta.get_all_field_names() - base_field_names = [] - - for name in all_field_names: - field = self.model._meta.get_field_by_name(name)[0] - if '.' not in name and field.get_internal_type() in MONGO_DOT_FIELDS: - base_field_names.append(name) - - for key, val in kwargs.items(): - if LOOKUP_SEP in key and key.split(LOOKUP_SEP)[0] in base_field_names: - del kwargs[key] - for op in ALL_OPERATORS: - if key.endswith(op): - key = re.sub(LOOKUP_SEP + op + '$', '#' + op, key) - break - key = key.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) - kwargs[key] = val - name = key.split(LOOKUP_SEP)[0] - if '.' in name and name not in all_field_names: - parts = name.split('.') - # FIXME: Need to recursively follow EmbeddedModelFields for any db_columns - column = self.model._meta.get_field_by_name(parts[0])[0].db_column - if column: - parts[0] = column - field = AbstractIterableField( - db_column = '.'.join(parts), - blank=True, - null=True, - editable=False, - ) - field.contribute_to_class(self.model, name) + self._process_arg_filters(args, kwargs) if negate: clone.query.add_q(~Q(*args, **kwargs)) @@ -136,6 +107,67 @@ def _filter_or_exclude(self, negate, *args, **kwargs): clone.query.add_q(Q(*args, **kwargs)) return clone + def _get_mongo_field_names(self): + if not hasattr(self, '_mongo_field_names'): + self._mongo_field_names = [] + for name in self.model._meta.get_all_field_names(): + field = self.model._meta.get_field_by_name(name)[0] + if '.' not in name and field.get_internal_type() in MONGO_DOT_FIELDS: + self._mongo_field_names.append(name) + + return self._mongo_field_names + + def _process_arg_filters(self, args, kwargs): + for key, val in kwargs.items(): + del kwargs[key] + key = self._dotify_field_name(key) + kwargs[key] = val + self._maybe_add_dot_field(key) + + for a in args: + if isinstance(a, Q): + self._process_q_filters(a) + + def _process_q_filters(self, q): + for c in range(len(q.children)): + child = q.children[c] + if isinstance(child, Q): + self._process_q_filters(child) + elif isinstance(child, tuple): + key, val = child + key = self._dotify_field_name(key) + q.children[c] = (key, val) + self._maybe_add_dot_field(key) + + def _dotify_field_name(self, name): + if LOOKUP_SEP in name and name.split(LOOKUP_SEP)[0] in self._get_mongo_field_names(): + for op in ALL_OPERATORS: + if name.endswith(op): + name = re.sub(LOOKUP_SEP + op + '$', '#' + op, name) + break + name = name.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) + + return name + + def _maybe_add_dot_field(self, name): + name = name.split(LOOKUP_SEP)[0] + + if '.' in name and name not in self.model._meta.get_all_field_names(): + parts = name.split('.') + + # FIXME: Need to recursively follow EmbeddedModelFields for any db_columns + column = self.model._meta.get_field_by_name(parts[0])[0].db_column + if column: + parts[0] = column + + field = AbstractIterableField( + db_column = '.'.join(parts), + blank=True, + null=True, + editable=False, + ) + field.contribute_to_class(self.model, name) + def map_reduce(self, *args, **kwargs): """ Performs a Map/Reduce operation on all documents matching the query, diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 34ff5cac..fe1a8889 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -42,51 +42,52 @@ def tearDown(self): DotQueryTestModel.objects.all().delete() def test_dict_queries(self): - q = DotQueryTestModel.objects.filter(f_dict__numbers=2) - self.assertEqual(q.count(), 2) - self.assertEqual(q[0].f_id, 51) - self.assertEqual(q[1].f_id, 52) - q = DotQueryTestModel.objects.filter(f_dict__letters__contains='b') - self.assertEqual(q.count(), 2) - self.assertEqual(q[0].f_id, 51) - self.assertEqual(q[1].f_id, 52) - q = DotQueryTestModel.objects.exclude(f_dict__letters__contains='b') - self.assertEqual(q.count(), 1) - self.assertEqual(q[0].f_id, 53) + qs = DotQueryTestModel.objects.filter(f_dict__numbers=2) + self.assertEqual(qs.count(), 2) + self.assertEqual(qs[0].f_id, 51) + self.assertEqual(qs[1].f_id, 52) + qs = DotQueryTestModel.objects.filter(f_dict__letters__contains='b') + self.assertEqual(qs.count(), 2) + self.assertEqual(qs[0].f_id, 51) + self.assertEqual(qs[1].f_id, 52) + qs = DotQueryTestModel.objects.exclude(f_dict__letters__contains='b') + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].f_id, 53) def test_list_queries(self): - q = DotQueryTestModel.objects.filter(f_list__color='red') - q = q.exclude(f_list__color='green') - q = q.exclude(f_list__color='purple') - self.assertEqual(q.count(), 1) - self.assertEqual(q[0].f_id, 51) + qs = DotQueryTestModel.objects.filter(f_list__color='red') + qs = qs.exclude(f_list__color='green') + qs = qs.exclude(f_list__color='purple') + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].f_id, 51) def test_embedded_queries(self): - q = DotQueryTestModel.objects.exclude(f_embedded__f_int__in=[10, 12]) - self.assertEqual(q.count(), 1) - self.assertEqual(q[0].f_id, 52) + qs = DotQueryTestModel.objects.exclude(f_embedded__f_int__in=[10, 12]) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].f_id, 52) def test_embedded_list_queries(self): - q = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) - self.assertEqual(q.f_id, 53) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) + self.assertEqual(qs.f_id, 53) -# FIXME: Need to implement Q on dot fields -# def test_q_queries(self): -# q = DotQueryTestModel.objects.filter(Q(f_dict__numbers=1)|Q(f_dict__numbers=4)) -# self.assertEqual(q.count(), 2) -# self.assertEqual(q[0].f_id, 51) -# self.assertEqual(q[1].f_id, 53) + def test_q_queries(self): + q = Q(f_dict__numbers=1) | Q(f_dict__numbers=4) + q = q & Q(f_dict__numbers=3) + qs = DotQueryTestModel.objects.filter(q) + self.assertEqual(qs.count(), 2) + self.assertEqual(qs[0].f_id, 51) + self.assertEqual(qs[1].f_id, 53) def test_save_after_query(self): - q = DotQueryTestModel.objects.get(f_dict__letters='cd') - self.assertEqual(q.f_id, 53) - q.f_id = 1053 - q.clean() - q.save() - q = DotQueryTestModel.objects.get(f_dict__letters='cd') - self.assertEqual(q.f_id, 1053) - q.f_id = 53 - q.clean() - q.save() - q = DotQueryTestModel.objects.get(f_dict__letters='cd') - self.assertEqual(q.f_id, 53) + qs = DotQueryTestModel.objects.get(f_dict__letters='cd') + self.assertEqual(qs.f_id, 53) + qs.f_id = 1053 + qs.clean() + qs.save() + qs = DotQueryTestModel.objects.get(f_dict__letters='cd') + self.assertEqual(qs.f_id, 1053) + qs.f_id = 53 + qs.clean() + qs.save() + qs = DotQueryTestModel.objects.get(f_dict__letters='cd') + self.assertEqual(qs.f_id, 53) From 873dbb6425a3d63bfc5a341fa73adb4103b4ad1d Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Fri, 6 Dec 2013 18:25:21 -0800 Subject: [PATCH 08/15] Following through the models to get the correct db_columns --- django_mongodb_engine/contrib/__init__.py | 41 +++++++++++++++++++---- tests/dotquery/models.py | 29 +++++++++++++--- tests/dotquery/tests.py | 23 +++++++++++-- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 0615f22b..5431d3dc 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -153,15 +153,42 @@ def _maybe_add_dot_field(self, name): name = name.split(LOOKUP_SEP)[0] if '.' in name and name not in self.model._meta.get_all_field_names(): - parts = name.split('.') - - # FIXME: Need to recursively follow EmbeddedModelFields for any db_columns - column = self.model._meta.get_field_by_name(parts[0])[0].db_column - if column: - parts[0] = column + parts1 = name.split('.') + parts2 = [] + parts3 = [] + model = self.model + + while len(parts1) > 0: + part = parts1.pop(0) + field = model._meta.get_field_by_name(part)[0] + field_type = field.get_internal_type() + if field_type not in MONGO_DOT_FIELDS: + # FIXME: In this case, we are handling embedded fields + # and should probably use the actual field class + # instead of AbstractIterableField for the lookup. + pass + column = field.db_column + if column: + part = column + parts2.append(part) + if field_type == 'ListField': + list_type = field.item_field.get_internal_type() + if list_type == 'EmbeddedModelField': + field = field.item_field + field_type = list_type + if field_type == 'EmbeddedModelField': + model = field.embedded_model() + else: + while len(parts1) > 0: + part = parts1.pop(0) + if field_type in MONGO_DOT_FIELDS: + parts2.append(part) + else: + parts3.append(part) + db_column = LOOKUP_SEP.join(['.'.join(parts2)] + parts3) field = AbstractIterableField( - db_column = '.'.join(parts), + db_column = db_column, blank=True, null=True, editable=False, diff --git a/tests/dotquery/models.py b/tests/dotquery/models.py index 980a2f31..e5ba7c6c 100644 --- a/tests/dotquery/models.py +++ b/tests/dotquery/models.py @@ -3,15 +3,34 @@ from django_mongodb_engine.contrib import MongoDBManager +class DotQueryForeignModel(models.Model): + f_char = models.CharField(max_length=200, db_column='dbc_char') + + class DotQueryEmbeddedModel(models.Model): - f_int = models.IntegerField() + f_int = models.IntegerField(db_column='dbc_int') + f_foreign = models.ForeignKey( + DotQueryForeignModel, + null=True, + blank=True, + db_column='dbc_foreign' + ) class DotQueryTestModel(models.Model): objects = MongoDBManager() f_id = models.IntegerField() - f_dict = DictField(db_column='test_dict') - f_list = ListField() - f_embedded = EmbeddedModelField(DotQueryEmbeddedModel) - f_embedded_list = ListField(EmbeddedModelField(DotQueryEmbeddedModel)) + f_dict = DictField(db_column='dbc_dict') + f_list = ListField(db_column='dbc_list') + f_embedded = EmbeddedModelField( + DotQueryEmbeddedModel, + db_column='dbc_embedded', + ) + f_embedded_list = ListField( + EmbeddedModelField( + DotQueryEmbeddedModel, + db_column='dbc_embedded', + ), + db_column='dbc_embedded_list', + ) diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index fe1a8889..1bbe86b5 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -7,11 +7,14 @@ class DotQueryTests(TestCase): """Tests for querying on foo.bar using join syntax.""" def setUp(self): + fm = DotQueryForeignModel.objects.create( + f_char = 'hello', + ) DotQueryTestModel.objects.create( f_id=51, f_dict={'numbers': [1, 2, 3], 'letters': 'abc'}, f_list=[{'color': 'red'}, {'color': 'blue'}], - f_embedded=DotQueryEmbeddedModel(f_int=10), + f_embedded=DotQueryEmbeddedModel(f_int=10, f_foreign=fm), f_embedded_list=[ DotQueryEmbeddedModel(f_int=100), DotQueryEmbeddedModel(f_int=101), @@ -23,8 +26,8 @@ def setUp(self): f_list=[{'color': 'red'}, {'color': 'green'}], f_embedded=DotQueryEmbeddedModel(f_int=11), f_embedded_list=[ - DotQueryEmbeddedModel(f_int=110), - DotQueryEmbeddedModel(f_int=111), + DotQueryEmbeddedModel(f_int=110, f_foreign=fm), + DotQueryEmbeddedModel(f_int=111, f_foreign=fm), ], ) DotQueryTestModel.objects.create( @@ -40,6 +43,7 @@ def setUp(self): def tearDown(self): DotQueryTestModel.objects.all().delete() + DotQueryForeignModel.objects.all().delete() def test_dict_queries(self): qs = DotQueryTestModel.objects.filter(f_dict__numbers=2) @@ -70,6 +74,19 @@ def test_embedded_list_queries(self): qs = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) self.assertEqual(qs.f_id, 53) + def skip_foreign_queries(self): + # FIXME: I suspect this does not work because the fields are + # searched using AbstractIterableField instead of ForeignKey. + fm = DotQueryForeignModel.objects.get(f_char='hello') + qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) + self.assertEqual(qs.f_id, 51) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) + self.assertEqual(qs.f_id, 53) + qs = DotQueryTestModel.objects.get(f_embedded__f_foreign__f_char='hello') + self.assertEqual(qs.f_id, 51) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign__f_char='hello') + self.assertEqual(qs.f_id, 53) + def test_q_queries(self): q = Q(f_dict__numbers=1) | Q(f_dict__numbers=4) q = q & Q(f_dict__numbers=3) From cb9538b3076582680bc566b4c6b5fe05ec1f93c3 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Fri, 6 Dec 2013 23:32:06 -0800 Subject: [PATCH 09/15] Fixed querying on a foreign key within an embedded model --- django_mongodb_engine/contrib/__init__.py | 29 +++++++++++++---------- tests/dotquery/tests.py | 13 ++++------ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 5431d3dc..7e82e296 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -95,7 +95,7 @@ class MongoDBQuerySet(QuerySet): def _filter_or_exclude(self, negate, *args, **kwargs): if args or kwargs: assert self.query.can_filter(), \ - "Cannot filter a query once a slice has been taken." + 'Cannot filter a query once a slice has been taken.' clone = self._clone() @@ -126,7 +126,7 @@ def _process_arg_filters(self, args, kwargs): for a in args: if isinstance(a, Q): - self._process_q_filters(a) + self._process_q_filters(a) def _process_q_filters(self, q): for c in range(len(q.children)): @@ -162,11 +162,6 @@ def _maybe_add_dot_field(self, name): part = parts1.pop(0) field = model._meta.get_field_by_name(part)[0] field_type = field.get_internal_type() - if field_type not in MONGO_DOT_FIELDS: - # FIXME: In this case, we are handling embedded fields - # and should probably use the actual field class - # instead of AbstractIterableField for the lookup. - pass column = field.db_column if column: part = column @@ -187,12 +182,20 @@ def _maybe_add_dot_field(self, name): parts3.append(part) db_column = LOOKUP_SEP.join(['.'.join(parts2)] + parts3) - field = AbstractIterableField( - db_column = db_column, - blank=True, - null=True, - editable=False, - ) + if field_type in MONGO_DOT_FIELDS: + field = AbstractIterableField( + db_column=db_column, + blank=True, + null=True, + editable=False, + ) + else: + field = field.__deepcopy__(field.__dict__) + field.name = None + field.db_column = db_column + field.blank = True + field.null = True + field.editable = False field.contribute_to_class(self.model, name) def map_reduce(self, *args, **kwargs): diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 1bbe86b5..19e505aa 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -3,12 +3,13 @@ from models import * from utils import * + class DotQueryTests(TestCase): """Tests for querying on foo.bar using join syntax.""" def setUp(self): fm = DotQueryForeignModel.objects.create( - f_char = 'hello', + f_char='hello', ) DotQueryTestModel.objects.create( f_id=51, @@ -74,18 +75,12 @@ def test_embedded_list_queries(self): qs = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) self.assertEqual(qs.f_id, 53) - def skip_foreign_queries(self): - # FIXME: I suspect this does not work because the fields are - # searched using AbstractIterableField instead of ForeignKey. + def test_foreign_queries(self): fm = DotQueryForeignModel.objects.get(f_char='hello') qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) self.assertEqual(qs.f_id, 51) qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) - self.assertEqual(qs.f_id, 53) - qs = DotQueryTestModel.objects.get(f_embedded__f_foreign__f_char='hello') - self.assertEqual(qs.f_id, 51) - qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign__f_char='hello') - self.assertEqual(qs.f_id, 53) + self.assertEqual(qs.f_id, 52) def test_q_queries(self): q = Q(f_dict__numbers=1) | Q(f_dict__numbers=4) From 6a73282c4f81ca52176bc05f2a4995598acd3639 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Fri, 6 Dec 2013 23:47:44 -0800 Subject: [PATCH 10/15] Skipping foreign field test for now until I can test on 1.6 --- tests/dotquery/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 19e505aa..4c27bfea 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -75,7 +75,7 @@ def test_embedded_list_queries(self): qs = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) self.assertEqual(qs.f_id, 53) - def test_foreign_queries(self): + def skip_foreign_queries(self): fm = DotQueryForeignModel.objects.get(f_char='hello') qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) self.assertEqual(qs.f_id, 51) From bd36570a803d2dd7ca12d250e65f4f0f25a35d6d Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Sat, 7 Dec 2013 15:52:59 -0800 Subject: [PATCH 11/15] Better foreign field handling and added back test for django < 1.6 --- django_mongodb_engine/contrib/__init__.py | 43 ++++++++++++----------- tests/dotquery/tests.py | 17 ++++++--- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 7e82e296..7c4c8d76 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -120,9 +120,8 @@ def _get_mongo_field_names(self): def _process_arg_filters(self, args, kwargs): for key, val in kwargs.items(): del kwargs[key] - key = self._dotify_field_name(key) + key = self._maybe_add_dot_field(key) kwargs[key] = val - self._maybe_add_dot_field(key) for a in args: if isinstance(a, Q): @@ -135,11 +134,10 @@ def _process_q_filters(self, q): self._process_q_filters(child) elif isinstance(child, tuple): key, val = child - key = self._dotify_field_name(key) + key = self._maybe_add_dot_field(key) q.children[c] = (key, val) - self._maybe_add_dot_field(key) - def _dotify_field_name(self, name): + def _maybe_add_dot_field(self, name): if LOOKUP_SEP in name and name.split(LOOKUP_SEP)[0] in self._get_mongo_field_names(): for op in ALL_OPERATORS: if name.endswith(op): @@ -147,25 +145,21 @@ def _dotify_field_name(self, name): break name = name.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) - return name - - def _maybe_add_dot_field(self, name): - name = name.split(LOOKUP_SEP)[0] - - if '.' in name and name not in self.model._meta.get_all_field_names(): - parts1 = name.split('.') - parts2 = [] + parts1 = name.split(LOOKUP_SEP) + if '.' in parts1[0] and parts1[0] not in self.model._meta.get_all_field_names(): + parts2 = parts1[0].split('.') parts3 = [] + parts4 = [] model = self.model - while len(parts1) > 0: - part = parts1.pop(0) + while len(parts2) > 0: + part = parts2.pop(0) field = model._meta.get_field_by_name(part)[0] field_type = field.get_internal_type() column = field.db_column if column: part = column - parts2.append(part) + parts3.append(part) if field_type == 'ListField': list_type = field.item_field.get_internal_type() if list_type == 'EmbeddedModelField': @@ -174,13 +168,14 @@ def _maybe_add_dot_field(self, name): if field_type == 'EmbeddedModelField': model = field.embedded_model() else: - while len(parts1) > 0: - part = parts1.pop(0) + while len(parts2) > 0: + part = parts2.pop(0) if field_type in MONGO_DOT_FIELDS: - parts2.append(part) - else: parts3.append(part) - db_column = LOOKUP_SEP.join(['.'.join(parts2)] + parts3) + else: + parts4.append(part) + + db_column = '.'.join(parts3) if field_type in MONGO_DOT_FIELDS: field = AbstractIterableField( @@ -196,7 +191,13 @@ def _maybe_add_dot_field(self, name): field.blank = True field.null = True field.editable = False + + parts5 = parts1[0].split('.')[0:len(parts3)] + name = '.'.join(parts5) field.contribute_to_class(self.model, name) + name = LOOKUP_SEP.join([name] + parts4 + parts1[1:]) + + return name def map_reduce(self, *args, **kwargs): """ diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 4c27bfea..d9be822a 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -1,5 +1,6 @@ from __future__ import with_statement from django.db.models import Q +from django import VERSION from models import * from utils import * @@ -75,12 +76,18 @@ def test_embedded_list_queries(self): qs = DotQueryTestModel.objects.get(f_embedded_list__f_int=120) self.assertEqual(qs.f_id, 53) - def skip_foreign_queries(self): + def test_foreign_queries(self): fm = DotQueryForeignModel.objects.get(f_char='hello') - qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) - self.assertEqual(qs.f_id, 51) - qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) - self.assertEqual(qs.f_id, 52) + # FIXME: Figure out why 1.6 does not find any results + if VERSION[0] == 1 and VERSION[1] < 6: + qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) + self.assertEqual(qs.f_id, 51) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) + self.assertEqual(qs.f_id, 52) + qs = DotQueryTestModel.objects.get(f_embedded__f_foreign__pk=fm.pk) + self.assertEqual(qs.f_id, 51) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign__pk__exact=fm.pk) + self.assertEqual(qs.f_id, 52) def test_q_queries(self): q = Q(f_dict__numbers=1) | Q(f_dict__numbers=4) From 0f1707b7923e91ebaf99d05b12bb4401ab4b86e6 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Fri, 13 Dec 2013 10:44:24 -0800 Subject: [PATCH 12/15] Recommended changes --- django_mongodb_engine/contrib/__init__.py | 9 +++++++-- tests/dotquery/tests.py | 5 +++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 7c4c8d76..7e3b2bfe 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -1,14 +1,19 @@ import sys import re +import django from django.db import models, connections from django.db.models.query import QuerySet from django.db.models.sql.query import Query as SQLQuery from django.db.models.query_utils import Q -from django.db.models.constants import LOOKUP_SEP from django_mongodb_engine.compiler import OPERATORS_MAP, NEGATED_OPERATORS_MAP from djangotoolbox.fields import AbstractIterableField +if django.VERSION >= (1, 5): + from django.db.models.constants import LOOKUP_SEP +else: + from django.db.models.sql.constants import LOOKUP_SEP + ON_PYPY = hasattr(sys, 'pypy_version_info') ALL_OPERATORS = dict(list(OPERATORS_MAP.items() + NEGATED_OPERATORS_MAP.items())).keys() @@ -99,7 +104,7 @@ def _filter_or_exclude(self, negate, *args, **kwargs): clone = self._clone() - self._process_arg_filters(args, kwargs) + clone._process_arg_filters(args, kwargs) if negate: clone.query.add_q(~Q(*args, **kwargs)) diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index d9be822a..34275966 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -1,9 +1,10 @@ from __future__ import with_statement from django.db.models import Q -from django import VERSION from models import * from utils import * +import django + class DotQueryTests(TestCase): """Tests for querying on foo.bar using join syntax.""" @@ -79,7 +80,7 @@ def test_embedded_list_queries(self): def test_foreign_queries(self): fm = DotQueryForeignModel.objects.get(f_char='hello') # FIXME: Figure out why 1.6 does not find any results - if VERSION[0] == 1 and VERSION[1] < 6: + if django.VERSION < (1, 6): qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) self.assertEqual(qs.f_id, 51) qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) From bb8586b3279a7a9dd15b43eabf46c4137b142c3b Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Fri, 13 Dec 2013 14:27:40 -0800 Subject: [PATCH 13/15] Fixed ForeignKey issue with 1.6 --- django_mongodb_engine/contrib/__init__.py | 2 ++ tests/dotquery/models.py | 4 ++++ tests/dotquery/tests.py | 20 ++++++++------------ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 7e3b2bfe..626f4df1 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -196,6 +196,8 @@ def _maybe_add_dot_field(self, name): field.blank = True field.null = True field.editable = False + if hasattr(field, '_related_fields'): + delattr(field, '_related_fields') parts5 = parts1[0].split('.')[0:len(parts3)] name = '.'.join(parts5) diff --git a/tests/dotquery/models.py b/tests/dotquery/models.py index e5ba7c6c..f4e06640 100644 --- a/tests/dotquery/models.py +++ b/tests/dotquery/models.py @@ -4,10 +4,14 @@ class DotQueryForeignModel(models.Model): + objects = MongoDBManager() + f_char = models.CharField(max_length=200, db_column='dbc_char') class DotQueryEmbeddedModel(models.Model): + objects = MongoDBManager() + f_int = models.IntegerField(db_column='dbc_int') f_foreign = models.ForeignKey( DotQueryForeignModel, diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 34275966..0a768292 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -3,8 +3,6 @@ from models import * from utils import * -import django - class DotQueryTests(TestCase): """Tests for querying on foo.bar using join syntax.""" @@ -79,16 +77,14 @@ def test_embedded_list_queries(self): def test_foreign_queries(self): fm = DotQueryForeignModel.objects.get(f_char='hello') - # FIXME: Figure out why 1.6 does not find any results - if django.VERSION < (1, 6): - qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) - self.assertEqual(qs.f_id, 51) - qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) - self.assertEqual(qs.f_id, 52) - qs = DotQueryTestModel.objects.get(f_embedded__f_foreign__pk=fm.pk) - self.assertEqual(qs.f_id, 51) - qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign__pk__exact=fm.pk) - self.assertEqual(qs.f_id, 52) + qs = DotQueryTestModel.objects.get(f_embedded__f_foreign=fm) + self.assertEqual(qs.f_id, 51) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign=fm) + self.assertEqual(qs.f_id, 52) + qs = DotQueryTestModel.objects.get(f_embedded__f_foreign__pk=fm.pk) + self.assertEqual(qs.f_id, 51) + qs = DotQueryTestModel.objects.get(f_embedded_list__f_foreign__pk__exact=fm.pk) + self.assertEqual(qs.f_id, 52) def test_q_queries(self): q = Q(f_dict__numbers=1) | Q(f_dict__numbers=4) From a8e1ec66c302eaea5d008e6ed9d96ce0dc5c226e Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Sun, 15 Dec 2013 20:13:45 -0800 Subject: [PATCH 14/15] Matching Django's method to add internal fields --- django_mongodb_engine/contrib/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 626f4df1..81a76e35 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -1,5 +1,6 @@ import sys import re +import copy import django from django.db import models, connections @@ -103,9 +104,7 @@ def _filter_or_exclude(self, negate, *args, **kwargs): 'Cannot filter a query once a slice has been taken.' clone = self._clone() - clone._process_arg_filters(args, kwargs) - if negate: clone.query.add_q(~Q(*args, **kwargs)) else: @@ -190,7 +189,7 @@ def _maybe_add_dot_field(self, name): editable=False, ) else: - field = field.__deepcopy__(field.__dict__) + field = copy.deepcopy(field) field.name = None field.db_column = db_column field.blank = True @@ -201,7 +200,7 @@ def _maybe_add_dot_field(self, name): parts5 = parts1[0].split('.')[0:len(parts3)] name = '.'.join(parts5) - field.contribute_to_class(self.model, name) + self.model.add_to_class(name, field) name = LOOKUP_SEP.join([name] + parts4 + parts1[1:]) return name From 9c207338857a567148b341f8b6640efcc0b9d659 Mon Sep 17 00:00:00 2001 From: Brian Gontowski Date: Thu, 1 May 2014 17:23:34 -0700 Subject: [PATCH 15/15] Fixed confusion between contains and icontains --- django_mongodb_engine/contrib/__init__.py | 2 +- tests/dotquery/tests.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/django_mongodb_engine/contrib/__init__.py b/django_mongodb_engine/contrib/__init__.py index 81a76e35..d45bf42f 100644 --- a/django_mongodb_engine/contrib/__init__.py +++ b/django_mongodb_engine/contrib/__init__.py @@ -144,7 +144,7 @@ def _process_q_filters(self, q): def _maybe_add_dot_field(self, name): if LOOKUP_SEP in name and name.split(LOOKUP_SEP)[0] in self._get_mongo_field_names(): for op in ALL_OPERATORS: - if name.endswith(op): + if name.endswith(LOOKUP_SEP + op): name = re.sub(LOOKUP_SEP + op + '$', '#' + op, name) break name = name.replace(LOOKUP_SEP, '.').replace('#', LOOKUP_SEP) diff --git a/tests/dotquery/tests.py b/tests/dotquery/tests.py index 0a768292..ca365861 100644 --- a/tests/dotquery/tests.py +++ b/tests/dotquery/tests.py @@ -58,6 +58,9 @@ def test_dict_queries(self): qs = DotQueryTestModel.objects.exclude(f_dict__letters__contains='b') self.assertEqual(qs.count(), 1) self.assertEqual(qs[0].f_id, 53) + qs = DotQueryTestModel.objects.exclude(f_dict__letters__icontains='B') + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].f_id, 53) def test_list_queries(self): qs = DotQueryTestModel.objects.filter(f_list__color='red')