Skip to content

Instantly share code, notes, and snippets.

@guillaumepiot
Last active January 22, 2024 19:19
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save guillaumepiot/817a70706587da3bd862835c59ef584e to your computer and use it in GitHub Desktop.
Save guillaumepiot/817a70706587da3bd862835c59ef584e to your computer and use it in GitHub Desktop.
Django Rest Framework - Image/File upload test
import os
import io
from PIL import Image
from django.core.urlresolvers import reverse
from django.conf import settings
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework.authtoken.models import Token
from rest_framework.renderers import JSONRenderer
# Custom user model based on Django Auth AbstractUser
from account.models import User
class CrewUploadPhotoTests(APITestCase):
fixtures = []
maxDiff = None
def setUp(self):
# Normal user
self.normal_user = User.objects.create(
first_name="Bob",
last_name="Green",
username="bob@green.com",
email="bob@green.com",
is_active=True,
is_staff=False)
self.normal_user.set_password('demo1234')
self.normal_user.save()
self.normal_token, created = Token.objects.get_or_create(
user=self.normal_user)
def generate_photo_file(self):
file = io.BytesIO()
image = Image.new('RGBA', size=(100, 100), color=(155, 0, 0))
image.save(file, 'png')
file.name = 'test.png'
file.seek(0)
return file
def test_upload_photo(self):
"""
Test if we can upload a photo
"""
self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.normal_token.key)
url = reverse('crew-api:upload-photo', args=[self.normal_user.crew.uuid])
photo_file = self.generate_photo_file()
data = {
'photo':photo_file
}
response = self.client.post(url, data, format='multipart')
self.assertEqual(response.status_code, status.HTTP_200_OK)
@KrzysztofWelc
Copy link

hello, is there any way to delete image after tests? I'm asking, because found out, that all those images are stored in media_cdn folder

@guillaumepiot
Copy link
Author

hello, is there any way to delete image after tests? I'm asking, because found out, that all those images are stored in media_cdn folder

I would recommend to use the tearDown method to delete the image using the filesystem. Your file path could be save in memory against your test (e.g. self.file_path) or you could just delete the upload folder altogether.

Example:

    def tearDown(self):
        if Path(settings.FILE_UPLOAD_PATH).is_dir():
            shutil.rmtree(settings.FILE_UPLOAD_PATH)

@guillaumepiot
Copy link
Author

The status_code would not have to be 201? nice job by the way.

Should be yeah 🙃

@guillaumepiot
Copy link
Author

def generate_photo_file(self):
       file_obj = StringIO()
       image = Image.new("RGBA", size=(50, 50), color=(256, 0, 0))
       image.save(file_obj, "png")
       file_obj.seek(0)
       file_obj.name = "testasdas.png"
       file_obj.size = 1000
       return file_obj
self.client.post(reverse("registration_register"), data={'photo': self.photo()})

In form I'm getting these values
self.cleaned_data.get('photo').size # I'm getting 144 rather than 1000 why?
But
self.cleaned_data.get('photo').name # I'm getting testasdas.png
I didn't get this? Can anyone tell me the reason for this? Also, how can I mock the size of the file?

.size would give you the file size in bytes, what you are looking for here is the image dimension. For that you need an image library such as Python Pillow to load the file and extract those values for you.
https://pillow.readthedocs.io/en/stable/reference/Image.html

Hope that helps and sorry for responding a year too late 🤦‍♂️

@lane-eb
Copy link

lane-eb commented Nov 3, 2020

Cool.
The key is generate_photo_file and I should also make the
avatar = factory.Sequence(lambda n: f"{settings.MEDIA_ROOT}/user_avatar/fake_{n}.jpg") correctly.

@wombstrat
Copy link

Great example, thx

Copy link

ghost commented Nov 7, 2022

You are the best, when i grow up i want to be like you.

@bukowa
Copy link

bukowa commented Jan 16, 2023

Example while testing with raw serializer:

# The same file cannot be used after it has been passed to the RequestFactory, as it probably has been closed. 
# Consider using a factory to create the image.
# import factory
# image = factory.django.ImageField().evaluate(None, None, {})
upfile = BytesIO()
pilimg = Image.new('RGB', (100, 100))
pilimg.save(fp=upfile, format='PNG')
image = SimpleUploadedFile('image.png', upfile.getvalue(), content_type='image/png')

# The default 'content_type' for POST requests in Django's RequestFactory is already 
# the value of 'MULTIPART_CONTENT'.
# While using Django's RequestFactory, we can also set it explicitly, which works.
# However, when using APIRequestFactory, we have to set 'format' to 'multipart' to get the same behavior.
# Trying to set 'content_type' to the value of 'MULTIPART_CONTENT' will not work.

# from django.test.client import RequestFactory
# request = RequestFactory().post(
#     path=None,
#     data={'images': [file], 'name': "test"},
# )

request = APIRequestFactory().post(
    path=None,
    data={'images': [image], 'name': "test"},
    # mandatory for APIRequestFactory
    format='multipart',
)

# By default, DRF serializers do not have access to the request.
# But the serializer needs it to access the request.FILES.
# To simulate what DRF is doing in its request handling, update the request.POST with the request.FILES.
# This is finally passed to the serializer as request.data.
# https://github.com/encode/django-rest-framework/blob/0618fa88e1a8c2cf8a2aab29ef6de66b49e5f7ed/rest_framework/request.py#L274

class Serializer(serializers.Serializer):
    images = serializers.ListField(
        child=serializers.ImageField(),
        allow_empty=False,
    )
    name = serializers.CharField(
    required=True, validators=[lambda x: x == 'test'])


data = request.POST.copy()
data.update(request.FILES)
serializer = Serializer(data=data)
self.assertTrue(serializer.is_valid(), msg=serializer.errors)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment