Skip to content

Instantly share code, notes, and snippets.

@nvie
Forked from hmarr/gist:727195
Created December 3, 2010 17:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nvie/727282 to your computer and use it in GitHub Desktop.
Save nvie/727282 to your computer and use it in GitHub Desktop.
from mongoengine import *
from mongoengine import DENY, CASCADE, NULLIFY, DO_NOTHING
from mongoengine import DeleteForbidden # or something equivalent
from mongoengine.queryset import QuerySet
class Post(Document):
title = StringField()
@classmethod
def register_delete_rule(cls, document_cls, field_name, rule):
"""
This method registers the delete rules to apply when removing this
object. This could go into the Document class.
"""
if rule == DO_NOTHING:
# This is the default behaviour already
return
# I don't know if this is the correct way of storing meta-information on
# the given Document class, but the essence is that the calling
# document_cls/field_name combination (in this case Comment.post)
# should be registered to be CASCADED
cls.meta['delete_rules'][(document_cls, field_name)] = rule
def delete(self, safe=False):
# Check first. If *any* delete_rule says DENY, we may not
# delete/nullify any of the others, too
for key, val in self.meta['delete_rules'].items():
if val == DENY:
document_cls, field_name = key
raise DeleteForbidden('Delete forbidden because at least the '
'following documents still refer to it: %s.%s' % \
(document_cls, field_name))
for document_cls, field_name in self.meta['delete_rules']:
rule = self.meta['delete_rules'][(document_cls, field_name)]
if rule == CASCADE:
# This line is equivalent to your
# Comment.objects(post=self.id).delete(safe=safe)
document_cls.objects(**{field_name: self.id}).delete(safe=safe)
elif rule == NULLIFY:
obj = document_cls.objects(**{field_name: self.id})
obj.field_name = None # See #1
obj.save() # See #2
#
# Notes:
# 1. This yields a key error for "field_name". Don't know the
# correct way of getting this right now from the top of my
# head
# 2. Probably not atomic this way, don't know the syntax right
# now
#
super(Post, self).delete(safe=safe)
class Comment(Document):
# The "delete_rule" attribute here would have to store the "backwards"
# relation in Post's meta info, for example by setting
#
# Post.register_delete_rule(
# cls, # In this case Comment
# attr_that_this_is_assigned_to, # The field name
# delete_rule # In this case, CASCADE
# )
post = ReferenceField(Post, delete_rule=mongoengine.CASCADE)
@hmarr
Copy link

hmarr commented Dec 3, 2010

Really like this - definitely want it in MongoEngine core.

The KeyError on line 44 should be fixable using setattr(self, field_name, None).

WRT atomicity - yeah MongoDB isn't great on that in general. The actual delete operations (i.e. one loop iteration) can optionally be atomic (http://www.mongodb.org/display/DOCS/Removing) but I can't see a way for the operation as a whole to be as there is no transaction support. Shouldn't be a huge issue for most cases though as the super(Post, self).delete call comes at the end...

I might write up some tests laster on and include this in MongoEngine for the next release (feel free to fork it and add it to the Document class if that'd be more convenient).

Cheers
H

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment