Fixed amibiguity and differing behaviour regarding field defaults (#349)

Now field defaults are king, unsetting or setting to None on a field
with a default means the default is reapplied.
This commit is contained in:
Ross Lawley 2013-06-06 13:31:52 +00:00
parent ea53612822
commit ad15781d8f
7 changed files with 160 additions and 33 deletions

View File

@ -167,3 +167,4 @@ that much better:
* zhy0216 (https://github.com/zhy0216) * zhy0216 (https://github.com/zhy0216)
* istinspring (https://github.com/istinspring) * istinspring (https://github.com/istinspring)
* Massimo Santini (https://github.com/mapio) * Massimo Santini (https://github.com/mapio)
* Nigel McNie (https://github.com/nigelmcnie)

View File

@ -54,6 +54,7 @@ Querying
Fields Fields
====== ======
.. autoclass:: mongoengine.base.fields.BaseField
.. autoclass:: mongoengine.fields.StringField .. autoclass:: mongoengine.fields.StringField
.. autoclass:: mongoengine.fields.URLField .. autoclass:: mongoengine.fields.URLField
.. autoclass:: mongoengine.fields.EmailField .. autoclass:: mongoengine.fields.EmailField

View File

@ -5,6 +5,7 @@ Changelog
Changes in 0.8.2 Changes in 0.8.2
================ ================
- Fixed amibiguity and differing behaviour regarding field defaults (#349)
- ImageFields now include PIL error messages if invalid error (#353) - ImageFields now include PIL error messages if invalid error (#353)
- Added lock when calling doc.Delete() for when signals have no sender (#350) - Added lock when calling doc.Delete() for when signals have no sender (#350)
- Reload forces read preference to be PRIMARY (#355) - Reload forces read preference to be PRIMARY (#355)

View File

@ -36,6 +36,29 @@ class BaseField(object):
unique=False, unique_with=None, primary_key=False, unique=False, unique_with=None, primary_key=False,
validation=None, choices=None, verbose_name=None, validation=None, choices=None, verbose_name=None,
help_text=None): help_text=None):
"""
:param db_field: The database field to store this field in
(defaults to the name of the field)
:param name: Depreciated - use db_field
:param required: If the field is required. Whether it has to have a
value or not. Defaults to False.
:param default: (optional) The default value for this field if no value
has been set (or if the value has been unset). It Can be a
callable.
:param unique: Is the field value unique or not. Defaults to False.
:param unique_with: (optional) The other field this field should be
unique with.
:param primary_key: Mark this field as the primary key. Defaults to False.
:param validation: (optional) A callable to validate the value of the
field. Generally this is deprecated in favour of the
`FIELD.validate` method
:param choices: (optional) The valid choices
:param verbose_name: (optional) The verbose name for the field.
Designed to be human readable and is often used when generating
model forms from the document model.
:param help_text: (optional) The help text for this field and is often
used when generating model forms from the document model.
"""
self.db_field = (db_field or name) if not primary_key else '_id' self.db_field = (db_field or name) if not primary_key else '_id'
if name: if name:
msg = "Fields' 'name' attribute deprecated in favour of 'db_field'" msg = "Fields' 'name' attribute deprecated in favour of 'db_field'"
@ -65,14 +88,9 @@ class BaseField(object):
if instance is None: if instance is None:
# Document class being used rather than a document object # Document class being used rather than a document object
return self return self
# Get value from document instance if available, if not use default
value = instance._data.get(self.name)
if value is None: # Get value from document instance if available
value = self.default value = instance._data.get(self.name)
# Allow callable default values
if callable(value):
value = value()
EmbeddedDocument = _import_class('EmbeddedDocument') EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None: if isinstance(value, EmbeddedDocument) and value._instance is None:
@ -82,9 +100,11 @@ class BaseField(object):
def __set__(self, instance, value): def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document. """Descriptor for assigning a value to a field in a document.
""" """
if value is None:
# If setting to None and theres a default
# Then set the value to the default value
if value is None and self.default is not None:
value = self.default value = self.default
# Allow callable default values
if callable(value): if callable(value):
value = value() value = value()

View File

@ -206,7 +206,7 @@ class Document(BaseDocument):
if validate: if validate:
self.validate(clean=clean) self.validate(clean=clean)
if not write_concern: if write_concern is None:
write_concern = {"w": 1} write_concern = {"w": 1}
doc = self.to_mongo() doc = self.to_mongo()

View File

@ -348,7 +348,7 @@ class QuerySet(object):
""" """
Document = _import_class('Document') Document = _import_class('Document')
if not write_concern: if write_concern is None:
write_concern = {} write_concern = {}
docs = doc_or_docs docs = doc_or_docs
@ -424,7 +424,7 @@ class QuerySet(object):
queryset = self.clone() queryset = self.clone()
doc = queryset._document doc = queryset._document
if not write_concern: if write_concern is None:
write_concern = {} write_concern = {}
# Handle deletes where skips or limits have been applied or # Handle deletes where skips or limits have been applied or
@ -490,7 +490,7 @@ class QuerySet(object):
if not update and not upsert: if not update and not upsert:
raise OperationError("No update parameters, would remove data") raise OperationError("No update parameters, would remove data")
if not write_concern: if write_concern is None:
write_concern = {} write_concern = {}
queryset = self.clone() queryset = self.clone()

View File

@ -34,33 +34,137 @@ class FieldTest(unittest.TestCase):
self.db.drop_collection('fs.files') self.db.drop_collection('fs.files')
self.db.drop_collection('fs.chunks') self.db.drop_collection('fs.chunks')
def test_default_values(self): def test_default_values_nothing_set(self):
"""Ensure that default field values are used when creating a document. """Ensure that default field values are used when creating a document.
""" """
class Person(Document): class Person(Document):
name = StringField() name = StringField()
age = IntField(default=30, help_text="Your real age") age = IntField(default=30, required=False)
userid = StringField(default=lambda: 'test', verbose_name="User Identity") userid = StringField(default=lambda: 'test', required=True)
person = Person(name='Test Person')
self.assertEqual(person._data['age'], 30)
self.assertEqual(person._data['userid'], 'test')
self.assertEqual(person._fields['name'].help_text, None)
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) created = DateTimeField(default=datetime.datetime.utcnow)
person = Person2() person = Person(name="Ross")
date1 = person.created
date2 = person.created
self.assertEqual(date1, date2)
person = Person2(created=None) # Confirm saving now would store values
date1 = person.created data_to_be_saved = sorted(person.to_mongo().keys())
date2 = person.created self.assertEqual(data_to_be_saved, ['age', 'created', 'name', 'userid'])
self.assertEqual(date1, date2)
self.assertTrue(person.validate() is None)
self.assertEqual(person.name, person.name)
self.assertEqual(person.age, person.age)
self.assertEqual(person.userid, person.userid)
self.assertEqual(person.created, person.created)
self.assertEqual(person._data['name'], person.name)
self.assertEqual(person._data['age'], person.age)
self.assertEqual(person._data['userid'], person.userid)
self.assertEqual(person._data['created'], person.created)
# Confirm introspection changes nothing
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'name', 'userid'])
def test_default_values_set_to_None(self):
"""Ensure that default field values are used when creating a document.
"""
class Person(Document):
name = StringField()
age = IntField(default=30, required=False)
userid = StringField(default=lambda: 'test', required=True)
created = DateTimeField(default=datetime.datetime.utcnow)
# Trying setting values to None
person = Person(name=None, age=None, userid=None, created=None)
# Confirm saving now would store values
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
self.assertTrue(person.validate() is None)
self.assertEqual(person.name, person.name)
self.assertEqual(person.age, person.age)
self.assertEqual(person.userid, person.userid)
self.assertEqual(person.created, person.created)
self.assertEqual(person._data['name'], person.name)
self.assertEqual(person._data['age'], person.age)
self.assertEqual(person._data['userid'], person.userid)
self.assertEqual(person._data['created'], person.created)
# Confirm introspection changes nothing
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
def test_default_values_when_setting_to_None(self):
"""Ensure that default field values are used when creating a document.
"""
class Person(Document):
name = StringField()
age = IntField(default=30, required=False)
userid = StringField(default=lambda: 'test', required=True)
created = DateTimeField(default=datetime.datetime.utcnow)
person = Person()
person.name = None
person.age = None
person.userid = None
person.created = None
# Confirm saving now would store values
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
self.assertTrue(person.validate() is None)
self.assertEqual(person.name, person.name)
self.assertEqual(person.age, person.age)
self.assertEqual(person.userid, person.userid)
self.assertEqual(person.created, person.created)
self.assertEqual(person._data['name'], person.name)
self.assertEqual(person._data['age'], person.age)
self.assertEqual(person._data['userid'], person.userid)
self.assertEqual(person._data['created'], person.created)
# Confirm introspection changes nothing
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
def test_default_values_when_deleting_value(self):
"""Ensure that default field values are used when creating a document.
"""
class Person(Document):
name = StringField()
age = IntField(default=30, required=False)
userid = StringField(default=lambda: 'test', required=True)
created = DateTimeField(default=datetime.datetime.utcnow)
person = Person(name="Ross")
del person.name
del person.age
del person.userid
del person.created
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
self.assertTrue(person.validate() is None)
self.assertEqual(person.name, person.name)
self.assertEqual(person.age, person.age)
self.assertEqual(person.userid, person.userid)
self.assertEqual(person.created, person.created)
self.assertEqual(person._data['name'], person.name)
self.assertEqual(person._data['age'], person.age)
self.assertEqual(person._data['userid'], person.userid)
self.assertEqual(person._data['created'], person.created)
# Confirm introspection changes nothing
data_to_be_saved = sorted(person.to_mongo().keys())
self.assertEqual(data_to_be_saved, ['age', 'created', 'userid'])
def test_required_values(self): def test_required_values(self):
"""Ensure that required field constraints are enforced. """Ensure that required field constraints are enforced.