Permalink
Browse files

Cleaned up the metaclasses for documents

Refactored and clarified intent and
tidied up
  • Loading branch information...
1 parent 9e67941 commit 19da2288550c3a7bb7b7df951b57429cb6939648 @rozza rozza committed Aug 17, 2012
Showing with 410 additions and 300 deletions.
  1. +3 −0 docs/changelog.rst
  2. +343 −243 mongoengine/base.py
  3. +4 −1 mongoengine/document.py
  4. +0 −1 mongoengine/fields.py
  5. +21 −12 mongoengine/queryset.py
  6. +2 −2 setup.cfg
  7. +2 −2 tests/test_django.py
  8. +33 −39 tests/test_document.py
  9. +2 −0 tests/test_queryset.py
View
@@ -4,6 +4,9 @@ Changelog
Changes in 0.7.X
=================
+- Embedded Documents dont care about inheritance
+
+
- Use weakref proxies in base lists / dicts (MongoEngine/mongoengine#74)
- Improved queryset filtering (hmarr/mongoengine#554)
- Fixed Dynamic Documents and Embedded Documents (hmarr/mongoengine#561)
View
Oops, something went wrong.
@@ -360,7 +360,9 @@ def register_delete_rule(cls, document_cls, field_name, rule):
"""This method registers the delete rules to apply when removing this
object.
"""
- cls._meta['delete_rules'][(document_cls, field_name)] = rule
+ delete_rules = cls._meta.get('delete_rules') or {}
+ delete_rules[(document_cls, field_name)] = rule
+ cls._meta['delete_rules'] = delete_rules
@classmethod
def drop_collection(cls):
@@ -392,6 +394,7 @@ class DynamicDocument(Document):
__metaclass__ = TopLevelDocumentMetaclass
_dynamic = True
+ meta = {'abstract': True}
def __delattr__(self, *args, **kwargs):
"""Deletes the attribute by setting to None and allowing _delta to unset
@@ -747,7 +747,6 @@ def to_mongo(self, document):
def prepare_query_value(self, op, value):
if value is None:
return None
-
return self.to_mongo(value)
def validate(self, value):
@@ -352,7 +352,7 @@ def __init__(self, document, collection):
# If inheritance is allowed, only return instances and instances of
# subclasses of the class being used
- if document._meta.get('allow_inheritance'):
+ if document._meta.get('allow_inheritance') != False:
self._initial_query = {'_types': self._document._class_name}
self._loaded_fields = QueryFieldList(always_include=['_cls'])
self._cursor_obj = None
@@ -442,14 +442,15 @@ def _ensure_indexes(self):
"""
background = self._document._meta.get('index_background', False)
drop_dups = self._document._meta.get('index_drop_dups', False)
- index_opts = self._document._meta.get('index_opts', {})
+ index_opts = self._document._meta.get('index_opts') or {}
index_types = self._document._meta.get('index_types', True)
# determine if an index which we are creating includes
# _type as its first field; if so, we can avoid creating
# an extra index on _type, as mongodb will use the existing
# index to service queries against _type
types_indexed = False
+
def includes_types(fields):
first_field = None
if len(fields):
@@ -466,8 +467,8 @@ def includes_types(fields):
background=background, drop_dups=drop_dups, **index_opts)
# Ensure document-defined indexes are created
- if self._document._meta['indexes']:
- for spec in self._document._meta['indexes']:
+ if self._document._meta['index_specs']:
+ for spec in self._document._meta['index_specs']:
types_indexed = types_indexed or includes_types(spec['fields'])
opts = index_opts.copy()
opts['unique'] = spec.get('unique', False)
@@ -498,7 +499,10 @@ def _build_index_spec(cls, doc_cls, spec):
index_list = []
direction = None
- use_types = doc_cls._meta.get('allow_inheritance', True)
+
+ allow_inheritance = doc_cls._meta.get('allow_inheritance') != False
+ use_types = allow_inheritance
+
for key in spec['fields']:
# Get ASCENDING direction from +, DESCENDING from -, and GEO2D from *
direction = pymongo.ASCENDING
@@ -516,7 +520,8 @@ def _build_index_spec(cls, doc_cls, spec):
key = '_id'
else:
fields = QuerySet._lookup_field(doc_cls, parts)
- parts = [field if field == '_id' else field.db_field for field in fields]
+ parts = [field if field == '_id' else field.db_field
+ for field in fields]
key = '.'.join(parts)
index_list.append((key, direction))
@@ -530,8 +535,9 @@ def _build_index_spec(cls, doc_cls, spec):
# If _types is being used, prepend it to every specified index
index_types = doc_cls._meta.get('index_types', True)
- allow_inheritance = doc_cls._meta.get('allow_inheritance')
- if spec.get('types', index_types) and allow_inheritance and use_types and direction is not pymongo.GEO2D:
+
+ if (spec.get('types', index_types) and allow_inheritance and use_types
+ and direction is not pymongo.GEO2D):
index_list.insert(0, ('_types', 1))
spec['fields'] = index_list
@@ -1329,22 +1335,25 @@ def delete(self, safe=False):
"""
doc = self._document
+ delete_rules = doc._meta.get('delete_rules') or {}
# Check for DENY rules before actually deleting/nullifying any other
# references
- for rule_entry in doc._meta['delete_rules']:
+ for rule_entry in delete_rules:
document_cls, field_name = rule_entry
rule = doc._meta['delete_rules'][rule_entry]
if rule == DENY and document_cls.objects(**{field_name + '__in': self}).count() > 0:
msg = u'Could not delete document (at least %s.%s refers to it)' % \
(document_cls.__name__, field_name)
raise OperationError(msg)
- for rule_entry in doc._meta['delete_rules']:
+ for rule_entry in delete_rules:
document_cls, field_name = rule_entry
rule = doc._meta['delete_rules'][rule_entry]
if rule == CASCADE:
ref_q = document_cls.objects(**{field_name + '__in': self})
- if doc != document_cls or (doc == document_cls and ref_q.count() > 0):
+ ref_q_count = ref_q.count()
+ if (doc != document_cls and ref_q_count > 0
+ or (doc == document_cls and ref_q_count > 0)):
ref_q.delete(safe=safe)
elif rule == NULLIFY:
document_cls.objects(**{field_name + '__in': self}).update(
@@ -1915,7 +1924,7 @@ def __get__(self, instance, owner):
return self
# owner is the document that contains the QuerySetManager
- queryset_class = owner._meta['queryset_class'] or QuerySet
+ queryset_class = owner._meta.get('queryset_class') or QuerySet
queryset = queryset_class(owner, owner._get_collection())
if self.get_queryset:
arg_count = self.get_queryset.func_code.co_argcount
View
@@ -1,5 +1,5 @@
[nosetests]
-verbosity = 2
+verbosity = 3
detailed-errors = 1
#with-coverage = 1
#cover-erase = 1
@@ -8,4 +8,4 @@ detailed-errors = 1
#cover-package = mongoengine
py3where = build
where = tests
-#tests = test_bugfix.py
+#tests = test_bugfix.py
@@ -18,8 +18,8 @@
from mongoengine.django.sessions import SessionStore, MongoSession
except Exception, err:
if PY3:
- SessionTestsMixin = type #dummy value so no error
- SessionStore = None #dummy value so no error
+ SessionTestsMixin = type # dummy value so no error
+ SessionStore = None # dummy value so no error
else:
raise err
@@ -387,19 +387,6 @@ class Employee(self.Person):
meta = {'allow_inheritance': False}
self.assertRaises(ValueError, create_employee_class)
- # Test the same for embedded documents
- class Comment(EmbeddedDocument):
- content = StringField()
- meta = {'allow_inheritance': False}
-
- def create_special_comment():
- class SpecialComment(Comment):
- pass
- self.assertRaises(ValueError, create_special_comment)
-
- comment = Comment(content='test')
- self.assertFalse('_cls' in comment.to_mongo())
- self.assertFalse('_types' in comment.to_mongo())
def test_allow_inheritance_abstract_document(self):
"""Ensure that abstract documents can set inheritance rules and that
@@ -491,9 +478,20 @@ def test_abstract_documents(self):
"""Ensure that a document superclass can be marked as abstract
thereby not using it as the name for the collection."""
+ defaults = {'index_background': True,
+ 'index_drop_dups': True,
+ 'index_opts': {'hello': 'world'},
+ 'allow_inheritance': True,
+ 'queryset_class': 'QuerySet',
+ 'db_alias': 'myDB',
+ 'shard_key': ('hello', 'world')}
+
+ meta_settings = {'abstract': True}
+ meta_settings.update(defaults)
+
class Animal(Document):
name = StringField()
- meta = {'abstract': True}
+ meta = meta_settings
class Fish(Animal): pass
class Guppy(Fish): pass
@@ -502,6 +500,10 @@ class Mammal(Animal):
meta = {'abstract': True}
class Human(Mammal): pass
+ for k, v in defaults.iteritems():
+ for cls in [Animal, Fish, Guppy]:
+ self.assertEqual(cls._meta[k], v)
+
self.assertFalse('collection' in Animal._meta)
self.assertFalse('collection' in Mammal._meta)
@@ -564,6 +566,7 @@ def test_inherited_collections(self):
class Drink(Document):
name = StringField()
+ meta = {'allow_inheritance': True}
class Drinker(Document):
drink = GenericReferenceField()
@@ -799,7 +802,6 @@ class UserBase(Document):
user_guid = StringField(required=True)
-
class Person(UserBase):
meta = {
'indexes': ['name'],
@@ -1325,7 +1327,6 @@ class Comment(EmbeddedDocument):
self.assertTrue('content' in Comment._fields)
self.assertFalse('id' in Comment._fields)
- self.assertFalse('collection' in Comment._meta)
def test_embedded_document_validation(self):
"""Ensure that embedded documents may be validated.
@@ -2504,32 +2505,24 @@ class Employee(self.Person):
def test_mixins_dont_add_to_types(self):
- class Bob(Document): name = StringField()
-
- Bob.drop_collection()
-
- p = Bob(name="Rozza")
- p.save()
- Bob.drop_collection()
+ class Mixin(object):
+ name = StringField()
class Person(Document, Mixin):
pass
Person.drop_collection()
- p = Person(name="Rozza")
- p.save()
- self.assertEqual(p._fields.keys(), ['name', 'id'])
+ self.assertEqual(Person._fields.keys(), ['name', 'id'])
+
+ Person(name="Rozza").save()
collection = self.db[Person._get_collection_name()]
obj = collection.find_one()
self.assertEqual(obj['_cls'], 'Person')
self.assertEqual(obj['_types'], ['Person'])
-
-
self.assertEqual(Person.objects.count(), 1)
- rozza = Person.objects.get(name="Rozza")
Person.drop_collection()
@@ -2668,35 +2661,38 @@ class BlogPost(Document):
self.assertEqual(len(BlogPost.objects), 0)
def test_reverse_delete_rule_cascade_and_nullify_complex_field(self):
- """Ensure that a referenced document is also deleted upon deletion.
+ """Ensure that a referenced document is also deleted upon deletion for
+ complex fields.
"""
- class BlogPost(Document):
+ class BlogPost2(Document):
content = StringField()
authors = ListField(ReferenceField(self.Person, reverse_delete_rule=CASCADE))
reviewers = ListField(ReferenceField(self.Person, reverse_delete_rule=NULLIFY))
self.Person.drop_collection()
- BlogPost.drop_collection()
+
+ BlogPost2.drop_collection()
author = self.Person(name='Test User')
author.save()
reviewer = self.Person(name='Re Viewer')
reviewer.save()
- post = BlogPost(content= 'Watched some TV')
+ post = BlogPost2(content='Watched some TV')
post.authors = [author]
post.reviewers = [reviewer]
post.save()
+ # Deleting the reviewer should have no effect on the BlogPost2
reviewer.delete()
- self.assertEqual(len(BlogPost.objects), 1) # No effect on the BlogPost
- self.assertEqual(BlogPost.objects.get().reviewers, [])
+ self.assertEqual(len(BlogPost2.objects), 1)
+ self.assertEqual(BlogPost2.objects.get().reviewers, [])
# Delete the Person, which should lead to deletion of the BlogPost, too
author.delete()
- self.assertEqual(len(BlogPost.objects), 0)
+ self.assertEqual(len(BlogPost2.objects), 0)
def test_two_way_reverse_delete_rule(self):
"""Ensure that Bi-Directional relationships work with
@@ -3074,7 +3070,7 @@ class B(A):
self.assertEqual('testdb-1', B._meta.get('db_alias'))
def test_db_ref_usage(self):
- """ DB Ref usage in __raw__ queries """
+ """ DB Ref usage in dict_fields"""
class User(Document):
name = StringField()
@@ -3216,7 +3212,6 @@ class Doc(DynamicDocument):
one = Doc.objects.filter(**{'hello world': 1}).count()
self.assertEqual(1, one)
-
def test_fields_rewrite(self):
class BasePerson(Document):
name = StringField()
@@ -3226,7 +3221,6 @@ class BasePerson(Document):
class Person(BasePerson):
name = StringField(required=True)
-
p = Person(age=15)
self.assertRaises(ValidationError, p.validate)
@@ -24,6 +24,8 @@ class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
+
+ Person.drop_collection()
self.Person = Person
def test_initialisation(self):

0 comments on commit 19da228

Please sign in to comment.