From a4cba34123248ba10b90616b3d10c1a491565d04 Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Fri, 7 Feb 2020 10:19:41 +0100
Subject: [PATCH] Add management command to delete orphaned attachment files

---
 .../delete_orphaned_attachment_files.py       | 41 +++++++++++++++++++
 apimail/models/attachment.py                  |  3 ++
 2 files changed, 44 insertions(+)
 create mode 100644 apimail/management/commands/delete_orphaned_attachment_files.py

diff --git a/apimail/management/commands/delete_orphaned_attachment_files.py b/apimail/management/commands/delete_orphaned_attachment_files.py
new file mode 100644
index 000000000..e1b7feef7
--- /dev/null
+++ b/apimail/management/commands/delete_orphaned_attachment_files.py
@@ -0,0 +1,41 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+import os
+
+from django.core.management import BaseCommand
+
+from ...models import AttachmentFile, ComposedMessage, StoredMessage
+
+
+class Command(BaseCommand):
+
+    def handle(self, *args, **options):
+        uuids_in_cm = [att.uuid for cm in ComposedMessage.objects.all()
+                       for att in cm.attachment_files.all()]
+        uuids_in_sm = [att.uuid for sm in StoredMessage.objects.all()
+                       for att in sm.attachment_files.all()]
+
+        uuids_in_use = set(uuids_in_cm + uuids_in_sm)
+        print(uuids_in_use)
+
+        orphaned_att = AttachmentFile.objects.exclude(uuid__in=uuids_in_use)
+
+        print(orphaned_att)
+
+        for orphan_att in orphaned_att:
+            # We double-check that we're not deleting any used attachment
+            # since the 'exclude' logic above is risky
+            # (any mistake in uuids_in_use would lead to unwanted deletion)
+            try:
+                ComposedMessage.objects.filter(attachment_files__uuid=orphan_att.uuid).first()
+            except ComposedMessage.DoesNotExist:
+                try:
+                    StoredMessage.objects.filter(attachment_files__uuid=orphan_att.uuid).first()
+                except StoredMessage.DoesNotExist:
+                    print('Deleting %s' % orphan_att.uuid)
+                    if orphan_att.file:
+                        if os.path.isfile(orphan_att.file.path):
+                            os.remove(orphan_att.file.path)
+                    orphan_att.delete()
diff --git a/apimail/models/attachment.py b/apimail/models/attachment.py
index 4c3790365..891609ac5 100644
--- a/apimail/models/attachment.py
+++ b/apimail/models/attachment.py
@@ -29,6 +29,9 @@ class AttachmentFile(models.Model):
         validators=[validate_max_email_attachment_file_size,],
         storage=SecureFileStorage())
 
+    def __str__(self):
+        return '%s (%s, %s)' % (self.data['name'], self.data['content-type'], self.file.size)
+
     def get_absolute_url(self):
         return reverse('apimail:attachment_file',
                        kwargs={'uuid': self.uuid})
-- 
GitLab