Добавить динамическое расположение содержимого для имен файлов (amazon S3) в Python

У меня есть модель Django, которая сохраняет имя файла как "uuid4(). Pdf". Где uuid4 генерирует случайный uuid для каждого созданного экземпляра. Это имя файла также хранится на сервере amazon s3 с тем же именем.

Я пытаюсь добавить пользовательское расположение для имени файла, которое я загружаю в amazon s3, потому что я хочу видеть пользовательское имя всякий раз, когда загружаю файл, а не uuid. В то же время я хочу, чтобы файлы хранились на s3 с именем файла uuid.

Итак, я использую django-хранилища с python 2.7. Я попытался добавить content_disposition в настройках, как это:

AWS_CONTENT_DISPOSITION = 'core.utils.s3.get_file_name'

где get_file_name() возвращает имя файла.

Я также попытался добавить это в настройках:

AWS_HEADERS = {
'Content-Disposition': 'attachments; filename="%s"'% get_file_name(),

 }

неудачно!

Кто-нибудь из вас знает, чтобы реализовать это.

4 ответа

Текущая версия S3Boto3Storage из django-хранилищ поддерживает AWS_S3_OBJECT_PARAMETERS переменная глобальных настроек, которая позволяет изменять ContentDisposition тоже. Но проблема в том, что он применяется как есть ко всем объектам, загруженным в s3, и, кроме того, влияет на все модели, работающие с хранилищем, что может оказаться не ожидаемым результатом.

Следующий взлом работал на меня.

from storages.backends.s3boto3 import S3Boto3Storage

class DownloadableS3Boto3Storage(S3Boto3Storage):

    def _save_content(self, obj, content, parameters):
        """
        The method is called by the storage for every file being uploaded to S3.
        Below we take care of setting proper ContentDisposition header for
        the file.
        """
        filename = obj.key.split('/')[-1]
        parameters.update({'ContentDisposition': f'attachment; filename="{filename}"'})
        return super()._save_content(obj, content, parameters)

Здесь мы переопределяем собственный метод сохранения объекта хранилища и следим за тем, чтобы в каждом файле было установлено правильное расположение содержимого. Разумеется, вам нужно передать это хранилище в область, над которой вы работаете:

my_file_filed = models.FileField(upload_to='mypath', storage=DownloadableS3Boto3Storage())

На случай, если кто-то обнаружит это, как и я: ни одно из решений, упомянутых в SO, не сработало для меня в Django 3.0.

Строка документации S3Boto3Storageпредлагает отменить S3Boto3Storage.get_object_parameters, однако этот метод получает только nameзагруженного файла, который на данный момент был изменен upload_toи может отличаться от оригинала.

Что сработало, так это следующее:

      class S3Boto3CustomStorage(S3Boto3Storage):
    """Override some upload parameters, such as ContentDisposition header."""

    def _get_write_parameters(self, name, content):
        """Set ContentDisposition header using original file name.

        While docstring recomments overriding `get_object_parameters` for this purpose,
        `get_object_parameters` only gets a `name` which is not the original file name,
        but the result of `upload_to`.
        """
        params = super()._get_write_parameters(name, content)
        original_name = getattr(content, 'name', None)
        if original_name and name != original_name:
            content_disposition = f'attachment; filename="{original_name}"'
            params['ContentDisposition'] = content_disposition
        return params

а затем использовать это хранилище в поле файла, например:

      
    file_field = models.FileField(
        upload_to=some_func,
        storage=S3Boto3CustomStorage(),
    )

Какое бы решение вы ни придумали, не меняйте file_field.storage.object_parametersнапрямую (например, в модели save()как было предложено в аналогичном вопросе), потому что это изменится ContentDispositionзаголовок для последующих загрузок файлов любого поля, использующего то же хранилище. Это не то, что вы, вероятно, хотите.

Я предполагаю, что вы используете S3BotoStorage из django-хранилищ, поэтому при загрузке файла в S3 переопределите метод save() модели и установите там заголовок.

Я приведу пример ниже:

class ModelName(models.Model):
    sthree = S3BotoStorage()
    def file_name(self,filename):
        ext = filename.split('.')[-1]
        name = "%s/%s.%s" % ("downloads", uuid.uuid4(), ext)
        return name
    upload_file = models.FileField(upload_to=file_name,storage = sthree)
    def save(self):
        self.upload_file.storage.headers = {'Content-Disposition': 'attachments; filename="%s"' %self.upload_file.name}
        super(ModelName, self).save()

Один из способов может дать ResponseContentDispositionпараметр для S3Boto3Storage.url()метод. В этом случае вам не нужно создавать собственное хранилище.

Пример модели:

      class MyModel(models.Model):
    file = models.FileField(upload_to=generate_upload_path)
    original_filename = models.CharField(max_length=255)

Создание URL для вашего файла:

      # obj is instance of MyModel
url = obj.file.storage.url(
    obj.file.name,
    parameters={
        'ResponseContentDisposition': f'inline; filename={obj.original_filename}',
    },
)

Если вы хотите заставить браузер загрузить файл, замените inlineс attachment.

Если вы используете имена файлов, отличные от ASCII, проверьте, как Django кодирует имена файлов для Content-Dispositionзаголовок в FileResponse.

Другие вопросы по тегам