Skip to content

Commit c4ef4d4

Browse files
authored
Merge pull request #2723 from bagerard/clone_null_check_oid
Work on top of "Added null check for Issue"
2 parents d783f32 + e81a106 commit c4ef4d4

14 files changed

+118
-23
lines changed

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Development
1919
Although it could work to switch an existing DecimalField to Decimal128Field without applying a migration script,
2020
it is not recommended to do so (DecimalField uses float/str to store the value, Decimal128Field uses Decimal128).
2121
- BREAKING CHANGE: When using ListField(EnumField) or DictField(EnumField), the values weren't always cast into the Enum (#2531)
22+
- BREAKING CHANGE (bugfix) Querying ObjectIdField or ComplexDateTimeField with None will no longer raise a ValidationError (#2681)
2223

2324
Changes in 0.25.0
2425
=================

mongoengine/base/document.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def to_mongo(self, use_db_field=True, fields=None):
372372
value = field.generate()
373373
self._data[field_name] = value
374374

375-
if (value is not None) or (field.null):
375+
if value is not None or field.null:
376376
if use_db_field:
377377
data[field.db_field] = value
378378
else:

mongoengine/base/fields.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,20 @@ def __init__(
5252
:param required: If the field is required. Whether it has to have a
5353
value or not. Defaults to False.
5454
:param default: (optional) The default value for this field if no value
55-
has been set (or if the value has been unset). It can be a
55+
has been set, if the value is set to None or has been unset. It can be a
5656
callable.
57-
:param unique: Is the field value unique or not. Defaults to False.
57+
:param unique: Is the field value unique or not (Creates an index). Defaults to False.
5858
:param unique_with: (optional) The other field this field should be
59-
unique with.
60-
:param primary_key: Mark this field as the primary key. Defaults to False.
59+
unique with (Creates an index).
60+
:param primary_key: Mark this field as the primary key ((Creates an index)). Defaults to False.
6161
:param validation: (optional) A callable to validate the value of the
6262
field. The callable takes the value as parameter and should raise
6363
a ValidationError if validation fails
6464
:param choices: (optional) The valid choices
65-
:param null: (optional) If the field value can be null. If no and there is a default value
66-
then the default value is set
65+
:param null: (optional) If the field value can be null when a default exist. If not set, the default value
66+
will be used in case a field with a default value is set to None. Defaults to False.
6767
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
68-
means that uniqueness won't be enforced for `None` values
68+
means that uniqueness won't be enforced for `None` values (Creates an index). Defaults to False.
6969
:param **kwargs: (optional) Arbitrary indirection-free metadata for
7070
this field can be supplied as additional keyword arguments and
7171
accessed as attributes of the field. Must not conflict with any
@@ -521,14 +521,17 @@ def to_python(self, value):
521521
return value
522522

523523
def to_mongo(self, value):
524-
if not isinstance(value, ObjectId):
525-
try:
526-
return ObjectId(str(value))
527-
except Exception as e:
528-
self.error(str(e))
529-
return value
524+
if isinstance(value, ObjectId):
525+
return value
526+
527+
try:
528+
return ObjectId(str(value))
529+
except Exception as e:
530+
self.error(str(e))
530531

531532
def prepare_query_value(self, op, value):
533+
if value is None:
534+
return value
532535
return self.to_mongo(value)
533536

534537
def validate(self, value):

mongoengine/document.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,9 @@ def save(
422422
try:
423423
# Save a new document or update an existing one
424424
if created:
425-
object_id = self._save_create(doc, force_insert, write_concern)
425+
object_id = self._save_create(
426+
doc=doc, force_insert=force_insert, write_concern=write_concern
427+
)
426428
else:
427429
object_id, created = self._save_update(
428430
doc, save_condition, write_concern

mongoengine/fields.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,6 @@ def __init__(
490490
super().__init__(**kwargs)
491491

492492
def to_python(self, value):
493-
if value is None:
494-
return value
495-
496493
# Convert to string for python 2.6 before casting to Decimal
497494
try:
498495
value = decimal.Decimal("%s" % value)
@@ -506,8 +503,6 @@ def to_python(self, value):
506503
return value.quantize(decimal.Decimal(), rounding=self.rounding)
507504

508505
def to_mongo(self, value):
509-
if value is None:
510-
return value
511506
if self.force_string:
512507
return str(self.to_python(value))
513508
return float(self.to_python(value))
@@ -528,6 +523,8 @@ def validate(self, value):
528523
self.error("Decimal value is too large")
529524

530525
def prepare_query_value(self, op, value):
526+
if value is None:
527+
return value
531528
return super().prepare_query_value(op, self.to_mongo(value))
532529

533530

@@ -731,6 +728,8 @@ def to_mongo(self, value):
731728
return self._convert_from_datetime(value)
732729

733730
def prepare_query_value(self, op, value):
731+
if value is None:
732+
return value
734733
return super().prepare_query_value(op, self._convert_from_datetime(value))
735734

736735

tests/document/test_instance.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uuid
55
import weakref
66
from datetime import datetime
7+
from unittest.mock import Mock
78

89
import bson
910
import pytest
@@ -1034,6 +1035,17 @@ def test_save(self):
10341035
"_id": person.id,
10351036
}
10361037

1038+
def test_save_write_concern(self):
1039+
class Recipient(Document):
1040+
email = EmailField(required=True)
1041+
1042+
rec = Recipient(email="garbage@garbage.com")
1043+
1044+
fn = Mock()
1045+
rec._save_create = fn
1046+
rec.save(write_concern={"w": 0})
1047+
assert fn.call_args[1]["write_concern"] == {"w": 0}
1048+
10371049
def test_save_skip_validation(self):
10381050
class Recipient(Document):
10391051
email = EmailField(required=True)
@@ -3593,8 +3605,7 @@ class User(Document):
35933605
cdt_fld = ComplexDateTimeField(null=True)
35943606

35953607
User.objects.delete()
3596-
u = User(name="user")
3597-
u.save()
3608+
u = User(name="user").save()
35983609
u_from_db = User.objects.get(name="user")
35993610
u_from_db.height = None
36003611
u_from_db.save()

tests/fields/test_complex_datetime_field.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,9 @@ class Log(Document):
206206

207207
with pytest.raises(ValidationError):
208208
log.save()
209+
210+
def test_query_none_value_dont_raise(self):
211+
class Log(Document):
212+
timestamp = ComplexDateTimeField()
213+
214+
_ = list(Log.objects(timestamp=None))

tests/fields/test_decimal_field.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class Person(Document):
7171
fetched_person = Person.objects.first()
7272
fetched_person.value is None
7373

74+
assert Person.objects(value=None).first() is not None
75+
7476
def test_validation(self):
7577
"""Ensure that invalid values cannot be assigned to decimal fields."""
7678

tests/fields/test_float_field.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,9 @@ class BigPerson(Document):
5757
big_person.height = 2**100000 # Too big for a float value
5858
with pytest.raises(ValidationError):
5959
big_person.validate()
60+
61+
def test_query_none_value_dont_raise(self):
62+
class BigPerson(Document):
63+
height = FloatField()
64+
65+
_ = list(BigPerson.objects(height=None))

tests/fields/test_object_id_field.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@ class MyDoc(Document):
2828
doc = MyDoc(oid="not-an-oid!")
2929
with pytest.raises(ValidationError, match="Invalid ObjectID"):
3030
doc.save()
31+
32+
def test_query_none_value_dont_raise(self):
33+
# cf issue #2681
34+
class MyDoc(Document):
35+
oid = ObjectIdField(null=True)
36+
37+
_ = list(MyDoc.objects(oid=None))

0 commit comments

Comments
 (0)