Динамический путь к файлу в 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)
Но, тем не менее, это не динамично, я хочу присвоить идентификатору автомобиля имя изображения, и я не могу присвоить идентификатор до завершения определения автомобиля. Так как я могу создать путь с идентификатором автомобиля?
Ответы
Ответ 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)
Ответ 6
В DjangoSnippets есть два решения
Ответ 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)