简体   繁体   English

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

[英]How can I alter a models.FileField in a pre_save signal?

I have a model field defined like the following:我有一个定义如下的模型字段:

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

What I am trying to do is grabbing the file in a pre_save and compress it then save the zipped version.我想要做的是在pre_save抓取file并压缩它然后保存压缩版本。

You may say I should do that in my views but that is not possible since I am using Django admin site and I need to have any file saved throughout the code whether from the views or Django admin site compressed.您可能会说我应该在我的视图中这样做,但这是不可能的,因为我使用的是 Django 管理站点,并且我需要在整个代码中保存任何文件,无论是来自视图还是压缩的 Django 管理站点。

So, how can I access the uploaded file, compress it and have it saved instead of the original file?那么,如何访问上传的文件,压缩它并保存它而不是原始文件?

It looks like the real name of the file is not final during the pre_save signal.看起来文件的真实名称在 pre_save 信号期间不是最终的。

What I would do is to get the final name using the post_save signal and write this name to another place (table, filed,...)我要做的是使用post_save 信号获取最终名称并将此名称写入另一个位置(表、归档等)

I would also create a crontab job (can be python program or a shell script) that run over the upload folder and zip all uncompressed files.我还将创建一个crontab 作业(可以是 python 程序或 shell 脚本),它在上传文件夹上运行并压缩所有未压缩的文件。
This job can decide which file to zip based on DB Query (Where you updated the final name)此作业可以根据 DB Query(您更新最终名称的位置)决定压缩哪个文件
It is also can update some extra fields with the "new zipped file name" and with a flag saying the file had been ziped successfully)它还可以使用“新压缩文件名”和一个表示文件已成功压缩的标志来更新一些额外的字段)

  • Create another model to describe you file uploads.创建另一个模型来描述您的文件上传。
    in the Model, you should have for every file the following fields (at least)在模型中,您应该为每个文件提供以下字段(至少)
    "file name:string", "zipped file name:string", "was_zipped:boolean" “文件名:字符串”,“压缩文件名:字符串”,“was_zipped:boolean”
  • During post_save for you current Model, update the "file name" for this new Model在当前模型的 post_save 期间,更新此新模型的“文件名”
  • Set a cron tab to read all entries from the new model with "was_zipped==none"设置 cron 选项卡以使用“was_zipped==none”读取新模型中的所有条目
    This Crontab job will zipped all those file and will update the model after writing the new file.此 Crontab 作业将压缩所有这些文件,并在写入新文件后更新模型。

This solution will let you separate the "house keeping" from your main code.此解决方案将让您将“内务管理”与主代码分开。
It will also make the "save" operation faster (no zip during save)它还将使“保存”操作更快(保存期间没有压缩)

It's easier to do it inside a post _save signal, like this:post _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

One point to note is that when creating the newname for the uploaded file, you should use the instance.pk value for it, so that only a single file will always exist for each instance.需要注意的一点是,在为上传的文件创建新名称时,您应该为其使用 instance.pk 值,以便每个实例始终只存在一个文件。 To remove this single file when the instance is deleted, you may use this code:要在删除实例时删除此单个文件,您可以使用以下代码:

@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!!!

All of this will work with a "normal" local file storage.所有这些都适用于“普通”本地文件存储。 I don't know about S3BotoStorage, but it should obey to the Storage interface, so it should have all the necessary methods.我不知道 S3BotoStorage,但它应该服从 Storage 接口,所以它应该有所有必要的方法。

One problem with this method may be that with S3 the file name will be a UUID of some sort assigned by S3 itself, so it may be impossible to use a different name for the file, but it may also be not needed at all...这种方法的一个问题可能是,对于 S3,文件名将是由 S3 本身分配的某种 UUID,因此可能无法为文件使用不同的名称,但也可能根本不需要它。 .

Anyway be careful to check somehow for the reentrancy that is striggered when the model instance is re-saved (needed to update the file name, if it is indeed changed).无论如何,请注意以某种方式检查重新保存模型实例时触发的可重入性(需要更新文件名,如果确实已更改)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM