在 Django 中通过哈希有效地保存文件

Pus*_*uel 6 python django hash

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

1) 将文件写入磁盘

2) 计算哈希

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

问题在于有两种写操作:一种是将文件从内存保存到磁盘以计算哈希值,另一种是将文件实际保存到指定位置时。

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

谢谢。

Ale*_*iew 6

upload_to参数FileField接受一个可调用的,并且从中返回的字符串加入到您的MEDIA_ROOT设置中以获取最终文件名(来自文档):

这也可能是一个可调用的,例如一个函数,它将被调用以获取上传路径,包括文件名。这个可调用对象必须能够接受两个参数,并返回要传递给存储系统的 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
Run Code Online (Sandbox Code Playgroud)

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

你会像这样使用它:

class MyModel(models.Model):
    my_file = models.FileField(upload_to=hash_upload,...)
Run Code Online (Sandbox Code Playgroud)

我还没有对此进行测试,因此您可能需要查看读取整个文件的行(并且您可能只想散列文件的第一个块以防止恶意用户上传大量文件并导致 DoS 攻击)。您可以使用
instance.my_file.read(instance.my_file.DEFAULT_CHUNK_SIZE).

  • 当该字段尚未包含图像时,此方法似乎不起作用。在这种情况下,哈希值似乎是从以前的内容而不是新内容派生的。查看 `save` 方法表明,在调用 `generate_filename` 之前,内容并未绑定到 `FieldFile`。不知道如何从上传函数中获取内容的哈希值……最好的办法可能是继承`FieldFile::save`。 (2认同)

Mel*_*vyn 6

更新了至少 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])
Run Code Online (Sandbox Code Playgroud)