Django Image field
For the ImageField to work it's necessary to have the Pillow library installed.
Django Models
The setup of the model containing the image field can be done in the following way.
class BookInformation(models.Model):
#...
cover_image = models.ImageField(upload_to="covers/", null=True)
#...
upload_to
option corresponds to the path in which the images are stored/saved in the media folder.
In this case the path will be .../library_project/media/covers/
.
Factory Boy mock data
The easiest way to generate mock images data with factory boy is with the factory.django.ImageField()
method just like the example below.
class BookInformationFactory(factory.django.DjangoModelFactory):
#...
cover_image = factory.django.ImageField(color=factory.Faker("color"))
#...
Note that the color
option can be Hardcoded with something like color="blue"
and that will result in the
generation of images with the color blue. In this case, factory.Faker("color")
will generate random color names
which will result in the generation of images with diferent colors for each cover_image
.
Django Model Serializer
Considering the previous factory, here is the correspondent serializer for POST and PUT requests.
class BookInformationCreateUpdateSerializer(serializers.ModelSerializer):
cover_image = serializers.ImageField(required=False, allow_empty_file=True)
class Meta:
model = BookInformation
fields = [
#... ,
"cover_image",
#... ,
]
API View
class BookInformationViewset(viewsets.ModelViewSet):
queryset = BookInformation.objects.all()
serializer_class = BookInformationCreateUpdateSerializer
ImageField Views Tests
To test the views that involve the cover_image
attribute you can simply generate an image based on the
implemented factory for the cover image just like the example below.
from PIL import Image
import shutil
from ..fixtures import REMOVE_MEDIA_FILES_PATH
class BookCreateViewTestCase(APITestCase):
def setUp(self):
#...
self.data = {
"cover_image": #...
#...
}
def tearDown(self):
shutil.rmtree(REMOVE_MEDIA_FILES_PATH)
def test_create_with_image(self):
cover = BookInformationFactory.create().cover_image
self.data["cover_image"] = cover
res = self.client.post(self.url, self.data, format="multipart")
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
self.assertEqual(
BookInformation.objects.first().cover_image.read(), BookInformation.objects.last().cover_image.read()
)
def test_create_with_image_failure(self):
self.data["cover_image"] = Image.new(mode="RGB", size=(20, 20))
res = self.client.post(self.url, self.data, format="multipart")
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
It is crucial that the format
option in set to "multipart"
for the test to work.
It is also important to have the tearDown
method implemented in order to delete the images
saved on the folder refered previously (.../library_project/media/covers/
).
ImageField Views Tests using temp files
Other way of implementing a view test that includes an ImageField is with the SimpleUploadedFile()
method.
The code below will generate an Image with the PIL Library, save it in a temporary file,
process it with SimpleUploadedFile()
method and then make the POST request.
In this case the tearDown method still needs to be implemented, since the POST request will save the image in the media folder.
from django.core.files.uploadedfile import SimpleUploadedFile
from io import BytesIO
from PIL import Image
import shutil
class BookCreateViewTestCase(APITestCase):
def setUp(self):
self.data = {
"cover_image": #...
#...
}
def tearDown(self):
shutil.rmtree(REMOVE_MEDIA_FILES_PATH)
def test_create_with_image(self):
base_image = Image.new(mode="RGB", size=(20, 20))
tempFile = BytesIO()
base_image.save(tempFile, format="JPEG")
tempFile.seek(0)
with tempFile as temp:
read_image = SimpleUploadedFile("sample.jpg", temp.read(), content_type="image/jpeg")
self.data["cover_image"] = read_image
res = self.client.post(self.url, self.data, format="multipart")
read_image.seek(0)
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
self.assertEqual(BookInformation.objects.first().cover_image.read(), read_image.read())