Добавить динамическое расположение содержимого для имен файлов (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
.