From ad0e4335e53c7f311784c5d9485ebc1565f6bec1 Mon Sep 17 00:00:00 2001 From: StoneMoe Date: Fri, 4 Dec 2020 19:01:09 +0800 Subject: [PATCH 1/2] Fix QuerySet.delete() won't delete GridFS files --- AUTHORS | 1 + mongoengine/fields.py | 3 ++ mongoengine/queryset/base.py | 10 +++++ tests/fields/test_file_field.py | 68 +++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/AUTHORS b/AUTHORS index 1cf7d78a6..dc1df9a79 100644 --- a/AUTHORS +++ b/AUTHORS @@ -259,3 +259,4 @@ that much better: * Agustin Barto (https://github.com/abarto) * Stankiewicz Mateusz (https://github.com/mas15) * Felix Schultheiß (https://github.com/felix-smashdocs) + * Lake Chan (https://github.com/StoneMoe) \ No newline at end of file diff --git a/mongoengine/fields.py b/mongoengine/fields.py index db292ba9f..2ce0c92ab 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1774,6 +1774,9 @@ def __eq__(self, other): def __ne__(self, other): return not self == other + def __hash__(self): + return int("{}{}".format(self.collection_name, self.grid_id).encode().hex(), 16) + @property def fs(self): if not self._fs: diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 1021bf5eb..6fbe1c233 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -517,7 +517,17 @@ def delete(self, write_concern=None, _from_doc_delete=False, cascade_refs=None): ) with set_write_concern(queryset._collection, write_concern) as collection: + gridfs_refs = set() + for name, field in doc._fields.items(): + if field.__class__.__name__ == "FileField": + gridfs_refs.update(queryset.scalar(name)) + if field.__class__.__name__ == "ListField": + for ref_list in queryset.scalar(name): + gridfs_refs.update(ref_list) + result = collection.delete_many(queryset._query) + for ref in gridfs_refs: + ref.delete() # If we're using an unack'd write concern, we don't really know how # many items have been deleted at this point, hence we only return diff --git a/tests/fields/test_file_field.py b/tests/fields/test_file_field.py index 4f3f1d45d..98ed9a6a3 100644 --- a/tests/fields/test_file_field.py +++ b/tests/fields/test_file_field.py @@ -9,6 +9,7 @@ from mongoengine import * from mongoengine.connection import get_db +from mongoengine.fields import FileField, ListField try: from PIL import Image @@ -581,6 +582,73 @@ class Animal(Document): assert marmot.photos[0].foo == "bar" assert marmot.photos[0].get().length == 8313 + def test_cascade_del_filefield(self): + """Ensure cascade deletion also remove file chunks""" + + class User(Document): + username = StringField() + + class Album(Document): + user = ReferenceField("User") + photo = FileField() + + User.register_delete_rule(Album, "user", CASCADE) + + User.drop_collection() + Album.drop_collection() + self.db["fs.files"].drop() + self.db["fs.chunks"].drop() + + user = User(username="bob").save() + assert User.objects.get() == user + + album = Album(user=user) + with open(TEST_IMAGE_PATH, "rb") as img: + album.photo.put(img) + album.save() + assert Album.objects.get().user == user + + user.delete() + assert User.objects.count() == 0 + assert Album.objects.count() == 0 + assert self.db["fs.files"].count() == 0 + assert self.db["fs.chunks"].count() == 0 + + def test_cascade_del_complex_field_filefield(self): + """Ensure cascade deletion also remove file chunks""" + + class User(Document): + username = StringField() + + class Album(Document): + user = ReferenceField("User") + photos = ListField(FileField()) + + User.register_delete_rule(Album, "user", CASCADE) + + User.drop_collection() + Album.drop_collection() + self.db["fs.files"].drop() + self.db["fs.chunks"].drop() + + user = User(username="bob").save() + assert User.objects.get() == user + + album = Album(user=user) + with open(TEST_IMAGE_PATH, "rb") as img: + photos_field = album._fields["photos"].field + new_proxy = photos_field.get_proxy_obj("photos", album) + new_proxy.put(img, content_type="image/jpeg", foo="bar") + album.photos.append(new_proxy) + album.save() + assert Album.objects.get().user == user + + user.delete() + assert User.objects.count() == 0 + assert Album.objects.count() == 0 + assert self.db["fs.files"].count() == 0 + assert self.db["fs.chunks"].count() == 0 + if __name__ == "__main__": unittest.main() From 35a9b98f365a88c0215811ae1c6ef35e8c9f3697 Mon Sep 17 00:00:00 2001 From: StoneMoe Date: Fri, 4 Dec 2020 19:14:09 +0800 Subject: [PATCH 2/2] Fix incorrect field type check --- mongoengine/queryset/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 6fbe1c233..693e4a63a 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -521,7 +521,10 @@ def delete(self, write_concern=None, _from_doc_delete=False, cascade_refs=None): for name, field in doc._fields.items(): if field.__class__.__name__ == "FileField": gridfs_refs.update(queryset.scalar(name)) - if field.__class__.__name__ == "ListField": + if ( + field.__class__.__name__ == "ListField" + and field.field.__class__.__name__ == "FileField" + ): for ref_list in queryset.scalar(name): gridfs_refs.update(ref_list)