Django FileField upload_to assistance - python

I'm new to python and trying to adapt to the OOP of Python. Could someone explain why the following is saving in a folder called 'None'? I want to upload an audio file in the admin page. This file gets stored in its own folder with the 'Vocab name'
class Vocab(models.Model):
module = models.ForeignKey(Modules, on_delete=models.CASCADE)
number = models.CharField(max_length = 250)
name = models.CharField(max_length = 250)
class VocabContent(models.Model):
vocab = models.ForeignKey(Vocab, on_delete=models.CASCADE)
audio = models.FileField(upload_to=vocab.name)
Running the following on shell.
>>> from module.models import Modules, Vocab, VocabContent
>>> vocab = VocabContent.objects.get(pk=1)
>>> vocab.vocab.name
'Numbers'
Numbers is the value i am looking for.

It's probably because the way you reference vocab.name is not defined when your model migration is run. I can't explain precisely why this happens but a solution would be to use a callable as your upload_to to evaluate it at runtime and get the value correctly, much like this other answer: Dynamic File Path in Django
So, for you, you could have something like:
import os
def get_upload_path(instance, filename):
return os.path.join("%s" % instance.vocab.name, filename)
class VocabContent(models.Model):
vocab = models.ForeignKey(Vocab, on_delete=models.CASCADE)
audio = models.FileField(upload_to=get_upload_path) # Important to NOT put the parenthesis after the function name
Which would result in a path that concatenates the vocab.name to your file name for every new file.

Related

remove orphaned file from a model

I have the following model:
class Class(models.Model):
title = models.CharField(max_length = 60)
video = models.FileField(
upload_to = class_files_custom_upload,
validators = [
FileExtensionValidator(['mp4', 'webm', 'mpg', 'mpeg', 'ogv']),
]
)
section = models.ForeignKey(Section, on_delete = models.CASCADE)
created = models.DateTimeField(auto_now_add = True)
class Meta:
verbose_name = 'clase'
verbose_name_plural = 'clases'
ordering = ['created']
def __str__(self):
return self.title
I create an instance of this model, but if I update the video field with another file of any instance, the previous saved file is orphaned and the file takes up space and I want to avoid it, deleting the file.
To do this I customize the file load, putting a callable in the upload_to:
def class_files_custom_upload(instance, filename):
try:
old_instance = Class.objects.get(id = instance.id)
old_instance.video.delete()
except Class.DoesNotExist:
pass
return os.path.join('courses/videos', generate_secure_filename(filename))
In this way I achieve my goal. But I have several models that save multimedia files, and for each one I have to customize the file load, practically doing a function almost equal to class_files_custom_upload, and the code repeats and this is not optimal at all.
I tried to create a reusable function that meets the goal of the class_files_custom_upload function, in various fields like ImageField and FileField, but I can't do it since the function receives only 2 parameters, instance and filename, which is too little data to achieve it.
The only way I managed to create that "function" that meets the goal and is reusable, was to create a validator:
def delete_orphaned_media_file(value):
old_instance = value.instance.__class__.objects.get(pk = value.instance.pk)
media_file_field = getattr(old_instance, value.field.name)
if not media_file_field.name == value.name: media_file_field.delete()
And it works, but after all it is a "validator", a "validator" is supposed to validate a field, not "that". My question is, is it good practice to do this?
Is there a better alternative to my solution? but that this alternative meets the objective of being reusable.
Any suggestion helps my learning, thanks.
One of the problem is that, two or more FileFields can refer to the same file. In the database a FileField stores the location of the file, so two or more columns can have the same file, therefore, just removing the old one is not (completely) safe.
You can for example make use of django-unused-media. You install this with:
$ pip install django-unused-media
Next you add this to the installed apps:
# settings.py
INSTALLED_APPS = [
# …,
'django_unused_media',
# …
]
Next you can run:
python3 manage.py cleanup_unused_media
this will look for files that are no longer referenced, and clean these up interactively.
You can also make a scheduled task (for example with cron), that runs with the --no-input flag:
python3 manage.py cleanup_unused_media --no-input

Unable to read Django FileField?

I am trying to read from Django Filefield, as can be seen in my Django Model:
import os
import win32api
from django.db import models
from custom.storage import AzureMediaStorage as AMS
class File(models.Model):
'''
File model
'''
file = models.FileField(blank=False, storage=AMS(), null=False)
timestamp = models.DateTimeField(auto_now_add=True)
remark = models.CharField(max_length=100, default="")
class File_Version(File):
"""
Model containing file version information
"""
version = models.CharField(max_length=25, default="")
#property
def get_version(self):
"""
Read all properties of the given file and return them as a dictionary
"""
props = {'FileVersion': None}
# To check if the file exists ?
### This returns FALSE
print("Is the file there? ", os.path.isfile(str(File.file)) )
# To get file version info
fixedInfo = win32api.GetFileVersionInfo(str(File.file), '\\')
print("FixedInfo: ", fixedInfo)
But os.path.isfile() keeps returning False. How do I read from FileField, into my custom model ?
And moreover, the line fixedInfo, gives me the error:
pywintypes.error: (2, 'GetFileVersionInfo:GetFileVersionInfoSize',
'The system cannot find the file specified.')
os.path.isfile returns whether the filepath is pointing to a file (as opposed to a directory, for instance). File.file is pointing to a models.FileField object; the current code will always return False. I suppose you would want File.file.path to get the actual absolute filepath for the file.
In your model definition you may add:
class File(models.Model):
file = models.FileField()
...
...
def filename(self):
return os.path.basename(self.file.name)
Or you may try:
from django.core.files.storage import default_storage
Use:
1) FieldFile.name:
The name of the file including the relative path from the root of the Storage of the associated FileField.
2) default_storage.exists(path)
Return True if a file referenced by the given name already exists in the
storage system, or False if the name is available for a new file.
Hope this works!
As you are using a different storage provider for your files, you need to use the methods of that storage provider to query the file object.
os.path.isfile and win32api.GetFileVersionInfo only work for the local file system.

Use model attribute as field parameter

I'm trying to create a folder and upload an image to in it. My model is similar to this:
class XYZ(models.Model):
def code(self):
syms = ['#','#','$','%','&','*','+','-']
return ''.join(x+random.choice(syms) for x in [self.name[-2:],self.name[2:]])
def make_folder(self):
os.mkdir(os.getcwd()+'/XYZ/'+folder)
def save(self):
self.make_folder()
super(XYZ,self).save(*args,**kwargs)
name = models.CharField(max_length=20)
folder = property(code)
image = models.ImageField(upload_to='HELP_HERE')
I've tried using folder, self.folder, property(code) but it says it isn't defined, how do I access this attribute? Is this the correct approach?
I've also set in the configs.py my MEDIA_URL and MEDIA_ROOT
I think a more simple and understandable way to do this would be to avoid using the inner class function and doing something like this:
# Reference this function outside of your model class.
def xyz_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/xyz_<id>/<filename>
return 'xyz_{0}/{1}'.format(instance.id, filename)
Then, on your models, reference the function in your upload_to field like so:
class XYZ(models.Model):
image = models.ImageField(upload_to=xyz_directory_path)
With your media config set up, this should work. There's a really nice and simple explanation about file handling here.

Convert blob to Django ImageField in python?

I read user image in BLOB file, but i want save it to image format in django model.
How can i convert this file to image file(.jpeg) and save it in django models.ImageField?
I use python 2.7 and django 1.9.
my model is:
class Staff(models.Model):
user = models.ForeignKey(User)
cn = models.CharField(max_length=100)
new_comer = models.NullBooleanField()
change_position = models.NullBooleanField()
change_manager = models.NullBooleanField()
acting = models.NullBooleanField()
expelled = models.NullBooleanField()
active = models.NullBooleanField()
avatar = models.ImageField(upload_to='/images/')
Please help me...
You need to try something like this.
import io
from django.core.files.base import File
# Set values of your model filed.
staff_instance = Staff()
staff_instance.user = user_instance
...
...
with io.BytesIO(blob_file) as stream:
django_file = File(stream)
staff_instance.avatar.save(some_file_name, django_file)
staff_instance.save()
I assume that blob is a byte array of the file.
To make it a file i need to convert it to a stream.
Thus I thought BytesIO would be a good choice.
You can directly save file to disk but if you want django to upload it to your upload_to directory, you need to use django.core.files.base.File class.
When you run django.core.files.base() method file will be saved to desired directory.
I guess you will use this for an data migration process, not in a view.
If this is the case than you could put this code at a django command.
Then you can use any django and project related resources.
Let me know if it helps.

Image field related problems while using in django admin

I have a following Banner class. Which is editable by admin.
class Banner(models.Model):
name = models.CharField(max_length = 128)
link = models.TextField(max_length = 450)
image = models.ImageField(upload_to = 'banner_images')
There are two problems.
When saving image it is saved with original file name. I would like to change it with some unique name so that is not clashed when image with the same name is uploaded again in the specified directory.
While updating the image, the first image file must be deleted. It is not happening...
Any suggestion will be helpful. Thanks in advance.
Try something like this:
from os import rename
class Banner(models.Model):
name = models.CharField(max_length = 128)
link = models.TextField(max_length = 450)
image = models.ImageField(upload_to = 'banner_images')
def save(self):
super(Banner, self).save()
new_filename = <insert code here to change name>
self.image.name = new_filename
rename(static_path+'banner_images/'+self.image, static_path+'banner_images/'+new_filename)
super(Banner, self).save()
I'm not sure if the super(Banner, self).save() called is required twice or not. The 1st might be needed to save the file, and the 2nd one to update the DB record.
1) upload_to can be a callable, on save you can modify it's filename (docs)
2) see https://code.djangoproject.com/ticket/6792, you have to delete it yourself,
Since I was having problem related to savings of image through admin I got following solution which answers all my queries...
First I found that even though admin keeps the original file name, if the file with same name already exists, it keeps on appending a count as a suffix to prevent duplicate file name... for example, if same file is uploaded it is stored as image, image_2, image_3 etc...
Second, while changing image through admin, it was not removing the original file. For that I wrote following code in admin.py. And it does the job well...
Code:
class BannerAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
#Remove the previous file if the object is not new
#and new file supplied.
if obj.id != None and len(request.FILES) > 0:
import os
old_obj = m.Banner.objects.get(id = obj.id)
os.remove(old_obj.image.path)
Hope this helps you if you have got the similar problem.

Categories