diff --git a/mongoengine/fields.py b/mongoengine/fields.py index bba8f0b5..8bb33a92 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -182,7 +182,7 @@ class FloatField(BaseField): if isinstance(value, int): value = float(value) if not isinstance(value, float): - self.error('FoatField only accepts float values') + self.error('FloatField only accepts float values') if self.min_value is not None and value < self.min_value: self.error('Float value is too small') diff --git a/mongoengine/queryset.py b/mongoengine/queryset.py index 1fb3eda7..8370d333 100644 --- a/mongoengine/queryset.py +++ b/mongoengine/queryset.py @@ -1314,7 +1314,9 @@ class QuerySet(object): document_cls, field_name = rule_entry rule = doc._meta['delete_rules'][rule_entry] if rule == CASCADE: - document_cls.objects(**{field_name + '__in': self}).delete(safe=safe) + ref_q = document_cls.objects(**{field_name + '__in': self}) + if doc != document_cls 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( safe_update=safe, diff --git a/tests/test_queryset.py b/tests/test_queryset.py index 3b662489..c768ade9 100644 --- a/tests/test_queryset.py +++ b/tests/test_queryset.py @@ -1344,6 +1344,37 @@ class QuerySetTest(unittest.TestCase): self.Person.objects(name='Test User').delete() self.assertEqual(1, BlogPost.objects.count()) + def test_reverse_delete_rule_cascade_self_referencing(self): + """Ensure self-referencing CASCADE deletes do not result in infinite loop + """ + class Category(Document): + name = StringField() + parent = ReferenceField('self', reverse_delete_rule=CASCADE) + + num_children = 3 + base = Category(name='Root') + base.save() + + # Create a simple parent-child tree + for i in range(num_children): + child_name = 'Child-%i' % i + child = Category(name=child_name, parent=base) + child.save() + + for i in range(num_children): + child_child_name = 'Child-Child-%i' % i + child_child = Category(name=child_child_name, parent=child) + child_child.save() + + tree_size = 1 + num_children + (num_children * num_children) + self.assertEquals(tree_size, Category.objects.count()) + self.assertEquals(num_children, Category.objects(parent=base).count()) + + # The delete should effectively wipe out the Category collection + # without resulting in infinite parent-child cascade recursion + base.delete() + self.assertEquals(0, Category.objects.count()) + def test_reverse_delete_rule_nullify(self): """Ensure nullification of references to deleted documents. """