1
It looks like the real name of the file is not final during the pre_save signal.
What I would do is to get the final name using the post_save signal and write this name to another place (table, filed,…)
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.
This job can decide which file to zip based on DB Query (Where you updated the final name)
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” - During post_save for you current Model, update the “file name” for this new Model
- Set a cron tab to read all entries from the new model with “was_zipped==none”
This Crontab job will zipped all those file and will update the model after writing the new file.
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)
1
It’s easier to do it inside a post_save signal, like this:
@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.
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.
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…
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).