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 0000000000000000000000000000000000000000..e1b7feef7c3fb5c8b4b5db0b104717239dc63e91 --- /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 4c3790365864ed8cfb3c25f30ec7d18a5aae56ee..891609ac5c509cc2758e2b0e7d76d6f682d39627 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})