【问题标题】:Efficiently saving a file by hash in Django在 Django 中通过哈希有效地保存文件
【发布时间】:2015-07-30 18:32:21
【问题描述】:

我正在做一个 Django 项目。我希望用户能够做的是上传文件(通过表单),然后将文件本地保存到自定义路径并使用自定义文件名 - 它的哈希。我能想到的唯一解决方案是使用我正在使用的 FileField 的“upload_to”参数。这意味着什么(我认为):

1) 将文件写入磁盘

2) 计算哈希

3) 返回路径+哈希作为文件名

问题是有两种写操作:一种是从内存中保存文件到磁盘计算hash,另一种是实际保存文件到指定位置时。

有没有办法覆盖 FileField 的保存到磁盘方法(或者我在哪里可以找到幕后发生的确切情况),这样我基本上可以使用临时名称保存文件,然后将其重命名为哈希,而不是它被保存两次。

谢谢。

【问题讨论】:

    标签: python django hash


    【解决方案1】:

    FileFieldupload_to 参数接受一个可调用对象,并且从该参数返回的字符串连接到您的 MEDIA_ROOT 设置以获取最终文件名(来自 documentation):

    这也可能是一个callable,比如一个函数,会被调用来获取上传路径,包括文件名。此可调用对象必须能够接受两个参数,并返回要传递给存储系统的 Unix 样式路径(带有正斜杠)。将传递的两个参数是:

    • instance:定义 FileField 的模型实例。更具体地说,这是附加当前文件的特定实例。在大多数情况下,这个对象还没有保存到数据库中,所以如果它使用默认的 AutoField,它可能还没有主键字段的值。
    • filename:最初赋予文件的文件名。在确定最终目的地路径时可能会或可能不会考虑这一点。

    此外,当您访问model.my_file_field 时,它会解析为FieldFile 的实例,其作用类似于文件。所以,你应该可以像下面这样写一个upload_to

    def hash_upload(instance, filename):
        instance.my_file.open() # make sure we're at the beginning of the file
        contents = instance.my_file.read() # get the contents
        fname, ext = os.path.splitext(filename)
        return "{0}_{1}{2}".format(fname, hash_function(contents), ext) # assemble the filename
    

    替换您想要使用的适当哈希函数。根本不需要保存到磁盘(事实上,文件通常已经上传到临时存储,或者较小的文件只保存在内存中)。

    你会这样使用:

    class MyModel(models.Model):
        my_file = models.FileField(upload_to=hash_upload,...)
    

    我还没有对此进行测试,因此您可能不得不戳读读取整个文件的行(并且您可能只想对文件的第一块进行哈希处理,以防止恶意用户上传大量文件并导致 DoS攻击)。您可以通过
    instance.my_file.read(instance.my_file.DEFAULT_CHUNK_SIZE)获取第一个块。

    【讨论】:

    • 这就是我正在做的事情,但是我没有将文件视为已写入磁盘,而是将其保存(根据文档逐块保存),然后计算哈希值。不知道我可以把它当作已经被保存了。
    • 当该字段尚未包含图像时,此方法似乎不起作用。在这种情况下,哈希似乎是从以前的内容派生的,而不是新的。查看save 方法表明,在调用generate_filename 之前,内容未绑定到FieldFile。不知道如何从上传函数中获取内容的哈希......最好的选择可能是子类FieldFile::save
    【解决方案2】:

    至少 1.10 的更新答案:

    • 您的 instance.my_file_fieldUploadedFile 的实例,而不是类似文件的对象
    • 它无法打开或关闭,只能读取并且可能以块的形式读取
    • 无条件调用 read() 可能会消耗所有可用物理内存

    在以下示例中,实例有一个类方法“get_image_basedir”,因为有几个模型都使用相同的函数,但需要不同的基目录。我把它留在了,因为它是一种常见的模式。 HASH_CHUNK_SIZE 是我自己设置的变量,用于优化磁盘读取(即匹配文件系统的块大小或其倍数)。

    def get_image_path(instance, filename):
        import os.path
        import hashlib
        base = instance.get_image_basedir()
        parts = os.path.splitext(filename)
        ctx = hashlib.sha256()
        if instance.img.multiple_chunks():
            for data in instance.img.chunks(HASH_CHUNK_SIZE):
                ctx.update(data)
        else:
            ctx.update(instance.img.read())
        return os.path.join(base, ctx.hexdigest() + parts[1])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-06
      • 1970-01-01
      相关资源
      最近更新 更多