February 9th, 2010 posted by Bender Rodríguez
I use the Django Photologue and ImageKit projects with my homegrown CMS with some custom views for things like uploading files to an existing gallery. My user base consists of students, educators, and staff at a small liberal arts college, most of whom do not have a high degree of IT prowess. As a result, I found myself in the unenviable position of having to protect them from uploading photos that would not be compatible in a UNIX environment, especially when being served up by Apache2 (i.e. files with spaces in the name). In addition, as there are a number of people who have access to the system, I wanted to prevent one file from overwriting another file with the same name.
I had not yet dealt with the new Django file upload architecture, nor had I ever really mucked about with file uploads in general. At the server level, I knew how the CGI aspect of it worked, and I understood how Django implemented it, but I did not know, for example, if you could rename a file in your view before saving it to the file system. It turns out that you can. Check it out:
if form.is_valid():
upload_form = form.save()
photos = request.FILES.getlist('photos[]')
captions = request.POST.getlist('captions[]')
counter=0
for photo in photos:
filename = photo.name.replace(' ', '')
try:
p = Photo.objects.get(title=filename)
name = filename.split('.')
photo.name = name[0] + "_" +
str(datetime.datetime.now().strftime("%Y%m%dT%H%M%S")) +
"." + name[len(name)-1]
except Photo.DoesNotExist:
photo.name = filename
p = Photo(title=photo.name, caption=captions[counter])
p.original_image.save(photo.name, ContentFile(photo.read()))
upload_form.photos.add(p)
counter = counter + 1
upload_form.save()
Basically, I strip the white space from the file name, and the check to see if the photo already exists in the system. If so, we rename it by appending the current date and time to it. If it does not exist, we move on. The photo is saved and then added to the parent model, which has a many to many relationship with the Photo model. At one point, I was using the commit=False flag on the form.save() method, and then attempting to do the final save after the photos were added to the object, but it turns out that you cannot do so because the object does not yet have a primary key, and the m2m relationship cannot be established.
It turns out, however, that Photologue and ImageKit already have this non-clobbering feature built into the system. If you try to upload a file that has a name of one already on the file system, then it adds an underscore to the end of the file name before the dot extension (i.e. duplicate_.jpg). If you try to upload a file by that name, then a second underscore is added, and so on. Not so elegant, granted, but it does the trick. So now my for loop looks like this:
for photo in photos:
filename = photo.name
photo.name = photo.name.replace(' ', '')
p = Photo(title=photo.name, caption=captions[counter])
p.original_image.save(photo.name, ContentFile(photo.read()))
memory.photos.add(p)
counter = counter + 1
Side note: The view in which the code above resides handles an upload form that I created using the jQuery MultiFile plugin. I was able to dynamically add a textarea to each file that was marked for upload so that the user could submit captions for their photos, and remove the textarea if they removed a photo from the list. Pretty sweet manoeuvre if I do say so myself, but we'll leave that trick for another article.
"No drug, not even alcohol, causes the fundamental ills of society. If we're looking for the source of our troubles, we shouldn't test people for drugs, we should test them for stupidity, ignorance, greed and love of power."
P.J. O'Rourke