How to test image upload on Django Rest Framework

Question:

i’m on a struggle. The problem is with the unit testing ("test.py"), and i figured out how to upload images with tempfile and PIL, but those temporary images never get deleted. I think about making a temporary dir and then, with os.remove, delete that temp_dir, but the images upload on different media directorys dependings on the model, so i really don’t know how to post temp_images and then delete them.

This is my models.py

class Noticia(models.Model):
  ...
  img = models.ImageField(upload_to="noticias", storage=OverwriteStorage(), default="noticias/tanque_arma3.jpg")
  ...

test.py

def temporary_image():
    import tempfile
    from PIL import Image

    image = Image.new('RGB', (100, 100))
    tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg', prefix="test_img_")
    image.save(tmp_file, 'jpeg')
    tmp_file.seek(0)
    return tmp_file

class NoticiaTest(APITestCase):
    def setUp(self):
        ...
        url = reverse('api:noticia-create')
        data = {'usuario': usuario.pk, "titulo":"test", "subtitulo":"test", "descripcion":"test", "img": temporary_image()}
        response = client.post(url, data,format="multipart")

        ...

So, to summarize, the question is, ¿How can i delete a temporary file from different directories, taking into account that those files strictly have to be upload on those directorys?

Asked By: Matias Coco

||

Answers:

For testing you can use the package dj-inmemorystorage and Django will not save to disk. Serializers and models will still work as expected, and you can read the data back out if needed.

In your settings, when you are in test mode, overwrite the default file storage. You can also put any other "test mode" settings in here, just make sure it runs last, after your other settings.

if 'test' in sys.argv :
    # store files in memory, no cleanup after tests are finished
    DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'
    # much faster password hashing, default one is super slow (on purpose)
    PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']

When you are uploading a file you can use SimpleUploadFile, which is purely in-memory. This takes care of the "client" side, while the dj-inmemorystorage package takes care of Django’s storage.

def temporary_image():
    bts = BytesIO()
    img = Image.new("RGB", (100, 100))
    img.save(bts, 'jpeg')
    return SimpleUploadedFile("test.jpg", bts.getvalue())
Answered By: Andrew
  def tearDown(self) -> None:
        self.Noticia.img.delete()
Answered By: sanusi abubakr

I managed to make it work on windows with the following:

from PIL import Image
from io import BytesIO
from django.core.files.uploadedfile import SimpleUploadedFile
from rest_framework import status

def test_noticia_file_upload(self):
    image_data = BytesIO()
    image = Image.new('RGB', (100, 100), 'white')
    image.save(image_data, format='png')
    image_data.seek(0)

    payload = {
        'img': SimpleUploadedFile("test.png", image_data.read(), content_type='image/png')
        ...
    }
    
    response = self.client.post('/api/your_endpoint/', payload, format='multipart')
    # "format='multipart'" is very important to upload file

    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    noticia = Noticia.objects.get(pk=response.data['id']
    self.assertIsNotNone(noticia.img)

    noticia.img.delete(True) # delete the file so it's not store in media/ folder
Answered By: Jeremy