Как использовать Django FileField с динамическим ведром Amazon S3? - PullRequest
0 голосов
/ 31 мая 2018

У меня есть модель Django с полем файлов и хранилище по умолчанию с использованием корзины Amazon S3 (через превосходное django-хранилище ).

Моя проблема не в том, чтобы загружать файлы вдинамический путь к папке (как мы видим во многих других ответах).Моя проблема глубже и двояче:

  • Файлы уже находятся в корзине Amazon S3, и я не хочу их загружать-перезагружать (хуже: у меня есть только доступ для чтения к ним).
  • Файлы доступны через учетные данные S3, которые могут отличаться от одного файла к другому (то есть файлы могут находиться в разных сегментах и ​​иметь доступ через разные учетные данные).Следовательно, мой FileField должен иметь динамическое хранилище .

Есть идеи?

(Djabgo 1.11, Python 3).

1 Ответ

0 голосов
/ 31 мая 2018

Оказывается, это не так сложно. Но приведенный ниже код не очень проверен , и я должен предупредить вас, чтобы не копировать-вставлять без проверки!

Я создал пользовательский FileField подкласс:

class DynamicS3BucketFileField(models.FileField):
    attr_class = S3Boto3StorageFile
    descriptor_class = DynamicS3BucketFileDescriptor

    def pre_save(self, model_instance, add):
        return getattr(model_instance, self.attname)

Обратите внимание, что attr_class специально использует класс S3Boto3StorageFile (подкласс File, предоставляемый django-storages).

Перегрузка pre_save имеет только одну цель: избегать внутреннего вызова file.save, который попытался бы повторно загрузить файл.

Волшебство происходит внутриFileDescriptor подкласс:

class DynamicS3BucketFileDescriptor(FileDescriptor):
    def __get__(self, instance, cls=None):
        if instance is None:
            return self

        # Copied from FileDescriptor
        if self.field.name in instance.__dict__:
            file = instance.__dict__[self.field.name]
        else:
            instance.refresh_from_db(fields=[self.field.name])
            file = getattr(instance, self.field.name)

        # Make sure to transform storage to a Storage instance.
        if callable(self.field.storage):
            self.field.storage = self.field.storage(instance)

        # The file can be a string here (depending on when/how we access the field).
        if isinstance(file, six.string_types):
            # We instance file following S3Boto3StorageFile constructor.
            file = self.field.attr_class(file, 'rb', self.field.storage)
            # We follow here the way FileDescriptor work (see 'return' finish line).
            instance.__dict__[self.field.name] = file

        # Copied from FileDescriptor. The difference here is that these 3
        # properties are set systematically without conditions.
        file.instance = instance
        file.field = self.field
        file.storage = self.field.storage
        # Added a very handy property to file.
        file.url = self.field.storage.url(file.name)

        return instance.__dict__[self.field.name]

Приведенный выше код использует некоторый внутренний код FileDescriptor, адаптированный к моему случаю.Обратите внимание на if callable(self.field.storage):, объясненный ниже.

Строка ключа: file = self.field.attr_class(file, 'rb', self.field.storage), которая автоматически создает действительный экземпляр S3Boto3StorageFile в зависимости от содержимого текущего экземпляра file (иногда этофайл, иногда это простая строка, это часть бизнеса FileDescriptor.

Теперь динамическая часть получается довольно просто.Фактически, когда вы объявляете FileField, вы можете предоставить опции storage функцию.Например:

class MyMedia(models.Model):
    class Meta:
        app_label = 'appname'

    mediaset = models.ForeignKey(Mediaset, on_delete=models.CASCADE, related_name='media_files')
    file = DynamicS3BucketFileField(null=True, blank=True, storage=get_fits_file_storage)

И функция get_fits_file_storage будет вызываться с одним аргументом: экземпляр MyMedia.Следовательно, я могу использовать любое свойство этого объекта, чтобы вернуть действительное хранилище.В моем случае mediaset, который содержит ключ, который позволяет мне получить объект, содержащий учетные данные S3, с помощью которого я могу создать экземпляр S3Boto3Storage (другой класс, предоставляемый django-storages).

В частности:

def get_fits_file_storage(instance):
    name = instance.mediaset.archive_storage_name
    return instance.mediaset.archive.bucket_keys.get(name= name).get_storage()

Et voilà!

...