From c4dce07142ccd4411543c3d202f7a2a2ea776b2b Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Wed, 13 Nov 2019 22:40:56 +0100
Subject: [PATCH] Add class StoredMessageAttachment and related facilities

---
 SciPost_v1/settings/base.py                   |  1 +
 apimail/admin.py                              | 14 +++++++-
 .../commands/mailgun_get_stored_messages.py   | 15 +++++++-
 apimail/migrations/0003_auto_20191113_2226.py | 34 +++++++++++++++++++
 apimail/models/__init__.py                    |  2 +-
 apimail/models/stored_message.py              | 20 +++++++++++
 apimail/validators.py                         | 16 +++++++++
 7 files changed, 99 insertions(+), 3 deletions(-)
 create mode 100644 apimail/migrations/0003_auto_20191113_2226.py
 create mode 100644 apimail/validators.py

diff --git a/SciPost_v1/settings/base.py b/SciPost_v1/settings/base.py
index 35b254683..f62973929 100644
--- a/SciPost_v1/settings/base.py
+++ b/SciPost_v1/settings/base.py
@@ -336,6 +336,7 @@ EMAIL_SUBJECT_PREFIX = '[SciPost Server] '
 MAILCHIMP_DATABASE_CODE = 'us6'
 MAILCHIMP_API_USER = 'test_API-user'
 MAILCHIMP_API_KEY = 'test_API-key'
+MAX_EMAIL_ATTACHMENT_FILE_SIZE = 20000000
 
 
 # iThenticate
diff --git a/apimail/admin.py b/apimail/admin.py
index 28a9f7314..58be9fe30 100644
--- a/apimail/admin.py
+++ b/apimail/admin.py
@@ -4,10 +4,22 @@ __license__ = "AGPL v3"
 
 from django.contrib import admin
 
-from .models import Event
+from .models import Event, StoredMessage, StoredMessageAttachment
 
 
 class EventAdmin(admin.ModelAdmin):
     pass
 
 admin.site.register(Event, EventAdmin)
+
+
+class StoredMessageAttachmentInline(admin.StackedInline):
+    model = StoredMessageAttachment
+    extra = 0
+    min_num = 0
+
+
+class StoredMessageAdmin(admin.ModelAdmin):
+    inlines = [StoredMessageAttachmentInline,]
+
+admin.site.register(StoredMessage, StoredMessageAdmin)
diff --git a/apimail/management/commands/mailgun_get_stored_messages.py b/apimail/management/commands/mailgun_get_stored_messages.py
index 001fe2dee..7b0b5f1dd 100644
--- a/apimail/management/commands/mailgun_get_stored_messages.py
+++ b/apimail/management/commands/mailgun_get_stored_messages.py
@@ -2,13 +2,16 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
+from tempfile import TemporaryFile
+
 import requests
 
 from django.conf import settings
+from django.core.files import File
 from django.core.management import BaseCommand
 
 from ...exceptions import APIMailError
-from ...models import Event, StoredMessage
+from ...models import Event, StoredMessage, StoredMessageAttachment
 
 
 class Command(BaseCommand):
@@ -34,3 +37,13 @@ class Command(BaseCommand):
                 sm = StoredMessage.objects.create(data=response)
                 orphan.stored_message = sm
                 orphan.save()
+                # Now deal with attachments
+                for att_item in response['attachments']:
+                    with TemporaryFile() as tf:
+                        r = requests.get(att_item['url'], stream=True)
+                        for chunk in r.iter_content(chunk_size=8192):
+                            tf.write(chunk)
+                        tf.seek(0)
+                        sma = StoredMessageAttachment.objects.create(
+                            message=sm, data=att_item)
+                        sma._file.save(att_item['name'], File(tf))
diff --git a/apimail/migrations/0003_auto_20191113_2226.py b/apimail/migrations/0003_auto_20191113_2226.py
new file mode 100644
index 000000000..1023b4a69
--- /dev/null
+++ b/apimail/migrations/0003_auto_20191113_2226.py
@@ -0,0 +1,34 @@
+# Generated by Django 2.1.8 on 2019-11-13 21:26
+
+import apimail.validators
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations, models
+import django.db.models.deletion
+import scipost.storage
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('apimail', '0002_auto_20191113_1547'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='StoredMessageAttachment',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('data', django.contrib.postgres.fields.jsonb.JSONField(default=dict)),
+                ('_file', models.FileField(storage=scipost.storage.SecureFileStorage(), upload_to='uploads/mail/stored_messages/attachments/%Y/%m/%d/', validators=[apimail.validators.validate_max_email_attachment_file_size])),
+            ],
+        ),
+        migrations.AlterModelOptions(
+            name='storedmessage',
+            options={'ordering': ['-data__Date']},
+        ),
+        migrations.AddField(
+            model_name='storedmessageattachment',
+            name='message',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='apimail.StoredMessage'),
+        ),
+    ]
diff --git a/apimail/models/__init__.py b/apimail/models/__init__.py
index a516face6..8214d8ede 100644
--- a/apimail/models/__init__.py
+++ b/apimail/models/__init__.py
@@ -4,4 +4,4 @@ __license__ = "AGPL v3"
 
 from .event import Event
 
-from .stored_message import StoredMessage
+from .stored_message import StoredMessage, StoredMessageAttachment
diff --git a/apimail/models/stored_message.py b/apimail/models/stored_message.py
index c6ac6cb33..f3e1bf895 100644
--- a/apimail/models/stored_message.py
+++ b/apimail/models/stored_message.py
@@ -8,6 +8,10 @@ from django.contrib.postgres.fields import JSONField
 from django.db import models
 from django.urls import reverse
 
+from scipost.storage import SecureFileStorage
+
+from ..validators import validate_max_email_attachment_file_size
+
 
 class StoredMessage(models.Model):
     """
@@ -19,5 +23,21 @@ class StoredMessage(models.Model):
         editable=False)
     data = JSONField(default=dict)
 
+    class Meta:
+        ordering = ['-data__Date',]
+
     def get_absolute_url(self):
         return reverse('apimail:stored_message_detail', kwargs={'uuid': self.uuid})
+
+
+class StoredMessageAttachment(models.Model):
+    message = models.ForeignKey(
+        'apimail.StoredMessage',
+        on_delete=models.CASCADE,
+        related_name='attachments' # doesn't collide with StoredMessage.data.attachments
+    )
+    data = JSONField(default=dict)
+    _file = models.FileField(
+        upload_to='uploads/mail/stored_messages/attachments/%Y/%m/%d/',
+        validators=[validate_max_email_attachment_file_size,],
+        storage=SecureFileStorage())
diff --git a/apimail/validators.py b/apimail/validators.py
new file mode 100644
index 000000000..2c5581249
--- /dev/null
+++ b/apimail/validators.py
@@ -0,0 +1,16 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django.conf import settings
+from django.core.exceptions import ValidationError
+from django.template.defaultfilters import filesizeformat
+
+
+def validate_max_email_attachment_file_size(value):
+    if value.size > int(settings.MAX_EMAIL_ATTACHMENT_FILE_SIZE):
+        raise ValidationError(
+            'Please keep filesize under %s. Current filesize: %s' % (
+                filesizeformat(settings.MAX_EMAIL_ATTACHMENT_FILE_SIZE),
+                filesizeformat(value.size))
+        )
-- 
GitLab