Подтвердить что ты не робот

Динамический путь к файлу в Django

Я пытаюсь создать динамические пути к файлам в Django. Я хочу сделать файловую систему такой:

 -- user_12
     --- photo_1
     --- photo_2
 --- user_ 13
     ---- photo_1

Я нашел связанный вопрос: Django Пользовательское поле загрузки изображения с динамическим путем

Здесь говорят, что мы можем изменить путь upload_to и приводит к https://docs.djangoproject.com/en/stable/topics/files/ doc. В документации есть пример:

from django.db import models
from django.core.files.storage import FileSystemStorage

fs = FileSystemStorage(location='/media/photos')

class Car(models.Model):
    ...
    photo = models.ImageField(storage=fs)

Но, тем не менее, это не динамично, я хочу присвоить идентификатору автомобиля имя изображения, и я не могу присвоить идентификатор до завершения определения автомобиля. Так как я могу создать путь с идентификатором автомобиля?

4b9b3361

Ответ 1

Вы можете использовать вызываемый в аргументе upload_to вместо использования пользовательского хранилища. См. docs и обратите внимание на предупреждение о том, что первичный ключ еще не может быть установлен при вызове функции (поскольку загрузка может быть обработана перед тем как объект будет сохранен в базе данных), поэтому использование ID может оказаться невозможным. Возможно, вы захотите использовать другое поле на модели, например, slug. Например:

import os
def get_upload_path(instance, filename):
    return os.path.join(
      "user_%d" % instance.owner.id, "car_%s" % instance.slug, filename)

то

photo = models.ImageField(upload_to=get_upload_path)

Ответ 2

https://docs.djangoproject.com/en/stable/ref/models/fields/#django.db.models.FileField.upload_to

def upload_path_handler(instance, filename):
    return "user_{id}/{file}".format(id=instance.user.id, file=filename)

class Car(models.Model):
    ...
    photo = models.ImageField(upload_to=upload_path_handler, storage=fs)

В документах есть предупреждение, но оно не должно влиять на вас, поскольку мы получаем идентификатор User, а не идентификатор Car.

В большинстве случаев этот объект не будет были сохранены в базе данных, поэтому, если он использует AutoField по умолчанию, он может еще не иметь значения для своего поле первичного ключа.

Ответ 3

Вы можете использовать лямбда-функцию, как показано ниже, обратите внимание, что если экземпляр является новым, то он не будет иметь идентификатор экземпляра, поэтому используйте что-то еще:

logo = models.ImageField(upload_to=lambda instance, filename: 'directory/images/{0}/{1}'.format(instance.owner.id, filename))

Ответ 4

Мое решение не изящно, но оно работает:

В модели используйте стандартную функцию, для которой потребуется id/pk

def directory_path(instance, filename):
    return 'files/instance_id_{0}/{1}'.format(instance.pk, filename)

в views.py сохраните форму следующим образом:

f=form.save(commit=False)
ftemp1=f.filefield
f.filefield=None
f.save()
#And now that we have crated the record we can add it
f.filefield=ftemp1
f.save()

Это сработало для меня. Примечание. Мое файловое поле в моделях и разрешено для значений Null. Null = True

Ответ 5

Хорошо, очень поздно, но это работает для меня.

def content_file_name(instance, filename):
    upload_dir = os.path.join('uploads',instance.albumname)
    if not os.path.exists(upload_dir):
        os.makedirs(upload_dir)
    return os.path.join(upload_dir, filename)

Модель, подобная этой только

class Album(models.Model):
    albumname = models.CharField(max_length=100)
    audiofile = models.FileField(upload_to=content_file_name)

Ответ 7

У этого парня есть способ сделать динамический путь. Идея состоит в том, чтобы установить ваше любимое хранилище и настроить параметр "upload_to()" с помощью функции.

Надеюсь, что это поможет.

Ответ 8

Я обнаружил другое решение, которое грязно, но оно работает. Вы должны создать новую манекен-модель, которая сама синхронизируется с оригинальной. Я не горжусь этим, но не нашел другого решения. В моем случае я хочу загружать файлы и хранить их в каталоге с именем идентификатора модели (потому что я создам там больше файлов).

model.py

class dummyexperiment(models.Model):
  def __unicode__(self):
    return str(self.id)

class experiment(models.Model):
  def get_exfile_path(instance, filename):
    if instance.id == None:
      iid = instance.dummye.id
    else:
      iid = instance.id
    return os.path.join('experiments', str(iid), filename)

  exfile = models.FileField(upload_to=get_exfile_path)

  def save(self, *args, **kwargs):
    if self.id == None:
      self.dummye = dummyexperiment()
      self.dummye.save()
    super(experiment, self).save(*args, **kwargs)

Я очень новичок в python и django, но мне кажется, что это нормально.

другое решение:

def get_theme_path(instance, filename):
  id = instance.id
  if id == None:
    id = max(map(lambda a:a.id,Theme.objects.all())) + 1
  return os.path.join('experiments', str(id), filename)

Ответ 9

Поскольку первичный ключ (id) может быть недоступен, если экземпляр модели еще не был сохранен в базе данных, я написал свои подклассы FileField, которые перемещают файл при сохранении модели, и подкласс хранилища, который удаляет старые файлы.

хранения:

class OverwriteFileSystemStorage(FileSystemStorage):
    def _save(self, name, content):
        self.delete(name)
        return super()._save(name, content)

    def get_available_name(self, name):
        return name

    def delete(self, name):
        super().delete(name)

        last_dir = os.path.dirname(self.path(name))

        while True:
            try:
                os.rmdir(last_dir)
            except OSError as e:
                if e.errno in {errno.ENOTEMPTY, errno.ENOENT}:
                    break

                raise e

            last_dir = os.path.dirname(last_dir)

FileField:

def tweak_field_save(cls, field):
    field_defined_in_this_class = field.name in cls.__dict__ and field.name not in cls.__bases__[0].__dict__

    if field_defined_in_this_class:
        orig_save = cls.save

        if orig_save and callable(orig_save):
            assert isinstance(field.storage, OverwriteFileSystemStorage), "Using other storage than '{0}' may cause unexpected behavior.".format(OverwriteFileSystemStorage.__name__)

            def save(self, *args, **kwargs):
                if self.pk is None:
                    orig_save(self, *args, **kwargs)

                    field_file = getattr(self, field.name)

                    if field_file:
                        old_path = field_file.path
                        new_filename = field.generate_filename(self, os.path.basename(old_path))
                        new_path = field.storage.path(new_filename)
                        os.makedirs(os.path.dirname(new_path), exist_ok=True)
                        os.rename(old_path, new_path)
                        setattr(self, field.name, new_filename)

                    # for next save
                    if len(args) > 0:
                        args = tuple(v if k >= 2 else False for k, v in enumerate(args))

                    kwargs['force_insert'] = False
                    kwargs['force_update'] = False

                orig_save(self, *args, **kwargs)

            cls.save = save


def tweak_field_class(orig_cls):
    orig_init = orig_cls.__init__

    def __init__(self, *args, **kwargs):
        if 'storage' not in kwargs:
            kwargs['storage'] = OverwriteFileSystemStorage()

        if orig_init and callable(orig_init):
            orig_init(self, *args, **kwargs)

    orig_cls.__init__ = __init__

    orig_contribute_to_class = orig_cls.contribute_to_class

    def contribute_to_class(self, cls, name):
        if orig_contribute_to_class and callable(orig_contribute_to_class):
            orig_contribute_to_class(self, cls, name)

        tweak_field_save(cls, self)

    orig_cls.contribute_to_class = contribute_to_class

    return orig_cls


def tweak_file_class(orig_cls):
    """
    Overriding FieldFile.save method to remove the old associated file.
    I'm doing the same thing in OverwriteFileSystemStorage, but it works just when the names match.
    I probably want to preserve both methods if anyone calls Storage.save.
    """

    orig_save = orig_cls.save

    def new_save(self, name, content, save=True):
        self.delete(save=False)

        if orig_save and callable(orig_save):
            orig_save(self, name, content, save=save)

    new_save.__name__ = 'save'
    orig_cls.save = new_save

    return orig_cls


@tweak_file_class
class OverwriteFieldFile(models.FileField.attr_class):
    pass


@tweak_file_class
class OverwriteImageFieldFile(models.ImageField.attr_class):
    pass


@tweak_field_class
class RenamedFileField(models.FileField):
    attr_class = OverwriteFieldFile


@tweak_field_class
class RenamedImageField(models.ImageField):
    attr_class = OverwriteImageFieldFile

а мои вызывные атрибуты upload_to выглядят следующим образом:

def user_image_path(instance, filename):
    name, ext = 'image', os.path.splitext(filename)[1]

    if instance.pk is not None:
        return os.path.join('users', os.path.join(str(instance.pk), name + ext))

    return os.path.join('users', '{0}_{1}{2}'.format(uuid1(), name, ext))

Ответ 10

MEDIA_ROOT/
   /company_Company1/company.png
                    /shop_Shop1/shop.png
                               /bikes/bike.png


def photo_path_company(instance, filename):
# file will be uploaded to MEDIA_ROOT/company_<name>/
    return 'company_{0}/{1}'.format(instance.name, filename)

class Company(models.Model):
    name = models.CharField()
    photo = models.ImageField(max_length=255, upload_to=photo_path_company)

def photo_path_shop(instance, filename):
# file will be uploaded to MEDIA_ROOT/company_<name>/shop_<name>/
    parent_path = instance.company._meta.get_field('photo').upload_to(instance.company, '')
    return parent_path + 'shop_{0}/{1}'.format(instance.name, filename)

class Shop(models.Model):
    name = models.CharField()
    photo = models.ImageField(max_length=255, upload_to=photo_path_shop)

def photo_path_bike(instance, filename):
    # file will be uploaded to MEDIA_ROOT/company_<name>/shop_<name>/bikes/
    parent_path = instance.shop._meta.get_field('photo').upload_to(instance.shop, '')
    return parent_path + 'bikes/{0}'.format(filename)


class Bike(models.Model):
    name = models.CharField()
    photo = models.ImageField(max_length=255, upload_to=photo_path_bike)

Ответ 11

Вы можете переопределить метод save модели:

def save_image(instance, filename):
    instance_id = f'{instance.id:03d}'  # 001
    return f'{instance_id}-{filename.lower()}'  # 001-foo.jpg

class Resource(models.Model):
    photo = models.ImageField(upload_to=save_image)

   def save(self, *args, **kwargs):
        if self.id is None:
            photo = self.photo
            self.photo = None
            super().save(*args, **kwargs)
            self.photo = photo
            if 'force_insert' in kwargs:
                kwargs.pop('force_insert')
        super().save(*args, **kwargs)