How to update file location in FileField?

The model has a FileField . For each instance of the model, I would like the name of the file on the disk to be updated with the value of another field (let it be called its label ) the model.

I am currently using the custom upload_to() function, which generates the correct file name the first time a new file is uploaded. But if I change the label value, the file name is not updated when the model is saved.

In the save() function of the model, I could (a) calculate the new file name from the label (also checking that the new name would not match another existing file on the disk), (b) rename the file on the disk and (c) set the new file location to FileField But is there an easier way to do this?

+8
django
source share
4 answers

All the solutions posted here, and all the solutions that I have seen on the Internet, include using third-party applications or a solution that you already have.

I agree with @Phillip, there is no easier way to do what you want, even when using third-party applications, this will require some work to adapt it to your goals.

If you have many models that need this, just use pre_save and write this code only once.

I recommend you read Django Signals , I am sure you will find it very interesting.

A 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. 
+4
source share

Here is an application that can take care of it for you django-smartfields . I added a special processor just for this purpose, simply because it seems to be useful functionality.

How it works:

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

It is worth noting that the file will be renamed using file_move_safe , but only in the case of FileSystemStorage, since @Phillip mentioned that you do not want to do this with cloud file storage, because usually these backends do not support file renaming.

Also a couple of notes. You do not need to use the UploadTo class, this will be done by a regular function. If you do not specify keep_orphans , the file will be deleted when you delete the model instance.

 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)) 
+2
source share

Well, it can't be lambda, because lambdas are not serialized 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 pk = 1 s:

slug = 'new-thing' will be //myexample.com/MEDIA_ROOT/1/new-thing/mything.png

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

It is assumed that you have MEDIA_ROOT installed, as the download is downloaded to the media and media address. Serve how you made static files, if during production, name it after MEDIA_URL.

upload_to passes the instance of the object and the file name to your function, from there you can manipulate it.

To change the actual file name, you need to do additional 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 can be very good, add it to the label when creating the shortcut or we will take the old file extension as part of the line:

 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 suggest writing a rename function as part of your app_label.utils

To check if a file exists, simply

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

I think your approach with the save () method is correct and β€œsimple”, except that I would do this using the pre_save signal, instead of overriding the save method (which is often a bad idea).

If this is the behavior you would like to repeat on other models, using the method associated with the pre_save signal also allows you to simply reuse the method.

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

+1
source share

All Articles