Repository: svthalia/concrexit

Issue:
Allow .svg files for slides
### Is your feature request related to a problem? Please describe.
In high resolutions, banners don't always look nice, although I often create them as vector graphics so this shouldn't be a problem

### Describe the solution you'd like
Allow .svg files for the slides

### Motivation
Lower file size, better quality

### Describe alternatives you've considered
Use high resolution .png's (`@4x`) but that increase file size quite a bit and is not optimal

### Additional context
The Django image field does not allow .svg, maybe we can make a Django file field with a validator on image or .svg?


Discussion:
This would require validation of the SVG file, otherwise it could be a source of an XSS vulnerability I believe
> This would require validation of the SVG file, otherwise it could be a source of an XSS vulnerability I believe

I researched this for a bit, but if we put it in image tags it is not vulnerable according to [this blog post](https://digi.ninja/blog/svg_xss.php)

Resolving patch:
diff --git a/website/announcements/migrations/0012_auto_20201209_1704.py b/website/announcements/migrations/0012_auto_20201209_1704.py
new file mode 100644
--- /dev/null
+++ b/website/announcements/migrations/0012_auto_20201209_1704.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.1.4 on 2020-12-09 16:04
+
+import announcements.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('announcements', '0011_remove_multilang_field'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='slide',
+            name='content',
+            field=models.FileField(help_text='The content of the slide; what image to display.', upload_to='public/announcements/slides/', validators=[announcements.models.validate_image], verbose_name='Content'),
+        ),
+    ]
diff --git a/website/announcements/models.py b/website/announcements/models.py
--- a/website/announcements/models.py
+++ b/website/announcements/models.py
@@ -1,6 +1,10 @@
 """The models defined by the announcement package"""
+from django.core.validators import (
+    FileExtensionValidator,
+    get_available_image_extensions,
+)
 from django.db import models
-from django.db.models import ImageField, CharField
+from django.db.models import CharField
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from tinymce.models import HTMLField
@@ -96,6 +100,12 @@ def is_visible(self):
         )
 
 
+def validate_image(value):
+    return FileExtensionValidator(
+        allowed_extensions=[*get_available_image_extensions(), "svg"]
+    )(value)
+
+
 class Slide(models.Model):
     """Describes an announcement"""
 
@@ -106,11 +116,12 @@ class Slide(models.Model):
         max_length=100,
     )
 
-    content = ImageField(
+    content = models.FileField(
         verbose_name=_("Content"),
         help_text=_("The content of the slide; what image to display."),
         blank=False,
         upload_to="public/announcements/slides/",
+        validators=[validate_image],
     )
 
     since = models.DateTimeField(
diff --git a/website/utils/media/services.py b/website/utils/media/services.py
--- a/website/utils/media/services.py
+++ b/website/utils/media/services.py
@@ -1,6 +1,6 @@
 import os
 
-from django.db.models.fields.files import ImageFieldFile
+from django.db.models.fields.files import FieldFile, ImageFieldFile
 from django.conf import settings
 from django.core import signing
 from django.urls import reverse
@@ -47,13 +47,16 @@ def get_thumbnail_url(path, size, fit=True):
     :param fit: False to keep the aspect ratio, True to crop
     :return: direct media url or generate-thumbnail path
     """
-    if isinstance(path, ImageFieldFile):
+    if isinstance(path, ImageFieldFile) or isinstance(path, FieldFile):
         path = path.name
 
     query = ""
     size_fit = "{}_{}".format(size, int(fit))
     parts = path.split("/")
 
+    if parts[-1].endswith(".svg") and parts[0] == "public":
+        return f"{settings.MEDIA_URL}{path}"
+
     sig_info = {
         "size": size,
         "fit": int(fit),


Test patch: