Temporary files in Django for tests and on the fly file manipulation
Sometimes you need to do some operations on files on the fly in the code and place the result for form validation or save it with a model instance. When you want to write forms tests files may also be needed. To avoid using extra static files for tests you can use some temporary files. All those (and more) can be done with in-memory / temporary file-like objects. On the web there are StringIO or ContentFile usage examples but they wont work always - or as good as Django InMemoryUploadedFile object would. Here is an example of InMemoryUploadedFile usage for tests and other similar cases.
InMemoryUploadedFile is a Django object, used in form file uploads, but it also can be used on its own. Using StringIO.StringIO as a temporary file with recent Django versions may results in exceptions when Django tries to call a methods that doesn't exist. Using ContentFile on a for example image data may result in broken image file. InMemoryUploadedFile is a Django friendly solution for such file operations. I used it in tests and also in thumbnail generation in ckeditor before saving the result via Django File Storage API.
An example usage would look like so:import Image
import StringIO
def get_temporary_text_file():
io = StringIO.StringIO()
io.write('foo')
text_file = InMemoryUploadedFile(io, None, 'foo.txt', 'text', io.len, None)
text_file.seek(0)
return text_file
def get_temporary_image():
io = StringIO.StringIO()
size = (200,200)
color = (255,0,0,0)
image = Image.new("RGBA", size, color)
image.save(io, format='JPEG')
image_file = InMemoryUploadedFile(io, None, 'foo.jpg', 'jpeg', io.len, None)
image_file.seek(0)
return image_file
StringIO.StringIO() is a file-like class allowing to mock real file objects. When creating a temporary text file I use "write" method - like in normal file objects. For temporary image PIL saves the image to the StringIO object. When you have the content in StringIO or in other object you can wrap it around InMemoryUploadedFile. This class takes few arguments: file (file object), field name (when used in forms), file name, mime type, size, charset. When using stand-alone the field name can be "skipped" with None. The same can be done for charset. Setting the file to its start with seek(0) prevents some issues with Django recognizing it as an empty file (Storage API, file upload).
Usage
InMemoryUploadedFile can be used to put a file from some uncommon source and then use it in forms validation, model instances or tests. When using non-local storage apis it can help avoiding writing on local storage before sending files to the storage backend (storage API). In tests such temporary files may be used to make a form pass or fail with an error depending on your needs - and without adding extra static files for that job.
Application tests example
I have a basic "testapp" application with a mode:from django.db import models
class TestModel(models.Model):
image = models.ImageField(upload_to='test/', blank=True, verbose_name='Test')
from django.views.generic.edit import CreateView
from testapp.models import *
class UploadImageView(CreateView):
model = TestModel
success_url = '/'
upload_image = UploadImageView.as_view()
<form action="./" method="post" enctype="multipart/form-data">
{% csrf_token %}
<table>
{{ form }}
</table>
<input type="submit" value="Add" />
</form>
import Image
import StringIO
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.urlresolvers import reverse
from django.test import TestCase
def get_temporary_text_file():
io = StringIO.StringIO()
io.write('foo')
text_file = InMemoryUploadedFile(io, None, 'foo.txt', 'text', io.len, None)
text_file.seek(0)
return text_file
def get_temporary_image():
io = StringIO.StringIO()
size = (200,200)
color = (255,0,0,0)
image = Image.new("RGBA", size, color)
image.save(io, format='JPEG')
image_file = InMemoryUploadedFile(io, None, 'foo.jpg', 'jpeg', io.len, None)
image_file.seek(0)
return image_file
class ImageUploadViewTests(TestCase):
def test_if_form_submits(self):
test_image = get_temporary_image()
response = self.client.post(reverse('upload-image'), {'image': test_image})
self.assertEqual(302, response.status_code)
def test_if_form_fails_on_text_file(self):
test_file = get_temporary_text_file()
response = self.client.post(reverse('upload-image'), {'image': test_file})
self.assertEqual(200, response.status_code)
error_message = 'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'
self.assertFormError(response, 'form', 'image', error_message)
Comment article