5

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?

Matias Coco
  • 351
  • 3
  • 9
  • try calling close() after posting data is complete as in this example https://docs.python.org/3/library/tempfile.html#tempfile.mktemp – Pavan Kumar T S May 19 '21 at 17:45
  • Hey Pavan, i tried but same result, tempfile just dont want to get deleted. – Matias Coco May 19 '21 at 17:58
  • whats the platform/os – Pavan Kumar T S May 19 '21 at 17:59
  • I'm using windows 10 – Matias Coco May 19 '21 at 18:13
  • did try adding delete=True – Pavan Kumar T S May 19 '21 at 18:16
  • I did. You know any way of testing image upload and then delete those images uploaded? – Matias Coco May 19 '21 at 18:23
  • you want to delete test images that you post to api or the once that are saved at the backed server. – Pavan Kumar T S May 19 '21 at 18:25
  • Yes, i want to delete the test images that are saved on the backend. Those files are saved inside folder "media/noticias" and all those test images start with a prefix of "**test_img**", i tried searching all files that starts with "test_img" with **glob** and **os.remove**, but nothing happend. – Matias Coco May 19 '21 at 18:29
  • 1
    when do want to delete them. if you want to delete after calling modelobject.delete() with Noticia. then you could use post_delete signal like in https://stackoverflow.com/a/33081018/7887883 – Pavan Kumar T S May 19 '21 at 18:36
  • O my god that worked. I was really simple and i realise everytime a user delete the object, the img doesn't. Also i realised that the tempfiles closed with the **file.close()** but when you upload the img, the tempfile goes the that particular directory so it's deleted in the tempfiles of windows but not in the object media directory. Thanks Pavan! – Matias Coco May 19 '21 at 19:15
  • I thought you are facing issue as in https://github.com/jisaacks/GitGutter/issues/284. .but anyway glad it helped. happy coding – Pavan Kumar T S May 19 '21 at 19:22

3 Answers3

3

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())
Andrew
  • 8,322
  • 2
  • 47
  • 70
  • Great!, thanks, but i **pip install dj-inmemorystorage** succesfully and when i tried to add **'dj-inmemorystorage'** to the INSTALLED_APPS and then makemigrations it says _no module named dj-inmemorystorage_. The other question is, ¿How i have to import **SimpleUploadedFile**? – Matias Coco May 20 '21 at 01:41
  • Where did you read that you should add it to 'installed apps'? Its not a django app, its just a library you can use, like Pillow. The github page linked to contains all the instructions for using. – Andrew May 20 '21 at 08:24
  • `from django.core.files.uploadedfile import SimpleUploadedFile`. If you google the class name this is the first result. https://docs.djangoproject.com/en/2.2/_modules/django/core/files/uploadedfile/ – Andrew May 20 '21 at 08:26
  • True, yesterday i coded for many hours and my brain was frozen. If you asked me to sum 2 plus 2, probably i would said 5. It works. Thank you a lot @AndrewBacker – Matias Coco May 20 '21 at 12:15
  • Andrew sorry for asking so much questions, but test unit is just so much faster with that PASSWORD_HASHERS, why i shouldn't use that as the default HASHER?, maybe is not secure so for tasting is great. Just asking, i was sorpised. – Matias Coco May 20 '21 at 12:20
  • DO NOT USE IN PRODUCTION. MD5 was declared "broken" ~15 years ago, for good reason. It is easy to generate collisions: if you know a hash you can quickly generate another password with the same hash value. New ones are designed to be slow, so doing that is effectively impossible, plus they have some other nice features. https://security.stackexchange.com/questions/52461/how-weak-is-md5-as-a-password-hashing-function – Andrew May 21 '21 at 07:41
1

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
Jeremy
  • 11
  • 2
-1
  def tearDown(self) -> None:
        self.Noticia.img.delete()
blackgreen
  • 34,072
  • 23
  • 111
  • 129
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 17 '22 at 17:47