Skip to content

Commit

Permalink
Merge pull request #2414 from bagerard/fix_db_fields_inconsistencies_…
Browse files Browse the repository at this point in the history
…in_constructor

Fix some issues related with db_field in constructor
  • Loading branch information
bagerard committed Nov 18, 2020
2 parents 079ee3c + f2638ec commit ecd297e
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 10 additions & 7 deletions mongoengine/base/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ def __init__(self, *args, **values):

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
Expand All @@ -115,7 +116,6 @@ def __init__(self, *args, **values):
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:
Expand Down Expand Up @@ -750,7 +750,8 @@ def _get_collection_name(cls):

@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"
Expand All @@ -763,6 +764,8 @@ def _from_son(cls, son, _auto_dereference=True, created=False):

# 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)
Expand Down
90 changes: 90 additions & 0 deletions tests/document/test_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3822,5 +3822,95 @@ class Book(Document):
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()
7 changes: 7 additions & 0 deletions tests/fields/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -2272,6 +2272,13 @@ class Doc(Document):
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):
Expand Down

0 comments on commit ecd297e

Please sign in to comment.