From 4c9e90732e711dfe3fac8f4f887330673147f51c Mon Sep 17 00:00:00 2001 From: Nigel McNie Date: Thu, 30 May 2013 16:37:40 +1200 Subject: [PATCH] Apply defaults to fields with None value at 'set' time. If a field has a default, and you explicitly set it to None, the behaviour before this patch was very confusing: class Person(Document): created = DateTimeField(default=datetime.datetime.utcnow) >>> p = Person(created=None) >>> p.created datetime.datetime(2013, 5, 30, 0, 18, 20, 242628) >>> p.created datetime.datetime(2013, 5, 30, 0, 18, 20, 995248) >>> p.created datetime.datetime(2013, 5, 30, 0, 18, 21, 370578) It would be stored as None, and then at 'get' time, the default would be applied. As you can see, if the default is a generator, this leads to some crazy behaviour. There's an argument that if I asked it to be set to None, why not respect that? But I don't think that's how the rest of mongoengine seems to work (for example, setting a field to None seems to mean it doesn't even get set in mongo - as opposed to being set but with a 'null' value). Besides, as the code shows above, you'd expect p.created to return None. So clearly, mongoengine is already expecting None to mean 'default' where a default is available. This bug also interacts nastily with required=True - if you're forcibly setting the field to None, then at validation time, the None will fail validation despite a perfectly valid default being available. With this patch, when the field is set, the default is immediately applied. This means any generation happens once, the getter always returns the same value, and 'required' validation always respects the default. Note: this breakage seems to be new since mongoengine 0.8. --- mongoengine/base/fields.py | 6 ++++++ tests/fields/fields.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index 72a9e8eb..94540234 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -82,6 +82,12 @@ class BaseField(object): def __set__(self, instance, value): """Descriptor for assigning a value to a field in a document. """ + if value is None: + value = self.default + # Allow callable default values + if callable(value): + value = value() + if instance._initialised: try: if (self.name not in instance._data or diff --git a/tests/fields/fields.py b/tests/fields/fields.py index e803af84..32c33f79 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -44,6 +44,19 @@ class FieldTest(unittest.TestCase): self.assertEqual(person._fields['age'].help_text, "Your real age") self.assertEqual(person._fields['userid'].verbose_name, "User Identity") + class Person2(Document): + created = DateTimeField(default=datetime.datetime.utcnow) + + person = Person2() + date1 = person.created + date2 = person.created + self.assertEqual(date1, date2) + + person = Person2(created=None) + date1 = person.created + date2 = person.created + self.assertEqual(date1, date2) + def test_required_values(self): """Ensure that required field constraints are enforced. """