Django中的动态文件路径

时间:2011-02-27 20:04:15

标签: django django-models

我正在尝试在django中生成动态文件路径。我想建立一个这样的文件系统:

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

我找到了一个相关问题:Django Custom image upload field with dynamic path

在这里,他们说我们可以更改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)

但是,这仍然不是动态的,我想将Car id赋予图像名称,并且在Car定义完成之前我无法分配id。那么如何创建一个带有车牌ID的路径?

13 个答案:

答案 0 :(得分:64)

您可以在upload_to参数中使用callable,而不是使用自定义存储。请参阅docs,并注意在调用函数时可能尚未设置主键的警告(因为可以在将对象保存到数据库之前处理上载),因此使用ID可能无法实现。您可能会考虑在模型上使用另一个字段,例如slug。 E.g:

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)

答案 1 :(得分:5)

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 ID。

  

在大多数情况下,此对象不会   已经保存到数据库了,   所以,如果它使用默认的AutoField,   它可能还没有它的价值   主键字段。

答案 2 :(得分:4)

你可以使用如下的lambda函数,请注意,如果instance是new,那么它将没有实例id,所以使用其他东西:

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

答案 3 :(得分:3)

我的解决方案不优雅,但有效:

在模型中,使用需要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值。空=真

答案 4 :(得分:2)

派对很晚,但这个适合我。

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)

答案 5 :(得分:1)

DjangoSnippets有两种解决方案

  1. 两阶段保存:https://djangosnippets.org/snippets/1129/
  2. 预取ID(仅限PostgreSQL):https://djangosnippets.org/snippets/2731/

答案 6 :(得分:1)

您可以覆盖模型的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)

答案 7 :(得分:0)

This guy有办法做动态路径。我们的想法是设置您喜欢的存储并使用函数自定义“upload_to()”参数。

希望这有帮助。

答案 8 :(得分:0)

我找到了一个不同的解决方案,这个解决方案很脏,但它确实有效。您应该创建一个新的虚拟模型,它与原始模型自我同步。我并不为此感到自豪,但没有找到另一种解决方案。在我的情况下,我想上传文件,并将每个文件存储在以模型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中都很新,但对我来说似乎没问题。

另一种解决方案:

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 :(得分:0)

如果模型实例尚未保存到数据库中,则主键(id)可能不可用,我编写了我的FileField子类,它在模型保存上移动文件,以及一个删除旧文件的存储子类。 / p>

存储

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 callable看起来像这样:

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 :(得分:0)

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 :(得分:0)

另一种解决方案:

  useEffect(() => {
    return () => {
      setStartDate(null);
    }
  }, []);

答案 12 :(得分:0)

方法将

def user_directory_path(field_name):

1

在模型中,您可以根据需要拥有任意多个ImageField。例子

照片=模型.ImageField(upload_to = user_directory_path('照片'),null =真,空白=真,)

passport_photo = models.ImageField(upload_to = user_directory_path('passport_photo'),null = True,blank = True,)