使用Django如何强制上传文件名唯一?

70

在使用django上传照片时,如何在服务器上以唯一的文件名重命名照片是最好的方法?我希望确保每个名称仅被使用一次。是否有任何pinax应用程序可以做到这一点,也许使用GUID?


你需要一个Python库来重命名文件,以便它们是唯一的吗? - Aurril
2
请参考@mlissner的答案。现在使用默认的“Storage”类的文件将自动生成唯一的名称,不再需要用户代码。请参阅get_available_name - phoenix
Django默认已经实现了这个功能。只需将所有图像命名为“image.jpg”,从第二个图像开始,它们将自动以类似于“image_XpmEQxy.jpg”的模式进行命名。 - lapin
7个回答

168

使用 uuid。 要将其与您的模型相关联,请参阅 Django 文档 中的 FileField upload_to。

例如,在您的 models.py 文件中定义以下函数:

import uuid
import os

def get_file_path(instance, filename):
    ext = filename.split('.')[-1]
    filename = "%s.%s" % (uuid.uuid4(), ext)
    return os.path.join('uploads/logos', filename)

接下来,在定义您的FileField/ImageField时,将get_file_path指定为upload_to值。

file = models.FileField(upload_to=get_file_path,
                        null=True,
                        blank=True,
                        verbose_name=_(u'Contact list'))

11
@Creotiv 这并不是改变上传路径,而是用 UUID 替换文件名。这不应该引起任何迁移问题。 - Hybrid
4
@Creotiv - 不,迁移只跟踪文件名生成方式。这意味着迁移知道正在使用一个名为 get_file_path 的函数。如果使用了upload_to=uuid4(),那么你所提到的问题就会出现。 - orokusaki
出现 NameError: name 'get_file_path' is not defined :( - NoobEditor
这实际上是一个很好的答案,非常有效 - 即使您想根据模型实例的uniqueId编程方式上传到不同的位置。唯一需要更新的是:str(uuid.uuid4())并将instance替换为“self” - 在模型类内部工作。 - matshidis

20

一个更好的方法是在你的helpers.py中使用一个普通的类。这样,你可以在你的应用程序中重复使用随机文件生成器。

在你的helpers.py中:

import os
import uuid
from django.utils.deconstruct import deconstructible


@deconstructible
class RandomFileName(object):
    def __init__(self, path):
        self.path = os.path.join(path, "%s%s")

    def __call__(self, _, filename):
        # @note It's up to the validators to check if it's the correct file type in name or if one even exist.
        extension = os.path.splitext(filename)[1]
        return self.path % (uuid.uuid4(), extension)

然后在你的模型中,只需导入帮助类即可:

from mymodule.helpers import RandomFileName 

然后使用它:

logo = models.ImageField(upload_to=RandomFileName('logos'))

参考:https://coderwall.com/p/hfgoiw/give-imagefield-uploads-a-unique-name-to-avoid-file-overwrites


12

截至本回答撰写时,似乎不再需要采取任何特殊措施才能实现此目的。如果使用静态upload_to属性设置一个FileField,Django存储系统将自动管理命名,以便在上传重复文件名时,Django会为副本生成一个新的唯一文件名。

在Django 1.10上有效。


2
确认在Django 2.1中仍然有效。这是在默认的Storage类中自动完成的。请参见get_available_name - phoenix
1
适用于django 3.1 - lordvcs

9
在Django 1.6.6、1.5.9和1.4.14之前,get_avaialable_name函数会通过添加下划线自动为文件命名以使其唯一。例如,如果您将一个名为“test.jpg”的文件保存到服务器上,然后再保存另一个名为“test.jpg”的文件,则第一个文件将被称为test.jpg,第二个文件将被称为test_1.jpg。
然而,这实际上是一种DDOS攻击机器的方法,可以发送成千上万个零字节文件进行存储,每个文件都要检查数千个先前的文件以确定其名称。
正如您在文档中看到的那样,新系统在下划线后附加了七个随机数字以解决此问题。

3
您可以编写自己的FileField并覆盖generate_filename方法。
例如:
class UniqueNameFileField(FileField):
    def generate_filename(self, instance, filename):
        _, ext = os.path.splitext(filename) 
        name = f'{uuid.uuid4().hex}{ext}'
        return super().generate_filename(instance, name)

我喜欢这个的原因是,我可以使用它来创建基于时间的路径,进一步使整个路径独特,并且在访问文件系统时不会遇到文件夹子项大小限制。对于大量内容文件,这可能是一个真正的性能问题。非常感谢您添加这个功能! - Harlin

0

你可以将文件名与照片上传的日期/时间连接起来,然后使用hashlib创建消息摘要,这样应该可以得到唯一的文件名。

或者你可以重复使用一个简洁的小片段来创建唯一的文件名,然后使用该文件的完整路径作为哈希调用的输入。这会给你唯一的恒定长度字符串,你可以将其映射到你的文件上。


但是,在同一时间内如何使用相同的名称? - zjm1126
1
你可以将可用磁盘空间的数量添加到哈希字符串中 - 这应该在上传新文件时始终更改。 - Jon Cage
1
@zjm1126 @Jon Cage,你可以发明各种方式来增加它的唯一性,但基本上总会存在某种形式的碰撞概率,而这个想法就是将该概率降至可接受的水平,然后在发生碰撞时进行处理。 - Daniel DiPaolo
确实。我链接的代码片段真的很独特;文件系统会保证只有一个文件可以用相同的名称创建。因此,在全路径上创建哈希应该是相当好的选择。 - Jon Cage

0

Django会自动强制执行唯一的文件名。如果文件已经存在,则会在文件名后添加七个唯一字符。

在Django 2.2上进行了测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接