Django: move all previously uploaded media files to new location and rename
Question:
So, I’m using Django (1.10) and I’ve got a few hundred uploaded (media) files. I want to move and rename all the files, because I want to slightly change the folder structure.
In my models.py, I do have a perfectly working save() method that saves the files in the correct location, but it only works on new files. I want to move all already-existing files.
I hoped that by just calling the save method on all the objects, it would automatically move and rename the files, but that didn’t do anything (like I kinda expected).
Is there an easy way to do this? I have the feeling I’m overlooking a really straight-forward solution…
Answers:
Ok you will have to do some scripting here. You can use django-extensions here.
https://github.com/django-extensions/django-extensions
You can setup the same in your project
Assuming your model File
Write a script let’s migrate_media_files.py
import os
from django.conf import settings
from shutil import copyfile
def run():
base_dir = settings.BASE_DIR
media_dir = os.path.join(base_dir,'project_name/media')
for file in Files.objects.all():
old_file_path = os.path.join(media_dir, file.image.name)
dir_name = '/{}-{}/{}-{}/'.format(file.theme,file.name,file.id, file.name)
if not os.path.exists(os.path.join(media_dir, dir_name)):
os.makedirs(os.path.join(media_dir, dir_name))
new_file_name = '/{}-{}/{}-{}/{}-{}.txt'.format(file.theme,file.name,file.id, file.name, file.id, file.name)
new_file_path = os.path.join(media_dir, new_file_name)
copyfile(old_file_path, new_file_path)
file.image.url = new_file_name
file.save()
To run :
python manage.py runscript migrate_file
This script mainly plays with files and your objects. You might need changes and testing. All the best.
Actually, there is a better implementation. I assume calling your_model.file_field.save(<filename>, <content>)
(check documentation here) saves it in the right place, right?
The problem is that doing your_model.save()
doesn’t call that or move the current file to the new location. You can, however, save the file again easily like this:
import os
from somewhere.models import YourModel
for your_model in YourModel.objects.all():
file_path = your_model.file_field.path # I'm assuming a local file storage to do this
if os.path.exists(file_path):
# Getting only the file name ("abc.jpg") using python std library
# (file_field.name will also return the folders, relative to media root, we don't want that)
only_name = os.path.split(file_path)[-1]
# you can also open in 'r' if it's text
your_model.file_field.save(only_name, your_model.file_field.open('rb'))
# no need to call your_model.save() because file_field.save() already does that
# now you need to delete the original file...
# if it's saved locally (like in this example), you can do this.
# to be safe, let's check if the new path is working and if
# it's different than the original
new_file_path = your_model.file_field.path
if new_file_path != file_path and os.path.exists(new_file_path):
os.remove(file_path)
And that’s it!
Edit: added the safety check at the end.
So, I’m using Django (1.10) and I’ve got a few hundred uploaded (media) files. I want to move and rename all the files, because I want to slightly change the folder structure.
In my models.py, I do have a perfectly working save() method that saves the files in the correct location, but it only works on new files. I want to move all already-existing files.
I hoped that by just calling the save method on all the objects, it would automatically move and rename the files, but that didn’t do anything (like I kinda expected).
Is there an easy way to do this? I have the feeling I’m overlooking a really straight-forward solution…
Ok you will have to do some scripting here. You can use django-extensions here.
https://github.com/django-extensions/django-extensions
You can setup the same in your project
Assuming your model File
Write a script let’s migrate_media_files.py
import os
from django.conf import settings
from shutil import copyfile
def run():
base_dir = settings.BASE_DIR
media_dir = os.path.join(base_dir,'project_name/media')
for file in Files.objects.all():
old_file_path = os.path.join(media_dir, file.image.name)
dir_name = '/{}-{}/{}-{}/'.format(file.theme,file.name,file.id, file.name)
if not os.path.exists(os.path.join(media_dir, dir_name)):
os.makedirs(os.path.join(media_dir, dir_name))
new_file_name = '/{}-{}/{}-{}/{}-{}.txt'.format(file.theme,file.name,file.id, file.name, file.id, file.name)
new_file_path = os.path.join(media_dir, new_file_name)
copyfile(old_file_path, new_file_path)
file.image.url = new_file_name
file.save()
To run :
python manage.py runscript migrate_file
This script mainly plays with files and your objects. You might need changes and testing. All the best.
Actually, there is a better implementation. I assume calling your_model.file_field.save(<filename>, <content>)
(check documentation here) saves it in the right place, right?
The problem is that doing your_model.save()
doesn’t call that or move the current file to the new location. You can, however, save the file again easily like this:
import os
from somewhere.models import YourModel
for your_model in YourModel.objects.all():
file_path = your_model.file_field.path # I'm assuming a local file storage to do this
if os.path.exists(file_path):
# Getting only the file name ("abc.jpg") using python std library
# (file_field.name will also return the folders, relative to media root, we don't want that)
only_name = os.path.split(file_path)[-1]
# you can also open in 'r' if it's text
your_model.file_field.save(only_name, your_model.file_field.open('rb'))
# no need to call your_model.save() because file_field.save() already does that
# now you need to delete the original file...
# if it's saved locally (like in this example), you can do this.
# to be safe, let's check if the new path is working and if
# it's different than the original
new_file_path = your_model.file_field.path
if new_file_path != file_path and os.path.exists(new_file_path):
os.remove(file_path)
And that’s it!
Edit: added the safety check at the end.