From fc0e67023a0fc56b5dc8572e71759bd5d086f385 Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Fri, 18 Dec 2009 13:40:33 +0000 Subject: [PATCH 1/9] Required fields may now use default values --- mongoengine/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 61deb409..abfe05dd 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -166,10 +166,11 @@ class BaseDocument(object): if attr_name in values: setattr(self, attr_name, values.pop(attr_name)) else: - if attr_value.required: + # Use default value if present + value = getattr(self, attr_name, None) + if value is None and attr_value.required: raise ValidationError('Field "%s" is required' % attr_name) - # Use default value - setattr(self, attr_name, getattr(self, attr_name, None)) + setattr(self, attr_name, value) @classmethod def _get_subclasses(cls): From 0a64f42d5f2162a9f39f49bfd9c5eab9e6f638b1 Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Fri, 18 Dec 2009 16:31:32 +0000 Subject: [PATCH 2/9] Renamed Document._id to id (still _id in DB) Although MongoDB uses _id, underscore prefixed attributes imply private access in Python and are sometimes may not be accessed (e.g. in the Django template language), but id should be public. --- mongoengine/base.py | 8 ++++---- mongoengine/document.py | 4 ++-- mongoengine/fields.py | 10 +++++----- tests/document.py | 12 ++++++------ tests/fields.py | 8 ++++---- tests/queryset.py | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index abfe05dd..4d42bbcd 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -147,7 +147,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): meta.update(attrs.get('meta', {})) attrs['_meta'] = meta - attrs['_id'] = ObjectIdField() + attrs['id'] = ObjectIdField(name='_id') # Set up collection manager, needs the class to have fields so use # DocumentMetaclass before instantiating CollectionManager object @@ -225,7 +225,7 @@ class BaseDocument(object): for field_name, field in self._fields.items(): value = getattr(self, field_name, None) if value is not None: - data[field_name] = field.to_mongo(value) + data[field.name] = field.to_mongo(value) data['_cls'] = self._class_name data['_types'] = self._superclasses.keys() + [self._class_name] return data @@ -248,7 +248,7 @@ class BaseDocument(object): cls = subclasses[class_name] for field_name, field in cls._fields.items(): - if field_name in data: - data[field_name] = field.to_python(data[field_name]) + if field.name in data: + data[field_name] = field.to_python(data[field.name]) return cls(**data) diff --git a/mongoengine/document.py b/mongoengine/document.py index 359a1789..a0bbda7c 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -18,8 +18,8 @@ class Document(BaseDocument): """Save the document to the database. If the document already exists, it will be updated, otherwise it will be created. """ - _id = self.objects._collection.save(self.to_mongo()) - self._id = _id + object_id = self.objects._collection.save(self.to_mongo()) + self.id = object_id @classmethod def drop_collection(cls): diff --git a/mongoengine/fields.py b/mongoengine/fields.py index c8a9086d..1625a582 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -179,19 +179,19 @@ class ReferenceField(BaseField): def to_mongo(self, document): if isinstance(document, (str, unicode, pymongo.objectid.ObjectId)): - _id = document + id_ = document else: try: - _id = document._id + id_ = document.id except: raise ValidationError('You can only reference documents once ' 'they have been saved to the database') - if not isinstance(_id, pymongo.objectid.ObjectId): - _id = pymongo.objectid.ObjectId(_id) + if not isinstance(id_, pymongo.objectid.ObjectId): + id_ = pymongo.objectid.ObjectId(id_) collection = self.document_type._meta['collection'] - return pymongo.dbref.DBRef(collection, _id) + return pymongo.dbref.DBRef(collection, id_) def validate(self, value): assert(isinstance(value, (self.document_type, pymongo.dbref.DBRef))) diff --git a/tests/document.py b/tests/document.py index c20e9b33..0d692395 100644 --- a/tests/document.py +++ b/tests/document.py @@ -41,7 +41,7 @@ class DocumentTest(unittest.TestCase): self.assertEqual(Person._fields['name'], name_field) self.assertEqual(Person._fields['age'], age_field) self.assertFalse('non_field' in Person._fields) - self.assertTrue('_id' in Person._fields) + self.assertTrue('id' in Person._fields) # Test iteration over fields fields = list(Person()) self.assertTrue('name' in fields and 'age' in fields) @@ -145,7 +145,7 @@ class DocumentTest(unittest.TestCase): person['name'] = 'Another User' self.assertEquals(person['name'], 'Another User') - # Length = length(assigned fields + _id) + # Length = length(assigned fields + id) self.assertEquals(len(person), 3) self.assertTrue('age' in person) @@ -160,7 +160,7 @@ class DocumentTest(unittest.TestCase): content = StringField() self.assertTrue('content' in Comment._fields) - self.assertFalse('_id' in Comment._fields) + self.assertFalse('id' in Comment._fields) self.assertFalse(hasattr(Comment, '_meta')) def test_save(self): @@ -174,14 +174,14 @@ class DocumentTest(unittest.TestCase): person_obj = collection.find_one({'name': 'Test User'}) self.assertEqual(person_obj['name'], 'Test User') self.assertEqual(person_obj['age'], 30) - self.assertEqual(person_obj['_id'], person._id) + self.assertEqual(person_obj['_id'], person.id) def test_save_custom_id(self): """Ensure that a document may be saved with a custom _id. """ # Create person object and save it to the database person = self.Person(name='Test User', age=30, - _id='497ce96f395f2f052a494fd4') + id='497ce96f395f2f052a494fd4') person.save() # Ensure that the object is in the database with the correct _id collection = self.db[self.Person._meta['collection']] @@ -268,7 +268,7 @@ class DocumentTest(unittest.TestCase): post_obj.author.age = 25 post_obj.author.save() - author = self.Person.objects(name='Test User').first() + author = list(self.Person.objects(name='Test User'))[-1] self.assertEqual(author.age, 25) BlogPost.drop_collection() diff --git a/tests/fields.py b/tests/fields.py index e8e7a46b..49c3f70f 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -46,10 +46,10 @@ class FieldTest(unittest.TestCase): name = StringField() person = Person(name='Test User') - self.assertRaises(AttributeError, getattr, person, '_id') - self.assertRaises(ValidationError, person.__setattr__, '_id', 47) - self.assertRaises(ValidationError, person.__setattr__, '_id', 'abc') - person._id = '497ce96f395f2f052a494fd4' + self.assertRaises(AttributeError, getattr, person, 'id') + self.assertRaises(ValidationError, person.__setattr__, 'id', 47) + self.assertRaises(ValidationError, person.__setattr__, 'id', 'abc') + person.id = '497ce96f395f2f052a494fd4' def test_string_validation(self): """Ensure that invalid values cannot be assigned to string fields. diff --git a/tests/queryset.py b/tests/queryset.py index 344ee65d..0ba1bb16 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -53,7 +53,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(people.count(), 2) results = list(people) self.assertTrue(isinstance(results[0], self.Person)) - self.assertTrue(isinstance(results[0]._id, (pymongo.objectid.ObjectId, + self.assertTrue(isinstance(results[0].id, (pymongo.objectid.ObjectId, str, unicode))) self.assertEqual(results[0].name, "User A") self.assertEqual(results[0].age, 20) @@ -99,7 +99,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(person.name, "User A") # Find a document using just the object id - person = self.Person.objects.with_id(person1._id) + person = self.Person.objects.with_id(person1.id) self.assertEqual(person.name, "User A") def test_find_embedded(self): From 44fc9096a46c4cde99da291c7f1c3156a7595d0b Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Fri, 18 Dec 2009 16:57:53 +0000 Subject: [PATCH 3/9] Added delete method to Document objects --- mongoengine/document.py | 6 ++++++ tests/document.py | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/mongoengine/document.py b/mongoengine/document.py index a0bbda7c..5bb313ea 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -21,6 +21,12 @@ class Document(BaseDocument): object_id = self.objects._collection.save(self.to_mongo()) self.id = object_id + def delete(self): + """Delete the document from the database. This will only take effect + if the document has been previously saved. + """ + self.objects._collection.remove(self.id) + @classmethod def drop_collection(cls): """Drops the entire collection associated with this Document type from diff --git a/tests/document.py b/tests/document.py index 0d692395..fdb195de 100644 --- a/tests/document.py +++ b/tests/document.py @@ -176,6 +176,15 @@ class DocumentTest(unittest.TestCase): self.assertEqual(person_obj['age'], 30) self.assertEqual(person_obj['_id'], person.id) + def test_delete(self): + """Ensure that document may be deleted using the delete method. + """ + person = self.Person(name="Test User", age=30) + person.save() + self.assertEqual(self.Person.objects.count(), 1) + person.delete() + self.assertEqual(self.Person.objects.count(), 0) + def test_save_custom_id(self): """Ensure that a document may be saved with a custom _id. """ From 5e6a6aa886647b3268ed0e07a307772b9724fd95 Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Fri, 18 Dec 2009 18:41:44 +0000 Subject: [PATCH 4/9] Added sort method to QuerySet --- mongoengine/document.py | 3 ++- mongoengine/queryset.py | 16 ++++++++++++++++ tests/queryset.py | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/mongoengine/document.py b/mongoengine/document.py index 5bb313ea..99c98cd4 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -25,7 +25,8 @@ class Document(BaseDocument): """Delete the document from the database. This will only take effect if the document has been previously saved. """ - self.objects._collection.remove(self.id) + object_id = self._fields['id'].to_mongo(self.id) + self.__class__.objects(_id=object_id).delete() @classmethod def drop_collection(cls): diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index c5997cf6..abe52a50 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -97,6 +97,22 @@ class QuerySet(object): """ self._collection.remove(self._query) + def sort(self, *keys): + """Sort the QuerySet by the keys. The order may be specified by + prepending each of the keys by a + or a -. Ascending order is assumed. + """ + key_list = [] + for key in keys: + direction = pymongo.ASCENDING + if key[0] == '-': + direction = pymongo.DESCENDING + if key[0] in ('-', '+'): + key = key[1:] + key_list.append((key, direction)) + + self._cursor.sort(key_list) + return self + def __iter__(self): return self diff --git a/tests/queryset.py b/tests/queryset.py index 0ba1bb16..39b14fc1 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -137,6 +137,25 @@ class QuerySetTest(unittest.TestCase): self.Person.objects.delete() self.assertEqual(self.Person.objects.count(), 0) + def test_sort(self): + """Ensure that QuerySets may be sorted. + """ + self.Person(name="User A", age=20).save() + self.Person(name="User B", age=40).save() + self.Person(name="User C", age=30).save() + + names = [p.name for p in self.Person.objects.sort('-age')] + self.assertEqual(names, ['User B', 'User C', 'User A']) + + names = [p.name for p in self.Person.objects.sort('+age')] + self.assertEqual(names, ['User A', 'User C', 'User B']) + + names = [p.name for p in self.Person.objects.sort('age')] + self.assertEqual(names, ['User A', 'User C', 'User B']) + + ages = [p.age for p in self.Person.objects.sort('-name')] + self.assertEqual(ages, [30, 40, 20]) + def tearDown(self): self.Person.drop_collection() From c58f377a0ace2f9aa359c149e64e5261c9159902 Mon Sep 17 00:00:00 2001 From: blackbrrr Date: Sat, 19 Dec 2009 01:34:32 +0800 Subject: [PATCH 5/9] removed reliance on '_cls' in document; fields only parsed if '__class__' present, allowing inner classes and non-field attributes on a document --- mongoengine/base.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index 4d42bbcd..ff6fbd66 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -111,7 +111,8 @@ class DocumentMetaclass(type): # Add the document's fields to the _fields attribute for attr_name, attr_value in attrs.items(): - if issubclass(attr_value.__class__, BaseField): + if hasattr(attr_value, "__class__") and \ + issubclass(attr_value.__class__, BaseField): if not attr_value.name: attr_value.name = attr_name doc_fields[attr_name] = attr_value @@ -135,7 +136,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): if attrs.get('__metaclass__') == TopLevelDocumentMetaclass: return super_new(cls, name, bases, attrs) - collection = name.lower() + collection = attrs.get('__collection__', name.lower()) + # Subclassed documents inherit collection from superclass for base in bases: if hasattr(base, '_meta') and 'collection' in base._meta: @@ -234,9 +236,14 @@ class BaseDocument(object): def _from_son(cls, son): """Create an instance of a Document (subclass) from a PyMongo SOM. """ - class_name = son[u'_cls'] + # get the class name from the document, falling back to the given + # class if unavailable + class_name = son.get(u'_cls', cls._class_name) + data = dict((str(key), value) for key, value in son.items()) - del data['_cls'] + + if '_cls' in data: + del data['_cls'] # Return correct subclass for document type if class_name != cls._class_name: From 551b2755d40980801d0ffb383b6a3d4ff15f39cd Mon Sep 17 00:00:00 2001 From: blackbrrr Date: Sat, 19 Dec 2009 01:35:26 +0800 Subject: [PATCH 6/9] added 'ensure_index' and 'order_by' methods to queryset. --- mongoengine/queryset.py | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index abe52a50..8bb3510c 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -11,8 +11,22 @@ class QuerySet(object): def __init__(self, document, collection): self._document = document self._collection = collection - self._query = {'_types': self._document._class_name} + self._query = {} self._cursor_obj = None + self._ordering = [] + + def ensure_index(self, key_or_list, direction=None): + """Ensure that the given indexes are in place. + """ + if isinstance(key_or_list, basestring): + # single-field indexes needn't specify a direction + if key_or_list.startswith("-"): + key_or_list = key_or_list[1:] + self._collection.ensure_index(key_or_list) + elif isinstance(key_or_list, (list, tuple)): + print key_or_list + self._collection.ensure_index(key_or_list) + return self def __call__(self, **query): """Filter the selected documents by calling the queryset with a query. @@ -91,7 +105,36 @@ class QuerySet(object): """ self._cursor.skip(n) return self + + def order_by(self, *params): + """Apply ordering conditions, Django-style. + + e.g., ``Model.objects.().order_by("-published_date", "ordering")`` + will order first by ``published_date DESC``, and then ``ordering ASC``. + + """ + if not params: + self._ordering = [] + for param in params: + if param.startswith("-"): + param = param[1:] + sort_dir = pymongo.DESCENDING + else: + sort_dir = pymongo.ASCENDING + sort_rule = (param, sort_dir) + if not sort_rule in self._ordering: + self._ordering.append(sort_rule) + self._cursor.sort(self._ordering) + return self + + def explain(self, format=False): + plan = self._cursor.explain() + if format: + import pprint + plan = pprint.pformat(plan) + return plan + def delete(self): """Delete the documents matched by the query. """ From 9d12dbad704d8d273684397d642bdaefd2546789 Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Sat, 19 Dec 2009 02:33:01 +0000 Subject: [PATCH 7/9] Made _cls etc optional, merged sort to order_by --- mongoengine/base.py | 27 +++++++++++++++++--- mongoengine/queryset.py | 55 ++++++++++++++--------------------------- tests/document.py | 30 ++++++++++++++++++++++ tests/queryset.py | 12 ++++----- 4 files changed, 78 insertions(+), 46 deletions(-) diff --git a/mongoengine/base.py b/mongoengine/base.py index ff6fbd66..60409127 100644 --- a/mongoengine/base.py +++ b/mongoengine/base.py @@ -136,17 +136,32 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): if attrs.get('__metaclass__') == TopLevelDocumentMetaclass: return super_new(cls, name, bases, attrs) - collection = attrs.get('__collection__', name.lower()) + collection = name.lower() + simple_class = True # Subclassed documents inherit collection from superclass for base in bases: if hasattr(base, '_meta') and 'collection' in base._meta: + # Ensure that the Document class may be subclassed - + # inheritance may be disabled to remove dependency on + # additional fields _cls and _types + if base._meta.get('allow_inheritance', True) == False: + raise ValueError('Document %s may not be subclassed' % + base.__name__) + else: + simple_class = False collection = base._meta['collection'] meta = { 'collection': collection, + 'allow_inheritance': True, } meta.update(attrs.get('meta', {})) + # Only simple classes - direct subclasses of Document - may set + # allow_inheritance to False + if not simple_class and not meta['allow_inheritance']: + raise ValueError('Only direct subclasses of Document may set ' + '"allow_inheritance" to False') attrs['_meta'] = meta attrs['id'] = ObjectIdField(name='_id') @@ -228,8 +243,11 @@ class BaseDocument(object): value = getattr(self, field_name, None) if value is not None: data[field.name] = field.to_mongo(value) - data['_cls'] = self._class_name - data['_types'] = self._superclasses.keys() + [self._class_name] + # Only add _cls and _types if allow_inheritance is not False + if not (hasattr(self, '_meta') and + self._meta.get('allow_inheritance', True) == False): + data['_cls'] = self._class_name + data['_types'] = self._superclasses.keys() + [self._class_name] return data @classmethod @@ -242,6 +260,9 @@ class BaseDocument(object): data = dict((str(key), value) for key, value in son.items()) + if '_types' in data: + del data['_types'] + if '_cls' in data: del data['_cls'] diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 8bb3510c..4f406b44 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -12,8 +12,11 @@ class QuerySet(object): self._document = document self._collection = collection self._query = {} + # If inheritance is allowed, only return instances and instances of + # subclasses of the class being used + if document._meta.get('allow_inheritance'): + self._query = {'_types': self._document._class_name} self._cursor_obj = None - self._ordering = [] def ensure_index(self, key_or_list, direction=None): """Ensure that the given indexes are in place. @@ -105,43 +108,9 @@ class QuerySet(object): """ self._cursor.skip(n) return self - - def order_by(self, *params): - """Apply ordering conditions, Django-style. - - e.g., ``Model.objects.().order_by("-published_date", "ordering")`` - will order first by ``published_date DESC``, and then ``ordering ASC``. - - """ - if not params: - self._ordering = [] - for param in params: - if param.startswith("-"): - param = param[1:] - sort_dir = pymongo.DESCENDING - else: - sort_dir = pymongo.ASCENDING - sort_rule = (param, sort_dir) - if not sort_rule in self._ordering: - self._ordering.append(sort_rule) - self._cursor.sort(self._ordering) - return self - - def explain(self, format=False): - plan = self._cursor.explain() - if format: - import pprint - plan = pprint.pformat(plan) - return plan - - def delete(self): - """Delete the documents matched by the query. - """ - self._collection.remove(self._query) - - def sort(self, *keys): - """Sort the QuerySet by the keys. The order may be specified by + def order_by(self, *keys): + """Order the QuerySet by the keys. The order may be specified by prepending each of the keys by a + or a -. Ascending order is assumed. """ key_list = [] @@ -155,6 +124,18 @@ class QuerySet(object): self._cursor.sort(key_list) return self + + def explain(self, format=False): + plan = self._cursor.explain() + if format: + import pprint + plan = pprint.pformat(plan) + return plan + + def delete(self): + """Delete the documents matched by the query. + """ + self._collection.remove(self._query) def __iter__(self): return self diff --git a/tests/document.py b/tests/document.py index fdb195de..bec66ee9 100644 --- a/tests/document.py +++ b/tests/document.py @@ -126,6 +126,36 @@ class DocumentTest(unittest.TestCase): self.assertEqual(Employee._meta['collection'], self.Person._meta['collection']) + def test_allow_inheritance(self): + """Ensure that inheritance may be disabled on simple classes and that + _cls and _types will not be used. + """ + class Animal(Document): + meta = {'allow_inheritance': False} + name = StringField() + + Animal.drop_collection() + + def create_dog_class(): + class Dog(Animal): + pass + self.assertRaises(ValueError, create_dog_class) + + # Check that _cls etc aren't present on simple documents + dog = Animal(name='dog') + dog.save() + collection = self.db[Animal._meta['collection']] + obj = collection.find_one() + self.assertFalse('_cls' in obj) + self.assertFalse('_types' in obj) + + Animal.drop_collection() + + def create_employee_class(): + class Employee(self.Person): + meta = {'allow_inheritance': False} + self.assertRaises(ValueError, create_employee_class) + def test_creation(self): """Ensure that document may be created using keyword arguments. """ diff --git a/tests/queryset.py b/tests/queryset.py index 39b14fc1..d40e73b0 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -137,23 +137,23 @@ class QuerySetTest(unittest.TestCase): self.Person.objects.delete() self.assertEqual(self.Person.objects.count(), 0) - def test_sort(self): - """Ensure that QuerySets may be sorted. + def test_order_by(self): + """Ensure that QuerySets may be ordered. """ self.Person(name="User A", age=20).save() self.Person(name="User B", age=40).save() self.Person(name="User C", age=30).save() - names = [p.name for p in self.Person.objects.sort('-age')] + names = [p.name for p in self.Person.objects.order_by('-age')] self.assertEqual(names, ['User B', 'User C', 'User A']) - names = [p.name for p in self.Person.objects.sort('+age')] + names = [p.name for p in self.Person.objects.order_by('+age')] self.assertEqual(names, ['User A', 'User C', 'User B']) - names = [p.name for p in self.Person.objects.sort('age')] + names = [p.name for p in self.Person.objects.order_by('age')] self.assertEqual(names, ['User A', 'User C', 'User B']) - ages = [p.age for p in self.Person.objects.sort('-name')] + ages = [p.age for p in self.Person.objects.order_by('-name')] self.assertEqual(ages, [30, 40, 20]) def tearDown(self): From 8a646f0f4c8b31fda11952c61b801e6f450ea2a9 Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Sat, 19 Dec 2009 16:04:05 +0000 Subject: [PATCH 8/9] Added API Reference to docs --- .gitignore | 1 + docs/Makefile | 2 +- .../nature/static/nature.css_t | 0 .../nature/static/pygments.css | 0 docs/{.themes => _themes}/nature/theme.conf | 0 docs/apireference.rst | 46 +++++++++++++++++++ docs/conf.py | 10 ++-- docs/index.rst | 3 +- mongoengine/document.py | 38 ++++++++++++--- mongoengine/fields.py | 2 +- mongoengine/queryset.py | 21 +++++---- 11 files changed, 101 insertions(+), 22 deletions(-) rename docs/{.themes => _themes}/nature/static/nature.css_t (100%) rename docs/{.themes => _themes}/nature/static/pygments.css (100%) rename docs/{.themes => _themes}/nature/theme.conf (100%) create mode 100644 docs/apireference.rst diff --git a/.gitignore b/.gitignore index e191be82..95ad521c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc .*.swp docs/.build +docs/_build diff --git a/docs/Makefile b/docs/Makefile index 66cd648f..76eb910c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,7 +5,7 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = -BUILDDIR = .build +BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 diff --git a/docs/.themes/nature/static/nature.css_t b/docs/_themes/nature/static/nature.css_t similarity index 100% rename from docs/.themes/nature/static/nature.css_t rename to docs/_themes/nature/static/nature.css_t diff --git a/docs/.themes/nature/static/pygments.css b/docs/_themes/nature/static/pygments.css similarity index 100% rename from docs/.themes/nature/static/pygments.css rename to docs/_themes/nature/static/pygments.css diff --git a/docs/.themes/nature/theme.conf b/docs/_themes/nature/theme.conf similarity index 100% rename from docs/.themes/nature/theme.conf rename to docs/_themes/nature/theme.conf diff --git a/docs/apireference.rst b/docs/apireference.rst new file mode 100644 index 00000000..9ec4321a --- /dev/null +++ b/docs/apireference.rst @@ -0,0 +1,46 @@ +API Reference +============= + +Connecting +---------- + +.. autofunction:: mongoengine.connect + +Documents +--------- + +.. autoclass:: mongoengine.Document + :members: + + .. attribute:: objects + + A :class:`~mongoengine.queryset.QuerySet` object that is created lazily + on access. + +.. autoclass:: mongoengine.EmbeddedDocument + :members: + +Querying +-------- + +.. autoclass:: mongoengine.queryset.QuerySet + :members: + +Fields +------ + +.. autoclass:: mongoengine.StringField + +.. autoclass:: mongoengine.IntField + +.. autoclass:: mongoengine.FloatField + +.. autoclass:: mongoengine.DateTimeField + +.. autoclass:: mongoengine.EmbeddedDocumentField + +.. autoclass:: mongoengine.ListField + +.. autoclass:: mongoengine.ObjectIdField + +.. autoclass:: mongoengine.ReferenceField diff --git a/docs/conf.py b/docs/conf.py index 5b29b371..be7d3f5d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,13 +16,13 @@ import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +sys.path.append(os.path.abspath('..')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] @@ -64,7 +64,7 @@ release = '0.1' # List of directories, relative to source directory, that shouldn't be searched # for source files. -exclude_trees = ['.build'] +exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None @@ -99,7 +99,7 @@ html_theme = 'nature' #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['.themes'] +html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". @@ -120,7 +120,7 @@ html_theme_path = ['.themes'] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['.static'] +html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. diff --git a/docs/index.rst b/docs/index.rst index 5fab07a6..e6a2bde6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to MongoEngine's documentation! +MongoEngine User Documentation ======================================= Contents: @@ -12,6 +12,7 @@ Contents: :maxdepth: 2 tutorial.rst + apireference.rst Indices and tables ================== diff --git a/mongoengine/document.py b/mongoengine/document.py index 99c98cd4..a4b78619 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -6,32 +6,58 @@ __all__ = ['Document', 'EmbeddedDocument'] class EmbeddedDocument(BaseDocument): + """A :class:`~mongoengine.Document` that isn't stored in its own + collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as + fields on :class:`~mongoengine.Document`\ s through the + :class:`~mongoengine.EmbeddedDocumentField` field type. + """ __metaclass__ = DocumentMetaclass class Document(BaseDocument): + """The base class used for defining the structure and properties of + collections of documents stored in MongoDB. Inherit from this class, and + add fields as class attributes to define a document's structure. + Individual documents may then be created by making instances of the + :class:`~mongoengine.Document` subclass. + + By default, the MongoDB collection used to store documents created using a + :class:`~mongoengine.Document` subclass will be the name of the subclass + converted to lowercase. A different collection may be specified by + providing :attr:`collection` to the :attr:`meta` dictionary in the class + definition. + + A :class:`~mongoengine.Document` subclass may be itself subclassed, to + create a specialised version of the document that will be stored in the + same collection. To facilitate this behaviour, `_cls` and `_types` + fields are added to documents (hidden though the MongoEngine interface + though). To disable this behaviour and remove the dependence on the + presence of `_cls` and `_types`, set :attr:`allow_inheritance` to + ``False`` in the :attr:`meta` dictionary. + """ __metaclass__ = TopLevelDocumentMetaclass def save(self): - """Save the document to the database. If the document already exists, - it will be updated, otherwise it will be created. + """Save the :class:`~mongoengine.Document` to the database. If the + document already exists, it will be updated, otherwise it will be + created. """ object_id = self.objects._collection.save(self.to_mongo()) self.id = object_id def delete(self): - """Delete the document from the database. This will only take effect - if the document has been previously saved. + """Delete the :class:`~mongoengine.Document` from the database. This + will only take effect if the document has been previously saved. """ object_id = self._fields['id'].to_mongo(self.id) self.__class__.objects(_id=object_id).delete() @classmethod def drop_collection(cls): - """Drops the entire collection associated with this Document type from - the database. + """Drops the entire collection associated with this + :class:`~mongoengine.Document` type from the database. """ db = _get_db() db.drop_collection(cls._meta['collection']) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 1625a582..e97aadb2 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -87,7 +87,7 @@ class DateTimeField(BaseField): class EmbeddedDocumentField(BaseField): """An embedded document field. Only valid values are subclasses of - EmbeddedDocument. + :class:`~mongoengine.EmbeddedDocument`. """ def __init__(self, document, **kwargs): diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 4f406b44..3678b099 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -5,7 +5,7 @@ import pymongo class QuerySet(object): """A set of results returned from a query. Wraps a MongoDB cursor, - providing Document objects as the results. + providing :class:`~mongoengine.Document` objects as the results. """ def __init__(self, document, collection): @@ -32,7 +32,8 @@ class QuerySet(object): return self def __call__(self, **query): - """Filter the selected documents by calling the queryset with a query. + """Filter the selected documents by calling the + :class:`~mongoengine.QuerySet` with a query. """ self._query.update(QuerySet._transform_query(**query)) return self @@ -76,7 +77,7 @@ class QuerySet(object): return result def with_id(self, object_id): - """Retrieve the object matching the _id provided. + """Retrieve the object matching the id provided. """ if not isinstance(object_id, pymongo.objectid.ObjectId): object_id = pymongo.objectid.ObjectId(object_id) @@ -87,7 +88,7 @@ class QuerySet(object): return result def next(self): - """Wrap the result in a Document object. + """Wrap the result in a :class:`~mongoengine.Document` object. """ return self._document._from_son(self._cursor.next()) @@ -97,21 +98,22 @@ class QuerySet(object): return self._cursor.count() def limit(self, n): - """Limit the number of returned documents to. + """Limit the number of returned documents to `n`. """ self._cursor.limit(n) # Return self to allow chaining return self def skip(self, n): - """Skip n documents before returning the results. + """Skip `n` documents before returning the results. """ self._cursor.skip(n) return self def order_by(self, *keys): - """Order the QuerySet by the keys. The order may be specified by - prepending each of the keys by a + or a -. Ascending order is assumed. + """Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The + order may be specified by prepending each of the keys by a + or a -. + Ascending order is assumed. """ key_list = [] for key in keys: @@ -126,6 +128,9 @@ class QuerySet(object): return self def explain(self, format=False): + """Return an explain plan record for the + :class:`~mongoengine.queryset.QuerySet`\ 's cursor. + """ plan = self._cursor.explain() if format: import pprint From bb23cdb03829e2b6d53c857a915e7d194b0bb28b Mon Sep 17 00:00:00 2001 From: Harry Marr Date: Sat, 19 Dec 2009 17:41:28 +0000 Subject: [PATCH 9/9] Added array-style index/slicing syntax to QuerySet --- mongoengine/queryset.py | 18 ++++++++++++++++-- tests/queryset.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 3678b099..034b8476 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -98,18 +98,32 @@ class QuerySet(object): return self._cursor.count() def limit(self, n): - """Limit the number of returned documents to `n`. + """Limit the number of returned documents to `n`. This may also be + achieved using array-slicing syntax (e.g. ``User.objects[:5]``). """ self._cursor.limit(n) # Return self to allow chaining return self def skip(self, n): - """Skip `n` documents before returning the results. + """Skip `n` documents before returning the results. This may also be + achieved using array-slicing syntax (e.g. ``User.objects[5:]``). """ self._cursor.skip(n) return self + def __getitem__(self, key): + """Support skip and limit using getitem and slicing syntax. + """ + # Slice provided + if isinstance(key, slice): + self._cursor_obj = self._cursor[key] + # Allow further QuerySet modifications to be performed + return self + # Integer index provided + elif isinstance(key, int): + return self._document._from_son(self._cursor[key]) + def order_by(self, *keys): """Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The order may be specified by prepending each of the keys by a + or a -. diff --git a/tests/queryset.py b/tests/queryset.py index d40e73b0..b73bab17 100644 --- a/tests/queryset.py +++ b/tests/queryset.py @@ -77,6 +77,26 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(len(people), 1) self.assertEqual(people[0].name, 'User B') + person3 = self.Person(name="User C", age=40) + person3.save() + + # Test slice limit + people = list(self.Person.objects[:2]) + self.assertEqual(len(people), 2) + self.assertEqual(people[0].name, 'User A') + self.assertEqual(people[1].name, 'User B') + + # Test slice skip + people = list(self.Person.objects[1:]) + self.assertEqual(len(people), 2) + self.assertEqual(people[0].name, 'User B') + self.assertEqual(people[1].name, 'User C') + + # Test slice limit and skip + people = list(self.Person.objects[1:2]) + self.assertEqual(len(people), 1) + self.assertEqual(people[0].name, 'User B') + def test_find_one(self): """Ensure that a query using find_one returns a valid result. """ @@ -97,6 +117,15 @@ class QuerySetTest(unittest.TestCase): person = self.Person.objects(age__lt=30).first() self.assertEqual(person.name, "User A") + + # Use array syntax + person = self.Person.objects[0] + self.assertEqual(person.name, "User A") + + person = self.Person.objects[1] + self.assertEqual(person.name, "User B") + + self.assertRaises(IndexError, self.Person.objects.__getitem__, 2) # Find a document using just the object id person = self.Person.objects.with_id(person1.id)