[Solved]-How to update a file location in a FileField?

6👍

All solutions posted here, and all of the solutions I’ve seen on the web involves the use of third-party apps or the solution you already have.

I agree with @Phillip, there is no easier way to do what you want, even with the use of third-party apps it would require some work in order to adapt it to your purposes.

If you have many models that needs this behaviour, just implement a pre_save signal and write that code only once.

I recommend you to read Django Signals too, I’m sure you’ll find it very interesting.

Very simple example:

from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=Product)
def my_signal_handler(sender, instance, **kwargs):
    """
    Sender here would be the model, Product in your case.
    Instance is the product instance being saved.
    """
    # Write your solution here.

2👍

Here is an app that can take care of it for you django-smartfields.
I added a special processor just for that purpose, just because it seems like a useful functionality.

How this works:

  • changes the name of the file as specified in upload_to, and
  • FileDependency will take care of the filename whenever label field is changed.

Worth noting is that file will be renamed using file_move_safe but only in case of FileSystemStorage, as @Phillip mentioned you don’t want to do it with Cloud file storage, since usually those backends don’t support file renaming.

Also couple notes. You don’t have to use UploadTo class, regular function will do. If you don’t specify keep_orphans, file will be removed whenever model instance is removed.

from django.db import models
from smartfields import fields, processors
from smartfields.dependencies import FileDependency
from smartfields.utils import UploadTo

def name_getter(name, instance):
    return instance.label

class TestModel(models.Model):
    label = fields.CharField(max_length=32, dependencies=[
        FileDependency(attname='dynamic_file', keep_orphans=True,
                       processor=processors.RenameFileProcessor())
    ])
    dynamic_file = models.FileField(
        upload_to=UploadTo(name=name_getter, add_pk=False))
👤lehins

2👍

I think your approach with the save() method is the right and “simple” one except I would do it using the pre_save signal instead of overriding the save method (which is often a bad idea).

If this is a behavior you would like to repeat on other models, using a method connected to the pre_save signal also allows you to simply re-use the method.

For more information on pre_save: https://docs.djangoproject.com/en/1.8/ref/signals/#pre-save

👤Emma

1👍

Ok, it cannot be a lambda, since lambdas are not serializable for some reason but here’s the easy answer.

def pic_loc(instance, filename):
"""
:param instance: Product Instance
:param filename: name of image being uploaded
:return: image location for the upload
"""
return '/'.join([str(instance.pk), str(instance.slug), filename])


Class Product(models.Model):
    image = models.ImageField(upload_to=pic_loc)
    slug = models.SlugField()
    user = models.ForeignKey(User, related_name="products")

Then to find say pk = 1 with:

slug = ‘new-thing’ would be //myexample.com/MEDIA_ROOT/1/new-thing/mything.png

<img src="{{ obj.image.url }}">

This is assuming you have MEDIA_ROOT set since uploads go to media and media url. Serve up like you did static files if in production, name it after MEDIA_URL.

upload_to passes both the object instance and the filename to a your function, from there you can manipulate it.

To change the actual filename you need to do some extra work in the save() method.

from django.core.files import File

class Product(models.Model):
label = CharField(max_length=255)
...
def save(self, **kwargs):
    # here we use os.rename then change the name of the file
    # add condition to do this, I suggest requerying the model
    # and checking if label is different
    if self.pk:  # Need this to mitigate error using self.pk
        if Product.objects.get(pk=self.pk).label != self.label: 
            path = self.image.path
            rename = '/'.join(path.split('/')[:-1]) + '/' + self.label
            os.rename(path, rename)
            file = File(open(rename))
            self.image.save(self.label, file)
    return super(Product, self).save(**kwargs)

If the file extension is important which it very well may be, either add it to label when creating label or we take the old file extension as part of the string:

filename, file_extention = os.splitext(path)
rename += file_extension  # before renaming and add to label too

os.rename(path, rename)
self.image.save(self.label + file_extension, file)

I would actually suggest writing a rename function as part of your app_label.utils

To check if the file exists is simply

if os.path.isfile(rename):
    # you can also do this before renaming, 
    # maybe raise an error if the file already exists

Leave a comment