Renaming files in Django FileField - django

I know that there is a very similar thread here, but I cannot find the solution to my problem.
I need to rename a file which is save in django models.FileField
I tried this
os.rename(old_path, new_path)
mod.direct_file = File(open(new_path))
mod.save()
And this
mod.direct_file.save(new_path, File(open(old_path)))
os.remove(old_path)
And many other ways, but nothing seemed to help. A new file is created in all ways, however, data in filefield does not change at all.
EDIT: SOLVED
os.rename(old_path, new_path)
cursor = connection.cursor()
cursor.execute("UPDATE mods_mod SET direct_file = %s WHERE id = %s", [new_name, mod.id])
transaction.commit_unless_managed()

I don't think you need to use raw SQL for this. I think you need to rename the file using the os facility, then set the model's FileField name to the new name. Maybe something like:
os.rename(model.direct_file.path, new_path)
model.direct_file.name = new_name
model.save()

new_name = 'photos_preview/' + str(uuid.uuid1())
os.rename(photo.image_preview.path, settings.MEDIA_ROOT + new_name)
photo.image_preview.name = new_name
photo.save()

The current Django documentation states:
"When you access a FileField on a model, you are given an instance of FieldFile as a proxy for accessing the underlying file." See docs for further reading.
Instead of using the Python File object to open the file, you should use FieldFile.open() to open the file, then manipulate the file's path accordingly. Afterward, save the model object, and the changes to the path should persist.

I came across this issue when I had blobs saved into django with no file extension, and I wanted to correct that. Best used when looping over a filtered queryset.
You cannot change instance.picture.path, and trying to access instance.picture.file.* will give an error because accessing it will try to open the old file. Setting instance.picture.name will still not let you access instance.picture.file.*, even after saving.
You can simply set the ImageField object itself to the location and all will work:
(Tested with django 1.10)
import imghdr
import os
from django.db import models
class MyModel(models.Model):
picture = models.ImageField()
instance = MyModel.objects.first()
if os.path.exists(instance.picture.path):
extension = imghdr.what(instance.picture.path)
os.rename(instance.picture.path, instance.picture.path + '.' + extension)
instance.picture = instance.picture.name + '.' + extension
instance.save()

You may use the following:
Suppose 'obj' is the django object that you want to rename. Then do this:
obj.file_field_name.name = new_name
obj.save()

It seems changing filename of a binary in FileField is pretty unflexible according to the django docs. It contains the path from Media root.
That points out to a name attr that is reflecting the path, not just the filename itself. Docs: This is the way that django model can find the file

Related

Adding file upload widget for BinaryField to Django Admin

We need to store a few smallish files to the database (yes, I'm well aware of the counterarguments, but setting up e.g. FileField to work in several environments seems very tedious for a couple of files, and having files on the database will also solve backup requirements).
However, I was surprised to find out that even though BinaryField can be set editable, Django Admin does not create a file upload widget for it.
The only functionality we need for the BinaryField is the possibility to upload a file and replace the existing file. Other than that, the Django Admin fulfills all our requirements.
How can we do this modification to Django Admin?
You will want to create a custom Widget specifically for BinaryField which has to read the file contents before putting them into the database.
class BinaryFileInput(forms.ClearableFileInput):
def is_initial(self, value):
"""
Return whether value is considered to be initial value.
"""
return bool(value)
def format_value(self, value):
"""Format the size of the value in the db.
We can't render it's name or url, but we'd like to give some information
as to wether this file is not empty/corrupt.
"""
if self.is_initial(value):
return f'{len(value)} bytes'
def value_from_datadict(self, data, files, name):
"""Return the file contents so they can be put in the db."""
upload = super().value_from_datadict(data, files, name)
if upload:
return upload.read()
And then you need to use it in admin in the following way:
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'widget': BinaryFileInput()},
}
fields = ('name', 'your_binary_file')
Note:
BinaryField doesn't have a url or a file name so you will not be able to check what's in the db
After uploading the file you will be able to see just the byte size of the value stored in the db
You might want to extend the widget to be able to download the file
by reading it's contents

Python & Django, UUID not unique

I'm developing an application using Django. I have a form in which the user uploads 3 different files (at least one). Then those files are sended to a home script that generates some result files. I want to store all those files in one directory, each directory name unique by form submission. I've look around on the Internet and I find the UUID technology. I installed the module of Django named django-uuid-upload-path. But when I submit my form, it is always the same uuid that is returned, looking like a UUID string. Here is my model where I'm using this module :
from django.db import models
from uuid_upload_path import uuid
class Analysis(models.Model):
uidDir = uuid()
dirFile = 'documents/%Y/%m/%d/' + str(uidDir)
structureFile = models.FileField(upload_to = dirFile)
I've tried to use upload_to from this module in this way :
from uuid_upload_path import upload_to
class Analysis(models.Model):
structureFile = models.FileFiels(upload_to = upload_to)
I've done this for my 3 FileFields and it gave me 3 different UUID on one form submission. The problem is now that my files are not in the same directory.
Here is my controller, where I upload the files submitted by the user :
def analysis(request):
if request.method == 'POST':
documents = Analysis.objects.all()
form = AnalysisForm(request.POST, request.FILES)
if form.is_valid():
newdoc = Analysis(structureFile = request.FILES['structureFile'])
newdoc.save()
I've tried with the uuid module from Python but I got the same problem. I've tried to refresh the web page and to delete the cookies but nothing worked.
P.S : I'm using Safari on OS X 10.9.4.
Thanks in advance
I think you should't hardcode your UUID in model section. Django ORM will use it once while creating database.
In your model you should keep field with UUID, but this UUID should be generated in views.py while uploading files.
So there are steps, how upload should look like:
You feel in form and send it.
Your controller gets the form with data.
In your controller you generate UUID
Your controller saves data in database (and there you should store path to files along with generated UUIDs)
From the upload_to documentation :
This may also be a callable, such as a function, which will be called
to obtain the upload path, including the filename. This callable must
be able to accept two arguments, and return a Unix-style path (with
forward slashes) to be passed along to the storage system. The two
arguments that will be passed are:
What you need to do is create this uuid function and pass it to upload_to.
I found the answer on the mailing list of Django. Here is how you get the value of a field :
def dir_file(analysis, file_name): # should live outside your model
return 'documents/%s/%s' % ( analysis.uuidDir, file_name)
In the model, you put :
uuidDir = models.CharField(max_length = 36)
structureFile = models.FileField(upload_to = dir_file)
Now my directory are UUID and generated at each form submission !

upload_to attribute seems not to be used while saving a FileField

I can't get my FileField's url set to what I want.
My model is defined by
class MyModel(models.Model):
pdf_file = models.FileField(upload_to="reports", null=True, blank=True)
# more stuff
and I create an instance using:
myModel = MyModel()
myModel.pdf_file = "some_file.pdf"
myModel.save()
myModel.pdf_file.url returns <MEDIA_URL>/some_file.pdf, while I would expect it to be <MEDIA_URL>/reports/some_file.pdf, because of the upload_to attribute.
What am I missing?
EDIT
I first tried to set a File object instead of a string but it duplicates my file with a _<duplication_num> appended to it, so I first create my file in a tmp folder, and delete it:
myModel.pdf_file = File(open(TMP_FILE_PATH + filename))
myModel.save()
# now that the file is saved to its final location, delete tmp
filepath = os.path.abspath(TMP_FILE_PATH + filename)
os.remove(filepath)
upload_to is used for uploading, You're assigning the string name directly. upload_to takes action only when you create a FileField object (by uploading from a form).
You can read the documentation here
upload_to is a directory relative to your project root where the files you upload are meant to be stored. But you are not assigning it a file, you are assigning it a string, which seems to be causing your FileField to assume you have a file named some_file.pdf in your MEDIA_ROOT.
Repeat: assigning a filename (string) makes FileField to ignore the path defined in upload_to and takes the given string as the real path.
Good luck :)

Overwrite over a file and delete old model's object if the file exists

I have made something to overwrite a file already uploaded with :
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name):
if self.exists(name):
os.remove(os.path.join(settings.MEDIA_ROOT, name))
return name
But my file is in a model :
class Work (models.Model):
file = models.FileField(storage=OverwriteStorage(), upload_to=path)
group = models.ForeignKey(Group, related_name='work_list')
And the new upload makes a new enter, so I have :
A model without file (that bug when I ask file.size..)
My new model
How can I remove my model when my file is deleted?
I have tried to change again FileSystemStorage process but I can't use any argument (said in doc and tested for hours ;)), I have tried to change save process too, but I didn't succeed..
A few things to check before continuing to troubleshoot:
Have you set the MEDIA_ROOT and MEDIA_URL in the settings.py file?
Check this link for more info on Managing files
I would take advantage of Django's built-in file storage rather than building something from scratch
My solution :
for work in groupwork : #It is the list of work associate with my group
try :
path = work.file.path.lstrip(SITE_ROOT+'/'+MEDIA_ROOT+'/').rstrip(request.FILES['file'].name)
deletedwork = groupwork.get(file=path+request.FILES['file'].name)
deletedwork.delete()
except:
pass

How to use primary key id in the name of uploaded file/image?

I need to generate name of uploaded image with mask
images/{{ uploaded_image.id }}.jpg
I think the best way to do this is FileField.upload_to property, but it is written Here:
In most cases, this object will not have been saved to the database yet, so if it uses the default AutoField, it might not yet have a value for its primary key field.
May be in my case it will be better to rename file after saving object in save() method?
What I do is give upload_to a function, then assign a UUID is the function for it to save to:
import uuid
def get_upload_to(instance, filename):
instance.uuid = uuid.uuid4().hex
return 'blah/%s/%s' % (instance.uuid, filename)
class Photo(models.Model):
file = models.FileField(max_length=200,upload_to=get_upload_to)
uuid = models.CharField(max_length=32)
To avoid concurrency issues, you will need to store your object to the database before you determine the filename (because even if you get the next incremental id, which is possible, it may change while you save the file.) So store the object and then rename the file.
Thanks, Blixt and Todd! I choose better solution for my issue:
import time
class Image(models.Model):
def _get_upload_to(instance, filename):
return 'images/%f.jpg' % time.time()
original = models.ImageField(upload_to=_get_upload_to, ....)
It is guarantees that every filename will be unique in the same directory. In my case I need only jpg files, so in other cases it possible to substitute original extension