Django: How to replace/overwrite/update/change a file of FileField?
Asked Answered
R

4

14

In Django, I have the following model:

from django.db import models
from django.core.files.base import File
import os, os.path

class Project(models.Model):
    video = models.FileField(upload_to="media")

    def replace_video(self):
        """Convert video to WebM format."""
        # This is where the conversion takes place,
        # returning a path to the new converted video
        # that I wish to override the old one.
        video_path = convert_video()

        # Replace old video with new one,
        # and remove original unconverted video and original copy of new video.
        self.video.delete(save=True)
        self.video.save(os.path.basename(video_path), File(open(video_path ,"wb")), save=True)
        os.remove(video_path)

I want to be able to replace the file in the FileField video on a model object/instance. The above method I've written does not work. Once I delete the original file, I get the following error:

ValueError: The 'video' attribute has no file associated with it.

How can I replace the file with an updated one, and remove the original one (no more necessary)?

Side-Note: I have found a related issue, but with no satisfying answer.

Rameau answered 24/1, 2011 at 21:4 Comment(0)
I
12

You have two choices.

I'll assume your Project model is only a snippet of code.

Option 1 is to break your model down so that a Project does not have a single file, but rather a Project model is associated with a ProjectFile model. Perhaps one-to-many. One Project as many ProjectFiles. That is, ProjectFile has a ForeigKey to Project.

Then you can add new ProjectFile based on an old ProjectFile. You can delete them, and fool around all you want. Indeed, you can keep both ProjectFile's with an indicator of which is "current".

Option 2 is to self.video.open("w") to open the file for writing. Rewrite the contents "in place". Instead of deleting and replacing the file, rewrite the old file with the new content.

with open(video_path ,"rb") as source:
    self.video.open("wb")
    bytes= source.read(4096)
    if bytes: 
        self.video.write( bytes )
        bytes= source.read(4096)

That will probably do what you want.

Yes, it seems inefficient. It's really not that bad. The conversion takes for ever. The copy takes moments.

Imp answered 25/1, 2011 at 0:36 Comment(0)
C
5

I came across this problem recently myself, and solved it something like this:

from django.db import models
from django.core.files.base import File
import os, os.path

class Project(models.Model):
    video = models.FileField(upload_to="media")

    def replace_video(self):
        """Convert video to WebM format."""
        # This is where the conversion takes place,
        # returning a path to the new converted video
        # that I wish to override the old one.
        video_path = convert_video()

        # Replace old video with new one,
        # and remove original unconverted video and original copy of new video.
        old_path = self.video.path
        self.video.save(os.path.basename(video_path), File(open(video_path ,"wb")), save=True)
        os.remove(video_path)
        os.remove(old_path)
Clari answered 12/2, 2013 at 22:50 Comment(2)
Could show where in the docs this functionality pertainsEarldom
This answer is 8 years old now. Unless you're using Django v1 I don't think the docs, or this answer, will help you much. Just in case, here they are.Clari
E
0

Another solution is to delete the original model instance and create a new one.

For example:

def upload_metadata(request):
  if request.method == 'POST':
    try: # removes existing file
      uf = UserFile.objects.get(
        library_id = request.POST['library_id'],
        file = 'uploads/' + request.FILES['file']._get_name())
      uf.delete()
    except Exception as e:
      pass
    # ... continue with form handling as before ...
Earldom answered 17/8, 2021 at 1:22 Comment(2)
but what if you want to keep the value of pk?Inalienable
This solution wouldn't really work well with foreign keys and would require extra handling for those.Orate
F
0

On my side I found that I can just delete the current one before saving the new one when using the Model.objects.

Example:

# Here I create or get the current record (name and version get a unique record)
my_entity, created = MyEntityModel.objects.get_or_create(name=name, version=new_version) 

current_ts = timezone.now()
        
my_entity.description=description
my_entity.updated_at=current_ts
my_entity.updated_by=user_email

if created:
    # update the create meta in DB if new record
    my_entity.created_at=current_ts
    my_entity.created_by=user_email
else:
    # delete the existing file if not newly created
    # file is the FileField in MyEntityModel 
    my_entity.file.delete()

content_file = ContentFile(file_content)
file_name = file_path
my_entity.file.save(file_name, content_file)
                
my_entity.save()
Froufrou answered 31/5 at 13:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.