如何在pre_save信号中更改models.FileField?

时间:2013-11-20 05:51:29

标签: python django

我的模型字段定义如下:

file = models.FileField(storage=S3BotoStorage(bucket=blablabla), upload_to='blablabla', max_length=250)

我要做的是抓住file中的pre_save并压缩然后保存压缩版本。

你可能会说我应该在我的观点中这样做,但这是不可能的,因为我使用Django管理站点,我需要在整个代码中保存任何文件,无论是从视图还是Django管理站点压缩。

那么,我如何访问上传的文件,压缩并保存而不是原始文件?

感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

在pre_save信号期间看起来文件的真实名称不是最终的。

我要做的是使用 post_save信号获取最终名称,并将此名称写入另一个地方(表,提交,......)

我还会创建一个 crontab作业(可以是python程序或shell脚本),它在上传文件夹上运行并压缩所有未压缩的文件。
此作业可以根据DB Query(您更新最终名称的位置)决定要压缩哪个文件 它还可以使用“新的压缩文件名”更新一些额外的字段,并带有一个标志,表示该文件已成功擦除)

  • 创建另一个模型来描述文件上传 在模型中,您应该为每个文件包含以下字段(至少)
    “文件名:字符串”,“压缩文件名:字符串”,“was_zipped:boolean”
  • 在为当前模型的post_save期间,更新此新模型的“文件名”
  • 设置一个cron选项卡,使用“was_zipped == none”读取新模型中的所有条目 此Crontab作业将压缩所有这些文件,并在写入新文件后更新模型。

此解决方案可让您将“保管”与主码分开 它还可以使“保存”操作更快(保存时没有拉链)

答案 1 :(得分:1)

帖子 _save信号中更容易做到这一点,如下所示:

@receiver(post_save, sender=MyModel)
def post_save_MyModel(sender, instance, *args, **kwargs):
  filefield = instance.file
  oldname = filefield.name        #gets the "normal" file name as it was uploaded  
  if not oldname:
    return                          #do nothing if file name is missing (no file was uploaded)

  storage = filefield.storage
  #build a unique file name for the file like: newname = '{}.zip'.format(instance.pk)
  newname = <create a new filename with zip extension here>
  if storage.path(newname) == storage.path(oldname):
    return                             #do nothing if file already renamed (avoid reentrancy)

  content = storage.open(oldname,"rb")
  content = <zip content in-memory here>

  storage.delete(newname)           #delete any file with the new name eventually present already
  #save the file field using the file name without directories
  filefield.save(os.path.basename(newname), content, save=True)   #must save model instance too!!!   
  content.close()                   #must close the original content, before deleting it!!!  
  storage.delete(oldname)           #delete any file with the old name eventually present already

需要注意的一点是,在为上传文件创建新名称时,应该为其使用instance.pk值,以便每个实例始终只存在一个文件。 要在删除实例时删除此单个文件,可以使用以下代码:

@receiver(post_delete, sender=MyModel)
def post_delete_MyModel(sender, instance, *args, **kwargs):
    filefield = instance.file
    if filefield.name:                  #only work if file actually present
        filefield.delete(save=False)        #do NOT delete the instance!!!

所有这些都适用于“普通”本地文件存储。我不知道S3BotoStorage,但它应该服从Storage接口,所以它应该有所有必要的方法。

这种方法的一个问题可能是,对于S3,文件名将是由S3本身分配的某种UUID,因此可能无法为文件使用不同的名称,但也可能不需要所有...

无论如何要小心检查重新保存模型实例时被重触的重入(如果确实更改了文件名需要更新)。