USe dynamic destination folder for uploaded file in Django - django

I would like to create dynamically the destination of my uploaded files.
But, it seems that the 'upload_to' option is only available for a models, not for forms. So the following code is wrong.
class MyForm(forms.Form):
fichier = forms.FileField(**upload_to='files/%m-%Y/'**)
In the view handling the uploaded file, the destination is static. How can I make it dynamic ?
Thank you.

class YourFileModel(models.Model)
def upload_path(self, name):
name = do_sth_with_name(name)
folder = generate_folder_name(self.id, self.whatever_field)
return 'uploads/' + folder + '/' + name
file = models.FileField(upload_to=upload_path)
edit after comment
def handle_uploaded_file(file):
# generate dynamic path
# save file to that path
example here http://docs.djangoproject.com/en/dev/topics/http/file-uploads/#handling-uploaded-files
if form from model, override the save() method
class YourForm(forms.ModelForm):
fichier = forms.FileField()
def save(self):
if self.cleaned_data['fichier']:
file = handle_uploaded_file(self.cleaned_data['fichier'])
super(YourForm, self).save()
if not form from model, call the upload handler in your view
def your_view(request):
#####
if form.is_valid():
file = handle_uploaded_file(form.cleaned_data['fichier'])

Instead of a string, supply a callable -- i.e. the name of a function that takes the model instance and a string, and returns the desired name. See FileField docs for specifics. One thing they don't say (at least I can't find it in the docs) is that if the returned filename starts with '/' then it is an absolute path, otherwise it is relative to your /media directory.

Related

Django removes "#" and other special character from file name

I want to upload a file with filename as email of the uploader. I know this is not a OS file system issue, nor a unsupported python operation because I did save images with this format in python. Now I want to save the images in model(Specifically ImageField).
I'm using OverwriteStorage to overwrite images if there is some existing image with same name.
Saving the image
model = Model.objects.create(email=email)
# Blob to image
tempfile_io = io.BytesIO()
img.save(tempfile_io, 'JPEG')
image_file = InMemoryUploadedFile(tempfile_io, None, email + ".jpeg",'image/jpeg',tempfile_io.getbuffer().nbytes, None)
print("email is", email)
model.pic.save(email + ".jpg",image_file)
Model
class Model(models.Model):
email = models.EmailField()
pic = models.ImageField(upload_to="pics", storage=OverwriteStorage())
OverwriteStorage
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, *args, **kwargs):
print("name is", name)
if self.exists(name):
self.delete(name)
return name
But for some reason, I don't get the exact name in OverwriteStorage-get_available_name.
email is xyz#example.com
name is xyzexamle.com.jpg
Notice how the # sign is brutally removed.
Is there any file name string check i need to disable. How can I make Django use the exact file name given(whenever possible)?
You will have to override get_valid_name(name) as documented
get_valid_name(name)
Returns a filename suitable for use with the underlying storage
system. The name argument passed to this method is either the original
filename sent to the server or, if upload_to is a callable, the
filename returned by that method after any path information is
removed. Override this to customize how non-standard characters are
converted to safe filenames.
From source
def get_valid_name(self, name):
"""
Return a filename, based on the provided filename, that's suitable for
use in the target storage system.
"""
return get_valid_filename(name)

how to not display original picture name in Django

I am building a Django project where users can upload pictures. I am wondering what I should do to not show the original picture name.
I want the url to be something like /pic/randomnumber, and when the picture is downloaded from the website, it would have the name randomnumber.jpg. For example, all the pictures on Tumblr have the name tumblr_blabla.jpg.
I think this is something that should be done in models.py, but I am not quite sure how to implement it.
IMO you should write method save in your model
Something like that:
from PIL import Image
import os
class YOURS_MODEL_NAME(models.Model):
photo = models.ImageField(upload_to="photos")
def save(self, miniature=True):
super(YOURS_MODEL_NAME, self).save()
if miniature:
filepath = self.photo.path
image = Image.open(filepath)
new_filepath = filepath.split('.')
new_filepath = '.'.join("HERE YOU CAN ADD EVERYTHING TO PATH TO THIS PHOTO") + "." + new_filepath[-1].lower()
try:
image.save(new_filepath, quality=90, optimize=1)
except:
image.save(new_filepath, quality=90)
photo_name = self.photo.name.split('.')
photo_name = '.'.join("HERE YOU CAN ADD EVERYTHING YOU WANT TO 'PHOTO NAME'") + "." + photo_name[-1].lower()
self.photo = photo_name
self.save(miniature=False)
# remove old image
os.remove(filepath)
The upload_to argument in your Model definition can be a callable function which you use to customize the name of the file. Taken from the Django docs on
FileField (of which ImageField is a subclass):
upload_to takes two arguments: instance and filename, (where filename is the original filename, which you may also chose to ignore).
Something similar to this in models.py should do the trick:
def random_filename(instance, filename):
file_name = "random_string" # use your choice for generating a random string!
return file_name
class SomeModel(models.Model):
file = models.ImageField(upload_to=random_filename)
(this is similar to the answer this question about FileFields).
If you are going down this path, I would recommend that you use either the hash/checksum or date/time of the file upload. Something along these lines should work (although I haven't tested it myself!):
from hashlib import sha1
def unique_filename(instance, field):
filehash = sha1()
for chunk in getattr(instance, field).chunks():
filehash.update(chunk)
return filehash
class SomeModel(models.Model):
file = models.ImageField(upload_to=unique_filename(field='file'))
Hope this helps!

Django form validation, clean(), and file upload

Can someone illuminate me as to exactly when an uploaded file is actually written to the location returned by "upload_to" in the FileField, in particular with regards to the order of field, model, and form validation and cleaning?
Right now I have a "clean" method on my model which assumes the uploaded file is in place, so it can do some validation on it. It looks like the file isn't yet saved, and may just be held in a temporary location or in memory. If that is the case, how do I "open" it or find a path to it if I need to execute some external process/program to validate the file?
Thanks,
Ian
The form cleansing has nothing to do with actually saving the file, or with saving any other data for that matter. The file isn't saved until to you run the save() method of the model instance (note that if you use ModelName.objects.create() this save() method is called for you automatically).
The bound form will contain an open File object, so you should be able to do any validation on that object directly. For example:
form = MyForm(request.POST, request.FILES)
if form.is_valid():
file_object = form.cleaned_data['myFile']
#run any validation on the file_object, or define a clean_myFile() method
# that will be run automatically when you call form.is_valid()
model_inst = MyModel('my_file' = file_object,
#assign other attributes here....
)
model_inst.save() #file is saved to disk here
What do you need to do on it? If your validation will work without a temporary file, you can access the data by calling read() on what your file field returns.
def clean_field(self):
_file = self.cleaned_data.get('filefield')
contents = _file.read()
If you do need it on the disk, you know where to go from here :) write it to a temporary location and do some magic on it!
Or write it as a custom form field. This is the basic idea how I go about verification of an MP3 file using the 'mutagen' library.
Notes:
first check the file size then if correct size write to tmp location.
Will write the file to temporary location specified in SETTINGS check its MP3 and then delete it.
The code:
from django import forms
import os
from mutagen.mp3 import MP3, HeaderNotFoundError, InvalidMPEGHeader
from django.conf import settings
class MP3FileField(forms.FileField):
def clean(self, *args, **kwargs):
super(MP3FileField, self).clean(*args, **kwargs)
tmp_file = args[0]
if tmp_file.size > 6600000:
raise forms.ValidationError("File is too large.")
file_path = getattr(settings,'FILE_UPLOAD_TEMP_DIR')+'/'+tmp_file.name
destination = open(file_path, 'wb+')
for chunk in tmp_file.chunks():
destination.write(chunk)
destination.close()
try:
audio = MP3(file_path)
if audio.info.length > 300:
os.remove(file_path)
raise forms.ValidationError("MP3 is too long.")
except (HeaderNotFoundError, InvalidMPEGHeader):
os.remove(file_path)
raise forms.ValidationError("File is not valid MP3 CBR/VBR format.")
os.remove(file_path)
return args

django-photologue upload_to

I have been playing around with django-photologue for a while, and find this a great alternative to all other image handlings apps out there.
One thing though, I also use django-cumulus to push my uploads to my CDN instead of running it on my local machine / server.
When I used imagekit, I could always pass a upload_to='whatever' but I cannot seem to do this with photologue as it automatically inserts the imagefield. How would I go about achieving some sort of an overwrite?
Regards
I think you can hook into the pre_save signal of the Photo model, and change the upload_to field, just before the instance is saved to the database.
Take a look at this:
http://docs.djangoproject.com/en/dev/topics/signals/
Managed to find a workaround for it, however this requires you to make the changes in photologue/models.py
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = __import__(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
dirname = 'photos'
if hasattr(instance, 'upload_to'):
if callable(instance.upload_to):
dirname = instance.upload_to()
else: dirname = instance.upload_to
return os.path.join(PHOTOLOGUE_DIR,
os.path.normpath( force_unicode(datetime.now().strftime(smart_str(dirname))) ),
filename)
And then in your apps models do the following:
class MyModel(ImageModel):
text = ...
name = ...
def upload_to(self):
return 'yourdirectorynamehere'
You can use the setting PHOTOLOGUE_PATH to provide your own callable. Define a method which takes instance and filename as parameters then return whatever you want. For example in your settings.py:
import photologue
...
def PHOTOLOGUE_PATH(instance, filename):
folder = 'myphotos' # Add your logic here
return os.path.join(photologue.models.PHOTOLOGUE_DIR, folder, filename)
Presumably (although I've not tested this) you could find out more about the Photo instance (e.g. what other instances it relates to) and choose the folder accordingly.

Processing file uploads before object is saved

I've got a model like this:
class Talk(BaseModel):
title = models.CharField(max_length=200)
mp3 = models.FileField(upload_to = u'talks/', max_length=200)
seconds = models.IntegerField(blank = True, null = True)
I want to validate before saving that the uploaded file is an MP3, like this:
def is_mp3(path_to_file):
from mutagen.mp3 import MP3
audio = MP3(path_to_file)
return not audio.info.sketchy
Once I'm sure I've got an MP3, I want to save the length of the talk in the seconds attribute, like this:
audio = MP3(path_to_file)
self.seconds = audio.info.length
The problem is, before saving, the uploaded file doesn't have a path (see this ticket, closed as wontfix), so I can't process the MP3.
I'd like to raise a nice validation error so that ModelForms can display a helpful error ("You idiot, you didn't upload an MP3" or something).
Any idea how I can go about accessing the file before it's saved?
p.s. If anyone knows a better way of validating files are MP3s I'm all ears - I also want to be able to mess around with ID3 data (set the artist, album, title and probably album art, so I need it to be processable by mutagen).
You can access the file data in request.FILES while in your view.
I think that best way is to bind uploaded files to a form, override the forms clean method, get the UploadedFile object from cleaned_data, validate it anyway you like, then override the save method and populate your models instance with information about the file and then save it.
a cleaner way to get the file before be saved is like this:
from django.core.exceptions import ValidationError
#this go in your class Model
def clean(self):
try:
f = self.mp3.file #the file in Memory
except ValueError:
raise ValidationError("A File is needed")
f.__class__ #this prints <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
processfile(f)
and if we need a path, ther answer is in this other question
You could follow the technique used by ImageField where it validates the file header and then seeks back to the start of the file.
class ImageField(FileField):
# ...
def to_python(self, data):
f = super(ImageField, self).to_python(data)
# ...
# We need to get a file object for Pillow. We might have a path or we might
# have to read the data into memory.
if hasattr(data, 'temporary_file_path'):
file = data.temporary_file_path()
else:
if hasattr(data, 'read'):
file = BytesIO(data.read())
else:
file = BytesIO(data['content'])
try:
# ...
except Exception:
# Pillow doesn't recognize it as an image.
six.reraise(ValidationError, ValidationError(
self.error_messages['invalid_image'],
code='invalid_image',
), sys.exc_info()[2])
if hasattr(f, 'seek') and callable(f.seek):
f.seek(0)
return f