【问题标题】:Django ImageField change file name on uploadDjango ImageField 在上传时更改文件名
【发布时间】:2013-02-14 23:06:52
【问题描述】:

在保存模型“产品”后,我希望上传的图像与 pk 命名相同,例如 22.png 或 34.gif 我不想仅更改名称的图像格式。如何才能做到这一点?到目前为止我的模型示例...

image = models.ImageField(
        upload_to="profiles",
        height_field="image_height",
        width_field="image_width",
        null=True,
        blank=True,
        editable=True,
        help_text="Profile Picture",
        verbose_name="Profile Picture"
    )
    image_height = models.PositiveIntegerField(null=True, blank=True, editable=False, default="100")
    image_width = models.PositiveIntegerField(null=True, blank=True, editable=False, default="100")

【问题讨论】:

    标签: django


    【解决方案1】:

    您可以将函数传递给upload_to 字段:

    def f(instance, filename):
        ext = filename.split('.')[-1]
        if instance.pk:
            return '{}.{}'.format(instance.pk, ext)
        else:
            pass
            # do something if pk is not there yet
    

    我的建议是返回一个随机文件名而不是 {pk}.{ext}。作为奖励,它会更安全。

    发生的情况是 Django 将调用这个函数来确定文件应该上传到哪里。这意味着您的函数负责返回文件的整个路径,包括文件名。以下是修改后的函数,您可以在其中指定上传到哪里以及如何使用它:

    import os
    from uuid import uuid4
    
    def path_and_rename(path):
        def wrapper(instance, filename):
            ext = filename.split('.')[-1]
            # get filename
            if instance.pk:
                filename = '{}.{}'.format(instance.pk, ext)
            else:
                # set filename as random string
                filename = '{}.{}'.format(uuid4().hex, ext)
            # return the whole path to the file
            return os.path.join(path, filename)
        return wrapper
    
    FileField(upload_to=path_and_rename('upload/here/'), ...)
    

    【讨论】:

    • 不,不,不要道歉。我没有发布该功能,而这正是 OP 所需要的。你的答案比我的更好,发布得更快。无论如何,我要 +1。
    • 您正在传递被调用的函数。相反,您应该传递函数本身 - upload_to=path_and_rename, ...
    • 解决方案正在运行,但是当我进行迁移时,我收到一个值错误,提示“找不到包装器”
    • 在实现该新功能时,我收到了Could not find function wrapper。我做错了什么?
    • 对于仍然遇到问题的任何人Could not find function wrapperstackoverflow.com/a/25768034/9493938
    【解决方案2】:

    Django 1.7 和更新版本不会使用这样的功能进行迁移。根据@miki725 和this 票的回答,您需要使您的功能如下:

    import os
    from uuid import uuid4
    from django.utils.deconstruct import deconstructible
    
    @deconstructible
    class UploadToPathAndRename(object):
    
        def __init__(self, path):
            self.sub_path = path
    
        def __call__(self, instance, filename):
            ext = filename.split('.')[-1]
            # get filename
            if instance.pk:
                filename = '{}.{}'.format(instance.pk, ext)
            else:
                # set filename as random string
                filename = '{}.{}'.format(uuid4().hex, ext)
            # return the whole path to the file
            return os.path.join(self.sub_path, filename)
    
    FileField(upload_to=UploadToPathAndRename(os.path.join(MEDIA_ROOT, 'upload', 'here'), ...)
    

    【讨论】:

    • 这也适用于 python 2.7 (Tested Django 1.9) 而接受的答案只适用于 >= 3.0 引发错误Please note that due to Python 2 limitations, you cannot serialize unbound method functions (e.g. a method declared and used in the same class body). Please move the function into the main module body to use migrations. For more information, see https://docs.djangoproject.com/en/1.9/topics/migrations/#serializing-values
    • UploadToPathAndRename 中不需要 MEDIA_ROOT(os.path.join(MEDIA_ROOT... FileField 稍后添加。
    【解决方案3】:

    您可以将分配给upload_to 的字符串替换为docs 中所述的可调用对象。但是,我怀疑在使用 upload_to 参数时主键可能不可用。

    【讨论】:

      【解决方案4】:

      默认情况下,Django 保留上传文件的原始名称,但您很可能希望将其重命名为其他名称(例如对象的 id)。幸运的是,使用 Django 表单的 ImageField 或 FileField,您可以为 upload_to 参数分配一个可调用函数来进行重命名。例如:

      from django.db import models
      from django.utils import timezone
      import os
      from uuid import uuid4
      
      def path_and_rename(instance, filename):
          upload_to = 'photos'
          ext = filename.split('.')[-1]
          # get filename
          if instance.pk:
              filename = '{}.{}'.format(instance.pk, ext)
          else:
              # set filename as random string
              filename = '{}.{}'.format(uuid4().hex, ext)
          # return the whole path to the file
          return os.path.join(upload_to, filename)
      

      在模型领域:

      class CardInfo(models.Model):
          ...
          photo = models.ImageField(upload_to=path_and_rename, max_length=255, null=True, blank=True)
      

      在本例中,每张上传的图片都会被重命名为 CardInfo 对象的主键,即 id_number。

      【讨论】:

        【解决方案5】:

        另一种选择,按照这个答案https://stackoverflow.com/a/15141228/3445802,当我们需要%Y/%m/%d的返回路径时,我们发现了问题,例如:

        FileField(upload_to=path_and_rename('upload/here/%Y/%m/%d'), ...)
        

        所以,我们用这个来处理它:

        FileField(upload_to=path_and_rename('upload/here/{}'.format(time.strftime("%Y/%m/%d"))), ...)
        

        确保模块time已经被导入。

        【讨论】:

          【解决方案6】:

          我确实有 Aidan Ewen 解决方案的更可定制的实现。

          有什么新鲜事?

          • 您可以将要在文件名中使用的字段作为列表发送(按预购)
          • ^ 其中一个必须是唯一的
          • ^ 否则,此列表必须包括(至少一个)唯一一起字段的元组
          • ^ 否则,您发送的字段将被忽略,并将使用 uuid4 作为文件名

          示例 1:

          image = models.ImageField(upload_to=PathAndRename('images/').wrapper)
          
          filename = {pk}.{ext}
          # default is pk for filenames
          

          示例 2:

          name = models.CharField(max_length=20)  # not unique
          image = models.ImageField(upload_to=PathAndRename('images/', ['name']).wrapper)
          
          filename = {uuid4}.{ext}
          # if given fields are did not accepted will use the uuid4
          

          示例 3:

          name = models.CharField(max_length=20, unique=True)
          no = models.CharField(max_length=10)
          image = models.ImageField(upload_to=PathAndRename('images/', ['name','no']).wrapper)
          
          filename = {name}_{no}.{ext}
          # one unique field is enough to use all of the given fields in the filename
          

          示例 4:

          name = models.CharField(max_length=20)  # not unique
          no = models.CharField(max_length=10)  # not unique
          image = models.ImageField(upload_to=PathAndRename('images/', ['name','no']).wrapper)
          
          class Meta:
              unique_together = ('name', 'no')
              # (('name', 'no'),) is acceptable too or multiple unique togethers
          
          filename = {name}_{no}.{ext}
          # if one of the unique together fields exists in the given fields, will use all of the given fields in the filename
          

          我可能忘了再举一些例子,但你可以从下面的代码中理解:

          class PathAndRename:
              """
              fields to use for naming, order is important
              """
          
              def __init__(self, path, fields_to_use=('pk',)):
                  self.path = path
                  self.fields_to_use = fields_to_use
          
              def wrapper(self, instance, filename):
                  # multiple extensions
                  ext = '.'.join(filename.split('.')[1:])
          
                  # check the uniqueness of the fields given for filename
                  if self.is_any_unique_exist(instance):
                      # if any unique field exist in the given list
                      # create filename by using given field values
                      filename = '{}.{}'.format(self.get_filename_by_fields(instance), ext)
                  # else check the existence of at least one unique together
                  elif self.is_any_unique_together_exist(instance):
                      # create filename by using given field values
                      filename = '{}.{}'.format(self.get_filename_by_fields(instance), ext)
                  # if any unique or unique together not exists
                  else:
                      # then create a filename by using uuid4
                      filename = '{}.{}'.format(uuid4().hex, ext)
          
                  # return the whole path to the file
                  return os.path.join(self.path, filename)
          
              def is_any_unique_exist(self, instance):
                  if 'pk' in self.fields_to_use:
                      return True
                  return any([instance._meta.get_field(field).unique for field in self.fields_to_use if hasattr(instance, field)])
          
              def is_any_unique_together_exist(self, instance):
                  if hasattr(instance._meta, 'unique_together'):
                      if isinstance(instance._meta.unique_together, (list, tuple)):
                          for uniques in instance._meta.unique_together:
                              # if any one of the unique together set is exists in the fields to use
                              if all(map(lambda field: field in self.fields_to_use, uniques)):
                                  return True
                      else:
                          if all(map(lambda field: field in self.fields_to_use, instance._meta.unique_together)):
                              return True
                  return False
          
              def get_filename_by_fields(self, instance):
                  return '_'.join([str(getattr(instance, field)) for field in self.fields_to_use])
          

          警告:当您放弃使用这些解决方案时,针对此 upload_to 问题的每个基于方法的解决方案都会对执行的迁移文件造成问题。如果您使用这些解决方案一段时间然后删除它们,旧的迁移将由于这些方法不存在而失败。 (当然,你可以通过修改旧的迁移文件来解决这个问题)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-04-28
            • 2012-12-11
            • 1970-01-01
            • 2021-09-16
            • 2012-09-09
            • 2011-02-10
            • 2013-11-06
            • 2019-01-05
            相关资源
            最近更新 更多