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…

Asked By: Landcross

||

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.

Answered By: Bipul Jain

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.

Answered By: arcstur
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.