]> git.parisson.com Git - teleforma.git/commitdiff
add S3 storage options for Media file
authorGuillaume Pellerin <guillaume.pellerin@parisson.com>
Sun, 3 May 2026 15:34:48 +0000 (17:34 +0200)
committerGuillaume Pellerin <guillaume.pellerin@parisson.com>
Sun, 3 May 2026 15:34:48 +0000 (17:34 +0200)
app/local_settings.py.sample
app/settings.py
lib/pdfannotator
teleforma/admin.py
teleforma/exam/models.py
teleforma/migrations/0038_media_file_s3_alter_documentprivate_file_and_more.py [new file with mode: 0644]
teleforma/models/core.py
teleforma/views/core.py

index 1ecfb00c34b6c6c2286df936a3b26becadc27dfc..0e6d06963d30fd0ca1a2ea550a13938a9488cbe1 100644 (file)
@@ -68,3 +68,7 @@ DEFAULT_TO_EMAIL = 'recipient@recipient.org' # default recipient, for your tests
 EMAIL_SUBJECT_PREFIX = "[PREFIX]" # prefix title in email
 SITE_TITLE = 'Your Site'
 SITE_TAGLINE = 'This is a Mezzo site'
+
+AWS_ACCESS_KEY_ID=""
+AWS_SECRET_ACCESS_KEY=""
+AWS_S3_ENCRYPTION=True
\ No newline at end of file
index da5bf07ab88a93b7222b11ac1810d63328de37b7..444b95953bf0ff81011a4f75ea938e90674746e7 100644 (file)
@@ -158,9 +158,9 @@ USE_L10N = True
 USE_TZ = False
 
 
-########################
+#################
 # MEDIA / STATIC
-########################
+#################
 
 # Absolute path to the directory that holds media.
 # Example: "/home/media/media.lawrence.com/"
@@ -172,8 +172,6 @@ MEDIA_ROOT = '/srv/media/'
 #MEDIA_URL = 'http://pre-barreau.com/archives/'
 MEDIA_URL = '/media/'
 
-DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage'
-
 FILE_UPLOAD_TEMP_DIR = '/tmp'
 
 # Absolute path to the directory static files should be collected to.
@@ -438,6 +436,7 @@ JAZZMIN_SETTINGS = {
     "site_title": "CRFPA",
     "site_header": "CRFPA",
     "site_logo": "teleforma/images/logo_pb.png",
+    "site_brand": "Admin CRFPA",
 
     # # Links to put along the top menu
     # "topmenu_links": [
@@ -521,7 +520,7 @@ JAZZMIN_UI_TWEAKS = {
     "sidebar_nav_legacy_style": False,
     "sidebar_nav_flat_style": False,
     "theme": "default",
-    "dark_mode_theme": "darkly",
+    "default_theme_mode": "auto",
     "button_classes": {
         "primary": "btn-outline-primary",
         "secondary": "btn-outline-secondary",
@@ -608,6 +607,42 @@ AWS_S3_FILE_OVERWRITE=False
 AWS_STORAGE_BUCKET_NAME=""
 MEDIA_S3_BUCKET = '/media/bucket/'
 
+
+########################
+# STORAGES
+########################
+
+DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage'
+
+STORAGES = {
+    "default": {
+        "BACKEND": "django.core.files.storage.FileSystemStorage",
+        "OPTIONS": {
+        },
+    },
+    "staticfiles": {
+        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
+    },
+    "private_documents_s3": {
+        "BACKEND": "storages.backends.s3.S3Storage",
+        "OPTIONS": {
+          "bucket_name": "crfpa-private",
+          "region_name": "gra",
+          "endpoint_url":  "https://s3.gra.io.cloud.ovh.net/",
+        },
+    },
+    "media_s3": {
+        "BACKEND": "storages.backends.s3.S3Storage",
+        "OPTIONS": {
+          "bucket_name": "pb-crfpa-medias",
+          "region_name": "eu-west-par",
+          "endpoint_url":  "https://s3.eu-west-par.io.cloud.ovh.net/",
+        },
+    },
+}
+
+
+
 ########################
 # TELEFORMA
 ########################
@@ -632,14 +667,12 @@ TELEFORMA_EXAM_SCRIPT_SERVICE_URL = '/webviewer/teleforma.html'
 TELEFORMA_EXAM_USE_S3 = True
 
 TELEFORMA_DOCUMENTS_USE_S3 = False
-
 TELEFORMA_PRIVATE_DOCUMENTS_MODE = True #wether the downloaded docs should be signed
 TELEFORMA_PRIVATE_DOCUMENTS_USE_S3 = True
-AWS_STORAGE_BUCKET_NAME_PRIVATE_DOCUMENT="crfpa-private"
 
-TELEFORMA_MEDIA_USE_S3 = False
 TELEFORMA_MEDIA_DOWNLOAD = False
-AWS_STORAGE_BUCKET_NAME_MEDIA="crfpa-media"
+TELEFORMA_MEDIA_USE_S3 = True
+
 
 ORAL_OPTION_PRICE = 250
 FASICLE_OPTION_PRICE = 110
@@ -706,6 +739,7 @@ CORS_ALLOWED_ORIGINS = [
 
 CORS_ALLOW_ALL_ORIGINS = False
 
+
 ##################
 # LOCAL SETTINGS #
 ##################
index 70e1f1833f63cb9105659b431357be580cdfc659..1f161760779798d79a9c0c073f15044c2bcc6e46 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 70e1f1833f63cb9105659b431357be580cdfc659
+Subproject commit 1f161760779798d79a9c0c073f15044c2bcc6e46
index 742bd938331b9de3e65fbd9c5c4bf9aafc28d544..557cc725bd2665ed75c1f3d4709fdeacfa9494ec 100644 (file)
@@ -382,11 +382,11 @@ def duplicate_medias(modeladmin, request, queryset):
 
 class MediaAdmin(admin.ModelAdmin):
 
-    def get_form(self, request, obj=None, **kwargs):
-        form = super(MediaAdmin, self).get_form(request, obj, **kwargs)
-        period = get_periods(request)
-        form.base_fields['conference'].queryset = Conference.objects.filter(period=period)
-        return form
+    def get_form(self, request, obj=None, **kwargs):
+        form = super(MediaAdmin, self).get_form(request, obj, **kwargs)
+        period = get_periods(request)
+        form.base_fields['conference'].queryset = Conference.objects.filter(period=period)
+        return form
 
     list_per_page = 30
     exclude = ['readers']
index fe0918123e38dcabdaef2d0767cd14c91eacd8da..6eb56e7a1577f3554c1c62bf8367c435569fc3bb 100755 (executable)
@@ -46,7 +46,7 @@ import uuid
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.contrib.sites.models import Site
-from django.core.files.storage import FileSystemStorage
+from django.core.files.storage import FileSystemStorage, storages
 from django.db import models
 from django.db.models.signals import post_save
 from django.template.loader import render_to_string
@@ -90,10 +90,13 @@ REJECT_REASON = (('unreadable', _('unreadable')),
 cache_path = settings.MEDIA_ROOT + 'cache/'
 script_path = settings.MEDIA_ROOT + 'scripts/'
 
-if settings.TELEFORMA_EXAM_USE_S3:
-    storage = S3Boto3Storage
-else:
-    storage = FileSystemStorage
+
+def script_storage():
+    if settings.TELEFORMA_EXAM_USE_S3:
+        return storages["private_documents_s3"]
+    else:
+        return storages["default"]
+
 
 
 def sha1sum_file(filename):
@@ -266,7 +269,7 @@ class Script(BaseResource):
         upload_to='scripts/%Y/%m/%d',
         max_length=1024,
         blank=True,
-        storage=storage
+        storage=script_storage
         )
     box_uuid = models.CharField(_('Box UUID'), max_length=256, blank=True)
     score = models.FloatField(_('score'), blank=True,
diff --git a/teleforma/migrations/0038_media_file_s3_alter_documentprivate_file_and_more.py b/teleforma/migrations/0038_media_file_s3_alter_documentprivate_file_and_more.py
new file mode 100644 (file)
index 0000000..979abb0
--- /dev/null
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.13 on 2026-05-02 15:04
+
+import django.core.files.storage
+import teleforma.models.core
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('teleforma', '0037_merge_20260114_1638'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='media',
+            name='file_s3',
+            field=models.FileField(blank=True, max_length=1024, null=True, storage=teleforma.models.core.media_storage, upload_to='items/%Y/%m/%d', verbose_name='S3 file'),
+        ),
+        migrations.AlterField(
+            model_name='documentprivate',
+            name='file',
+            field=models.FileField(blank=True, db_column='filename', max_length=1024, storage=teleforma.models.core.private_document_storage, upload_to='private/documents/%Y/%m/%d', verbose_name='file'),
+        ),
+        migrations.AlterField(
+            model_name='media',
+            name='file',
+            field=models.FileField(blank=True, max_length=1024, null=True, storage=django.core.files.storage.FileSystemStorage(), upload_to='items/%Y/%m/%d', verbose_name='file'),
+        ),
+    ]
index 0b49463a7e75b002fc622418d96f969320293d28..5917f18424130a20929049f7375289762090ba37 100644 (file)
@@ -49,7 +49,7 @@ import django.db.models as models
 from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.files import File
-from django.core.files.storage import FileSystemStorage
+from django.core.files.storage import FileSystemStorage, storages
 from django.core.paginator import InvalidPage
 from django.db import models
 from django.forms.fields import FileField
@@ -115,19 +115,18 @@ STATUS_CHOICES = (
 WEIGHT_CHOICES = get_nint_choices(5)
 
 
-if settings.TELEFORMA_MEDIA_USE_S3:
-    media_s3_storage = S3Boto3Storage(
-        bucket=settings.AWS_STORAGE_BUCKET_NAME_MEDIA)
-    media_local_storage = FileSystemStorage
-else:
-    media_s3_storage = FileSystemStorage
-    media_local_storage = FileSystemStorage
+def private_document_storage():
+    if settings.TELEFORMA_PRIVATE_DOCUMENTS_USE_S3:
+        return storages["private_documents_s3"]
+    else:
+        return storages["default"]
+
 
-if settings.TELEFORMA_PRIVATE_DOCUMENTS_USE_S3:
-    private_document_storage = S3Boto3Storage(
-        bucket=settings.AWS_STORAGE_BUCKET_NAME_PRIVATE_DOCUMENT)
-else:
-    private_document_storage = FileSystemStorage
+def media_storage():
+    if settings.TELEFORMA_MEDIA_USE_S3:
+        return storages["media_s3"]
+    else:
+        return storages["default"]
 
 
 def get_random_hash():
@@ -1050,7 +1049,7 @@ class Media(MediaBase):
         max_length=1024,
         null=True,
         blank=True,
-        storage=media_local_storage
+        storage=storages["default"]
         )
     file_s3 = models.FileField(
         _('S3 file'),
@@ -1058,7 +1057,7 @@ class Media(MediaBase):
         max_length=1024,
         null=True,
         blank=True,
-        storage=media_s3_storage
+        storage=media_storage,
         )
     poster_file = models.FileField(
         _('poster file'), upload_to='items/%Y/%m/%d', max_length=255, null=True, blank=False)
index f1c1983a994ebbd7d209c7d1fc51833c4c038202..19069e064d5f5f9fdff7f90a70ed65ed1cc9d022 100644 (file)
@@ -280,19 +280,22 @@ def serve_media(file, content_type="", buffering=True, streaming=False, bucket=F
 
     if not settings.DEBUG:
         if bucket:
-            url = settings.MEDIA_S3_BUCKET + file.url.split(settings.AWS_S3_ENDPOINT_URL)[-1]
+            # url = settings.MEDIA_S3_BUCKET + file.url.split(settings.AWS_S3_ENDPOINT_URL)[-1]
+            return HttpResponseRedirect(file.url)
         else:
-            url = file.url
-        return nginx_media_accel(url, content_type=content_type,
+            return nginx_media_accel(file.url, content_type=content_type,
                                  buffering=buffering, streaming=streaming)
     else:
-        response = StreamingHttpResponse(
-            stream_from_file(file.path), content_type=content_type)
-        filename = os.path.basename(file.path)
-        if not streaming:
-            response['Content-Disposition'] = 'attachment; ' + \
-                'filename=' + filename
-        return response
+        if bucket:
+            return HttpResponseRedirect(file.url)
+        else:
+            response = StreamingHttpResponse(
+                stream_from_file(file.path), content_type=content_type)
+            filename = os.path.basename(file.path)
+            if not streaming:
+                response['Content-Disposition'] = 'attachment; ' + \
+                    'filename=' + filename
+            return response
 
 
 def nginx_media_accel(url, content_type="", buffering=True, streaming=False):
@@ -652,17 +655,29 @@ class MediaView(CourseAccessMixin, DetailView):
         media = self.get_object()
         if not media.mime_type:
             media.set_mime_type()
+
+        if media.course:
+            course = media.course
+        else:
+            course = media.conference.course
+        if media.course_type:
+            course_type = media.course_type
+        else:
+            course_type = media.conference.course_type
+        if media.course:
+            room_name = media.course.code
+        else:
+            room_name = media.conference.course.code
+        if media.conference.web_class_group:
+            room_name += '_' + media.conference.public_id
+
         context['mime_type'] = media.mime_type
-        context['course'] = media.course
-        context['type'] = media.course_type
+        context['course'] = course
+        context['type'] = course_type
         # context['notes'] = media.notes.all().filter(author=self.request.user)
         content_type = ContentType.objects.get(
             app_label="teleforma", model="course")
 
-        room_name = media.course.code
-        if media.conference.web_class_group:
-            room_name += '_' + media.conference.public_id
-
         access = get_access(media, context['all_courses'])
         if not access:
             context['access_error'] = access_error
@@ -704,15 +719,15 @@ class MediaView(CourseAccessMixin, DetailView):
         media_detail_url = request.build_absolute_uri(
             reverse("teleforma-media-detail",
                 kwargs={"period_id": period_id, "pk": media.id}))
-        if get_access(media, courses) and referer == media_detail_url:
+        if (get_access(media, courses) and referer == media_detail_url) or settings.DEBUG:
             media_reads = MediaRead.objects.filter(media=media, user=request.user)
             if not media_reads:
                 media_read = MediaRead(media=media, user=request.user)
                 media_read.save()
-            return serve_media(media.file,
+            return serve_media(media.get_file(),
                 content_type=media.mime_type,
                 streaming=streaming,
-                bucket=False,
+                bucket=settings.TELEFORMA_MEDIA_USE_S3,
                 )
         else:
             raise Http404("You don't have access to this media.")