MongoEngine / mongoengine

A Python Object-Document-Mapper for working with MongoDB
http://mongoengine.org
MIT License
4.22k stars 1.23k forks source link

Reverse delete rule not allowed for EmbeddedDocuments #1592

Open Vayel opened 7 years ago

Vayel commented 7 years ago

Hi!

I have the following models:

class A(Document):
    name = StringField()

class Embedded(EmbeddedDocument):
    ref = ReferenceField('A', reverse_delete_rule=DENY)

class B(Document):
    children = MapField(EmbeddedDocumentField('Embedded'))

But I obtain the error:

File "/home/.../mongoengine/base/metaclasses.py", line 210, in __new__
    raise InvalidDocumentError(msg)
mongoengine.errors.InvalidDocumentError: Reverse delete rules are not supported for EmbeddedDocuments (field: ref)

What is it due to? Is there a way to bypass this constraint?

Thanks!

vedavidhbudimuri commented 7 years ago

Any update on this?

@Vayel have you got a workaround for this?

erdenezul commented 7 years ago

Is there any specific reason to use reverse_delete_rule in EmbeddedDocument? EmbeddedDocument is not saved in collection.

vedavidhbudimuri commented 7 years ago

As the default reverse_delete_rule DO_NOTHING for reference field. Is there a way to enable cascading on delete for EmbeddedDocument? (I do get that EmbeddedDocument not saved in collection, but just wanna know if anything like cascading on delete could be done in EmbeddedDocuments as well.)

electricworry commented 6 years ago

I would similarly like to see this available. If I have, say, multiple Comment EmbeddedDocuments contained as a list in a BlogPost Document,

class User(Document):
    name = StringField()

class Comment(EmbeddedDocument):
    content = StringField()
    user = ReferenceField(User, reverse_delete_rule=NULLIFY)

class BlogPost(Document):
    subject = StringField()
    content = StringField()
    comments = ListField(EmbeddedDocumentField(Comment))

The above would allow the user associated with a comment to be deleted and for all of the references on BlogPosts to be removed cleanly. As it stands at the moment (without the reverse_delete_rule) when the user gets deleted then access to BlogPost.comments[x].user raises an exception:

mongoengine.errors.DoesNotExist: Trying to dereference unknown document DBRef('user', ObjectId('...'))

Is there any workaround for this? Or does much work need to be done to allow ReferencesFields on EmbeddedDocument to support reverse_delete_rule?

Cheers.

ribas14 commented 5 years ago

Any news on this?

Killerjay666 commented 5 years ago

Hey, is there any update on this, or has anyone found a workaround? This is a pretty crucial feature to maintain database integrity in applications with nested document fields (which I imagine is common). Currently it seems the only option is to write a cascade rule myself, or change all of our EmbeddedDocument containing Referencefields into their own collections (I do not understand why this is necessary, is the reason technical or am I using EmbeddedDocuments improperly?)

DoubleSentinel commented 4 years ago

Hello all, I came looking for a solution as well for this case and it seems no one has taken the time to change/update this behaviour.

The workaround I propose is the following; the context is using mongoengine with Flask-Admin for the CRUD handling.

Here are my models:

class Member(Document):
    first_name = StringField(required=True)
    last_name = StringField(required=True, unique_with='first_name')
    default_role = StringField()

class ProjectRole(EmbeddedDocument):
    member = ReferenceField(Member,
                            required=True,)
    role = StringField()

class Project(Document):
    members = ListField(EmbeddedDocumentField(ProjectRole, required=True))
    title = StringField(unique=True, required=True)

The workaround I propose is to make a query upon deletion of a Member that scours the Projects in search of the projects containing the currently-being-deleted member and deleting them from those projects' ListField of EmbeddedDocumentField as show on the Flask-Admin view here:

class MemberView(ModelView):
    def on_model_delete(self, model):
        # find the projects that contain this member and delete the member from
        # the ProjectRole ListField (see models for clarification)
        Project.objects().update(pull__members__member=model)

The on_model_delete function is overwritten from the Flask-Admin framework, but you should be able to handle it in the same way from a standard POST form that deletes the Member instance.

I hope this helps. Cheers

muuvmuuv commented 4 years ago

Sad that this isn't get that much attention since 2017. This is something that is required for like chats or as eleectric mentioned for blog posts with comments. If no one is going to take on this of the maintainers or it is by design, please provide a proper solution to handle those cases. It is really unclear for me how to create a list of pre-defined dict structure without the use of embedded documents.

muuvmuuv commented 4 years ago

Tried removing the part where it throws the error (metaclasses.py) haha, but it seems it does not get the ref. We should tell MongoEngine here to respect it as an embedded document and ignore it.

  File "/usr/local/lib/python3.7/site-packages/mongoengine/document.py", line 618, in delete
    **self._object_key).delete(write_concern=write_concern, _from_doc_delete=True)
  File "/usr/local/lib/python3.7/site-packages/mongoengine/queryset/base.py", line 467, in delete
    if doc._collection == document_cls._collection:
AttributeError: type object 'ChatMember' has no attribute '_collection'
ameenalakhras commented 3 years ago

stuck on the same issue here. i would love to spend some time trying to solve it. @muuvmuuv do you still work with mongoengine ? if you do, do you know what part of the package code does the checking for the ReferenceField, I'll pretty much new to solving issues on open source content so any help would be great !

shuheng-liu commented 3 years ago

I'm new to mongoengine an got the same issue here. I think it's important to have reverse_delete_rule for ReferenceFields in EmbeddedDocuments, and most of the default rules make sense.

  1. DO_NOTHING: default behavior
  2. DENY: avoid invalid reference in embedded documents
  3. PULL: remove invalid references from a list in an embedded document
  4. NULLIFY: nullify invalid references in an embedded document
  5. The only option that might not make sense is CASCADE, because embedded documents are not saved in collection. But one could still expect a cascaded process where the parent document of the EmbeddedDocument is deleted from its collection.
ghost commented 3 years ago

I have the following model definitions:

class Movie(db.Document):
    _id = db.SequenceField(primary_key=True)
    name = db.StringField(required=True)

class Rating(db.EmbeddedDocument):
    movieId = db.ReferenceField(Movie, reverse_delete_rule=db.CASCADE)
    rating = db.IntField()

class User(db.Document):
    _id = db.IntField(primary_key=True)
    rating = db.EmbeddedDocumentListField(Rating)

Rating is a EmbeddedDocument, I want to add a reverse_delete_rule so that when a movie is deleted, a user's rating for that movie is deleted as well.

I think this is a really useful feature.

BarryFourie commented 3 years ago

Ill second that. I dont want to switch mongo engine for something else because of this..

Avarow commented 5 months ago

Still stuck on the same issue