Update 2015-01-01: As of Django 1.0 this essay is no longer necessary. Please see Django File Uploads.
This tutorial provides a concrete example of file and image uploads in Django, with a live example.
Actually, two examples. The image viewer example demonstrates file/image uploads with newforms and dynamically serving images. The picture upload example covers saving files/images with a model, as well as some validation techniques.
Both examples deal with images, because images and files are handled very similarly, with images having just a few extra behaviors. If you can work with images in Django, then you can work with other files.
Image Viewer Form
Django's documentation includes a step-by-step instructions for the first step, building a multi-part form. I'll detail the specifics of the image viewer example here and leave the general case to the documentation.
Django newforms provides FileField and ImageField classes, which can be used for file uploads like so:
import django.newforms as forms
class ImageViewerForm(forms.Form):
image = forms.ImageField()
The ImageViewerForm is then rendered inside a multi-part form in the tempate:
<h1>Select an Image to View</h1>
<form enctype="multipart/form-data"
method="post"
action="/example/image_viewer/">
{{form.as_p}}
<input type="submit" value="View">
</form>
The interesting part is the view. But first, a quick word about best practices. These examples use the same URL to both show the initial form and handle the submitted form. They distinguish these two cases by checking if the request is a GET or a POST. This is convenient, but is considered unsafe because the user can POST the same data twice by accident by hitting the back button. So, you should never use this idiom unless you know that repeated POSTs are harmless. Instead, send the POST to a seperate URL and redirect back to the original page.
Ok, here's the view function I'm using:
def image_viewer(request):
'''
form to upload an image.
shows you the image upside-down on POST.
does not save the image.
'''
if request.method != 'POST':
# for GET, just show the empty form
form = ImageViewerForm()
else:
form = ImageViewerForm(request.POST, request.FILES)
if form.is_valid():
uploadedImage = form.cleaned_data['image']
# the cleaned_data of a FileField is an
# UploadedFile object. It's a small data container
# with no methods and just two properties:
filename = uploadedImage.filename
imageData = uploadedImage.content
#just for fun, let's use PIL to flip the image
# upside-down and change to PNG format.
imageData = flip(imageData)
# note the mimetype; we're returning the image directly.
return HttpResponse(imageData, mimetype="image/png")
return render_to_response('example/image_viewer.html',
Context(dict(form=form) ) )
Notice that in addition to binding the ImageViewerForm to the POST data, We're also passing in the optional second parameter and binding the FILE data from our multi-part form.
Notice also that when we validate the form, we get an UploadedFile
object back in cleaned_data
. (The same class is used for images as for other files.) This class has no methods, and is only used within the newforms
module. As far as I can tell, it's only used in this one place.
If the form was valid and we were able to get a valid UploadedFile
, the image viewer example simply sends the same image back to you in PNG format. (Or almost the same image; it gets flipped upside-down, which is something I'll come back to latter.) It's also worth knowing that Django will validate ImageFields
by loading them with PIL, and field validation will fail if the file is not a well-formed image.
In this example, I coerce the image format to PNG, so I always use 'image/png' for the mimetype. If you want to serve dynamic images in any format, you can use Python's standard mimetypes
module to guess the mimetype from the filename.
This image viewer is pretty basic, but it's nice to get the form sorted out before we tackle saving files/images to disk. I recommend that you get the equivalent of the image viewer working on your own site before trying the next step. Doing so, you might discover that you don't have PIL installed on your machine. My first recommendation would be to get it; it's a great library. But it's not required if you only want to work with non-image files. If you're following along on your own deployment, you can use FileFields
, upload plain text files, and use a mimetype of 'text/plain' for the echo.
Preparing To Save
Now that we can send and receive files and images over HTTP, we can work on saving and serving them. Before we do that, we need to do some prep work.
Although we'll be using Django models to save files, Django only puts the filename in the database: the file itself is stored in the filesystem. You'll either have your server serving static content from some directory, or, for development purposes, be using Django's static serve. Make sure the directory has group write permissions, then tell Django where it is and how to refer to it in settings.py
:
MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'static/media')
MEDIA_URL = '/static/media/'
MEDIA_ROOT
has to be an absolute path, but it's generally a good idea to define all paths relative to PROJECT_ROOT
to ease deployment.
These settings won't work for you, of course: you'll need to set up the mapping in Apache (or your server of choice.)
Picture Upload
If you haven't already, check out the picture upload example. It has a simple Picture
model:
class Picture(models.Model):
'a picture with a caption.'
picture_id = models.AutoField(primary_key=True)
image = models.ImageField(upload_to='example')
caption = models.CharField(max_length=100)
Note the upload_to
argument. This is the sub-directory under the main root where the image file will actually be stored. What actually gets stored in the underlying database column will be something like 'example/image.png'.
class PictureUploadForm(forms.Form):
image = forms.ImageField()
caption = forms.CharField(max_length=100)
def clean_image(self):
' reject large images. '
max_size = 10**5
if len(self.cleaned_data['image'].content) > max_size:
raise forms.ValidationError(
'Image must be less then %d bytes.' % max_size
)
else:
return self.cleaned_data['image']
The meat of the form is just the first two lines: the rest is simply validation. Here, we limit uploads to reasonably small images. Note that this validation occurs after the full file has been loaded into memory. It's also worth knowing that Django validates image fields by having the PIL try to parse them out. PIL should be able to detect all major image formats. This happens independently of the image's filename, though.
Finally, we have the supporting view:
def picture_upload(request):
'''
form to upload an image together with a caption.
saves it as a Picture in the database on POST.
shows the last uploaded picture and let's you upload another.
'''
picture = None
if request.method != 'POST':
form = PictureUploadForm()
else:
form = PictureUploadForm(request.POST, request.FILES)
if form.is_valid():
# an UploadedFile object
uploadedImage = form.cleaned_data['image']
caption = form.cleaned_data['caption']
# limit to one database record and image file.
picture, created = Picture.objects.get_or_create(picture_id=1)
if not created and picture.get_image_filename():
try:
os.remove( picture.get_image_filename() )
except OSError:
pass
# save the image to the filesystem and set picture.image
picture.save_image_file(
uploadedImage.filename,
uploadedImage.content
)
# set the other fields and save it to the database
picture.caption = caption
picture.save()
# finally, create a new, empty form so the
# user can upload another picture.
form = PictureUploadForm()
return render_to_response(
'example/picture_upload.html',
Context(dict( form=form, last_picture=picture) ) )
There are a couple of things to pay attention to here:
- In case it's not clear, I'm allowing only one picture record and one image file to exist at any time (as a security precaution.)
-
As before, remember to pass request.FILES in when instantiating Form objects with `FileFields
or
ImageFields'. -
Also as before, remember that
cleaned_data
will contain an UploadedFile object. -
Saving the model to the database does not automatically save associated files. You need to call the
save_<fieldName>_file()
method on the model to save the associated file. -
likewise when deleting or updating model objects, you need to clean up any files yourself, as I've done here with
os.remove()
.
In your own implementation, you might consider moving the code to save and remove files to the Model class. Doing it in the view is probably not good practice.
Using Magic Model Methods
Because files and images are more complex than other fields, Python Model's are automatically given additional methods for each of these fields. This is documented here. The get_*_*()
methods can be used inside templates using the auto-call feature, and this is essential to using images in your HTML:
Location: {{last_picture.get_image_filename}}
<br/>
Size: {{last_picture.get_image_height}}x{{last_picture.get_image_width}}
<br/>
<div style="text-align:center;">
<img src="{{last_picture.get_image_url}}" alt="recently uploaded image."/>
</div>
A more realistic example would set the image's width and height attributes so page layout could be performed before the image was fully loaded.
Thanks
I hope these examples complement the documentation by showing how all the pieces fit together in a concrete way. Django's documentation covers each aspect in depth, and that's the first place you should look if you run into questions. Class is over, but I'm going to stick around for a few minutes to talk about the PIL.
Extra Credit
The image viewer example used the PIL to manipulate an image in memory. This is a neat trick, so let me show you how it's done.
Let's start with the source code:
def flip(imageData):
'flip an image and convert it to PNG in-memory.'
from PIL import Image
from cStringIO import StringIO # standard in-memory file class
# create a PIL.Image using an in-memory file
image = Image.open( StringIO(imageData) )
# flip the image
image = image.transpose(Image.FLIP_TOP_BOTTOM)
# open an empty in-memory file and write the image in PNG format.
outFile = StringIO()
image.save(outFile, 'png')
return outFile.getvalue()
The obstacle is that the PIL seems to only want to load and save images to files. The solution is to use in-memory files, provided in Python through the StringIO
and cStringIO
modules. The modules have identical interfaces, but cStringIO
is implemented as a C-extension of Python and is supposedly faster.
Anyway, if you ever find that you have image data stored in a string, simply wrap it in a StringIO
object, and you'll be able to treat it as an open file object. Likewise, if you find an API that wants to write out to a file object, give it a StringIO
object and the string value of the file will be available through getvalue()
.
I used only a simple transposition()
on the image, but the PIL Handbook documents all the possible transformations.
- Oran Looney March 24th 2008
Thanks for reading. This blog is in "archive" mode and comments and RSS feed are disabled. We appologize for the inconvenience.