2

There are several questions around this topic but not one that I've found that suits what I'm trying to do. I want to be able to upload a file to a model and save that file in a nice location using the model instance attributes like pk. I know that this stuff gets set after model.save() so I need to write a custom save to do this, but I can't figure it out. Here is what I have:

class UploadModel(models.Model):
    image = models.ImageField(upload_to='uploads')

    def save(self, *args, **kwargs):

        # Call standard save
        super(UploadModel, self).save(*args, **kwargs)

        if 'uploads' in self.image.path:

            initial_path = self.image.path

            # New path in the form eg '/images/uploadmodel/1/image.jpg'
            new_path = os.path.join(settings.MEDIA_ROOT, 'images', 
                self._meta.model_name, self.pk, os.path.basename(initial_path))

            # Create dir if necessary and move file
            if not os.path.exists(os.path.dirname(new_path)):
                makedirs(os.path.dirname(new_path))

            os.rename(initial_path, new_path)

            # Do something here to save the new file to the image field

            # Save changes
            super(UploadModel, self).save(*args, **kwargs)

What do I have to do to the image field to get it to reference this new file location, and set all the useful attributes to it like image.path, image.name, image.url etc?

The docs say that the above is all I should need to do but this just results in the image field pointing to a file that doesn't exist. I've looked at this related question and tried the snippet mentioned in one of the answers but I have not found a solution yet.

Community
  • 1
  • 1
DrBuck
  • 822
  • 7
  • 22

1 Answers1

3

I figured it out after a lot of searching and finding this old documentation ticket that has a good explanation.

class UploadModel(models.Model):
    image = models.ImageField(upload_to='uploads')

    def save(self, *args, **kwargs):

        # Call standard save
        super(UploadModel, self).save(*args, **kwargs)

        if 'uploads' in self.image.path:

            initial_path = self.image.path

            # New path in the form eg '/images/uploadmodel/1/image.jpg'
            new_name = '/'.join(['images', self._meta.model_name, str(self.id), 
                path.basename(initial_path)])
            new_path = os.path.join(settings.MEDIA_ROOT, 'images', 
            self._meta.model_name, self.pk, os.path.basename(initial_path))

            # Create dir if necessary and move file
            if not os.path.exists(os.path.dirname(new_path)):
                makedirs(os.path.dirname(new_path))

            os.rename(initial_path, new_path)

            # Update the image_file field
            self.image_file.name = new_name

            # Save changes
            super(UploadModel, self).save(*args, **kwargs)

Now I read the docs for this it looks totally obvious :) but I do think the explanation could be made more descriptive. Hopefully this will save someone some time!

DrBuck
  • 822
  • 7
  • 22
  • You should just pass a callable to `upload_to=`, as per https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.FileField.upload_to – janek37 Mar 08 '18 at 13:43
  • Thanks yes that looks like a better solution – DrBuck Mar 16 '18 at 11:29