diff --git a/docs/changelog.rst b/docs/changelog.rst index f8c59e74..1ef42232 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,7 @@ Development This should have a negative impact on performance of count see Issue #2219 - Fix a bug that made the queryset drop the read_preference after clone(). - Remove Py3.5 from CI as it reached EOL and add Python 3.9 +- Fix some issues related with db_field conflict in constructor #2414 - Fix the behavior of Doc.objects.limit(0) which should return all documents (similar to mongodb) #2311 - Bug fix in ListField when updating the first item, it was saving the whole list, instead of just replacing the first item (as it's usually done) #2392 diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index ee88e894..019d62d0 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -101,12 +101,13 @@ class BaseDocument: self._dynamic_fields = SON() - # Assign default values to the instance. - for key, field in self._fields.items(): - if self._db_field_map.get(key, key) in values: + # Assign default values for fields + # not set in the constructor + for field_name in self._fields: + if field_name in values: continue - value = getattr(self, key, None) - setattr(self, key, value) + value = getattr(self, field_name, None) + setattr(self, field_name, value) if "_cls" not in values: self._cls = self._class_name @@ -115,7 +116,6 @@ class BaseDocument: dynamic_data = {} FileField = _import_class("FileField") for key, value in values.items(): - key = self._reverse_db_field_map.get(key, key) field = self._fields.get(key) if field or key in ("id", "pk", "_cls"): if __auto_convert and value is not None: @@ -750,7 +750,8 @@ class BaseDocument: @classmethod def _from_son(cls, son, _auto_dereference=True, created=False): - """Create an instance of a Document (subclass) from a PyMongo SON.""" + """Create an instance of a Document (subclass) from a PyMongo SON (dict) + """ if son and not isinstance(son, dict): raise ValueError( "The source SON object needs to be of type 'dict' but a '%s' was found" @@ -763,6 +764,8 @@ class BaseDocument: # Convert SON to a data dict, making sure each key is a string and # corresponds to the right db field. + # This is needed as _from_son is currently called both from BaseDocument.__init__ + # and from EmbeddedDocumentField.to_python data = {} for key, value in son.items(): key = str(key) diff --git a/tests/document/test_instance.py b/tests/document/test_instance.py index 20828dd7..42df3c48 100644 --- a/tests/document/test_instance.py +++ b/tests/document/test_instance.py @@ -3822,5 +3822,95 @@ class ObjectKeyTestCase(MongoDBTestCase): assert book._object_key == {"pk": book.pk, "author__name": "Author"} +class DBFieldMappingTest(MongoDBTestCase): + def setUp(self): + class Fields(object): + w1 = BooleanField(db_field="w2") + + x1 = BooleanField(db_field="x2") + x2 = BooleanField(db_field="x3") + + y1 = BooleanField(db_field="y0") + y2 = BooleanField(db_field="y1") + + z1 = BooleanField(db_field="z2") + z2 = BooleanField(db_field="z1") + + class Doc(Fields, Document): + pass + + class DynDoc(Fields, DynamicDocument): + pass + + self.Doc = Doc + self.DynDoc = DynDoc + + def tearDown(self): + for collection in list_collection_names(self.db): + self.db.drop_collection(collection) + + def test_setting_fields_in_constructor_of_strict_doc_uses_model_names(self): + doc = self.Doc(z1=True, z2=False) + assert doc.z1 is True + assert doc.z2 is False + + def test_setting_fields_in_constructor_of_dyn_doc_uses_model_names(self): + doc = self.DynDoc(z1=True, z2=False) + assert doc.z1 is True + assert doc.z2 is False + + def test_setting_unknown_field_in_constructor_of_dyn_doc_does_not_overwrite_model_fields( + self, + ): + doc = self.DynDoc(w2=True) + assert doc.w1 is None + assert doc.w2 is True + + def test_unknown_fields_of_strict_doc_do_not_overwrite_dbfields_1(self): + doc = self.Doc() + doc.w2 = True + doc.x3 = True + doc.y0 = True + doc.save() + reloaded = self.Doc.objects.get(id=doc.id) + assert reloaded.w1 is None + assert reloaded.x1 is None + assert reloaded.x2 is None + assert reloaded.y1 is None + assert reloaded.y2 is None + + def test_dbfields_are_loaded_to_the_right_modelfield_for_strict_doc_2(self): + doc = self.Doc() + doc.x2 = True + doc.y2 = True + doc.z2 = True + doc.save() + reloaded = self.Doc.objects.get(id=doc.id) + assert ( + reloaded.x1, + reloaded.x2, + reloaded.y1, + reloaded.y2, + reloaded.z1, + reloaded.z2, + ) == (doc.x1, doc.x2, doc.y1, doc.y2, doc.z1, doc.z2) + + def test_dbfields_are_loaded_to_the_right_modelfield_for_dyn_doc_2(self): + doc = self.DynDoc() + doc.x2 = True + doc.y2 = True + doc.z2 = True + doc.save() + reloaded = self.DynDoc.objects.get(id=doc.id) + assert ( + reloaded.x1, + reloaded.x2, + reloaded.y1, + reloaded.y2, + reloaded.z1, + reloaded.z2, + ) == (doc.x1, doc.x2, doc.y1, doc.y2, doc.z1, doc.z2) + + if __name__ == "__main__": unittest.main() diff --git a/tests/fields/test_fields.py b/tests/fields/test_fields.py index aa530ced..344656c1 100644 --- a/tests/fields/test_fields.py +++ b/tests/fields/test_fields.py @@ -2272,6 +2272,13 @@ class TestField(MongoDBTestCase): with pytest.raises(FieldDoesNotExist): Doc(bar="test") + def test_undefined_field_works_no_confusion_with_db_field(self): + class Doc(Document): + foo = StringField(db_field="bar") + + with pytest.raises(FieldDoesNotExist): + Doc(bar="test") + class TestEmbeddedDocumentListField(MongoDBTestCase): def setUp(self):