在Django REST框架中测试图像上传



我正试图在Django项目中的一个应用程序中添加一些测试。此应用程序包括Image型号:

from django.db import models
from django.utils.translation import gettext_lazy as _
from cecommerce.image_mappings import ImageAgnosticMapping
from cecoresizer.fields import ResizableImageField

class Image(models.Model):
file = ResizableImageField(
_("Image"),
max_length=300,
image_config=ImageAgnosticMapping,
)
def __str__(self):
return str(self.file)

然后使用以下序列化程序进行序列化:

from django.http import Http404
from rest_framework import serializers
from cecotec_apps.landings.models import ProductLanding
from cecotec_apps.partner.models import Home
from images.models import Image

class ImageSerializer(serializers.ModelSerializer):
file = serializers.ImageField()
def to_representation(self, instance):
return {"file": str(instance)}
class Meta:
model = Image
exclude = ("id",)

我正在尝试使用以下测试来测试Image实例的创建(当使用Imsonia请求API时,该实例有效(:

import tempfile
from PIL import Image as ImageFile
from django.test import tag
from model_bakery import baker
from rest_framework.test import APITestCase
from rest_framework.authtoken.models import Token
from rest_framework.reverse import reverse

from cecotec_apps.landings.models import ProductLanding
from cecotec_apps.partner.models import Home
from images.api.serializers import ImageSerializer
from images.models import Image
from tests.oscar.decorator.decorator_all_methods import global_test_decorator
from user.models import User

@tag("e2e", "image")
@global_test_decorator()
class ImageTestCase(APITestCase):
API_VERSION = "v1"
IMAGES_QUANTITY = 20
HTTP_HOST = "localhost:8000"
@classmethod
def _populate(cls):
cls.token = baker.make(Token)
baker.make_recipe("images.image_recipe", _quantity=cls.IMAGES_QUANTITY)
@classmethod
def _generate_image_file(cls):
with tempfile.NamedTemporaryFile(suffix="jpg") as tmp_file:
image = ImageFile.new("RGB", size=(100, 100))
image.save(tmp_file, "jpeg")
tmp_file.seek(0)
return tmp_file
@classmethod
def _depopulate(cls):
Token.objects.all().delete()
User.objects.all().delete()
ProductLanding.objects.all().delete()
Home.objects.all().delete()
Image.objects.all().delete()
def setUp(self, *args):
self._depopulate()
self._populate()
def tearDown(self, *args):
self._depopulate()
@tag("create", "authenticated")
def test_create_authenticated(self, *args):
self.client.force_authenticate(user=self.token.user, token=self.token)
data = self._prepare_create_data(**{"file": self._generate_image_file()})
response = self._call_create(data)
self._assert_create(response)
self.assertRegex(response.data.get("file"), "^images/.+$")
def _call_create(self, data):
return self.client.post(
path=reverse(f"image-list"),
data=data,
# content_type=MULTIPART_CONTENT,
format="multipart",
HTTP_HOST=self.HTTP_HOST,
)
def _assert_create(self, response, expected_status_code=201):
self.assertEqual(response.status_code, expected_status_code)
if expected_status_code == 201:
response_data = response.data
self.assertEqual(Image.objects.count(), self.IMAGES_QUANTITY + 1)
self._assert_response_body(response_data)
else:
self.assertEqual(Image.objects.count(), self.IMAGES_QUANTITY)
def _assert_response_body(self, response_body):
self.assertIn("file", response_body)

但是,当运行它时,返回的响应代码是400错误请求,而不是201创建。响应体如下:

{
"file": [
ErrorDetail(string="The submitted data was not a file. Check the encoding type on the form.",
code="invalid")
]
}

我也尝试过使用一个已经创建的文件进行测试,但错误是一样的。我在网上看到了很多帖子,但没有一条能帮我解决这个问题。

我很感激你的帮助。提前谢谢。

原因

我认为问题来自_generate_image_file方法。

@classmethod
def _generate_image_file(cls):
with tempfile.NamedTemporaryFile(suffix="jpg") as tmp_file:
image = ImageFile.new("RGB", size=(100, 100))
image.save(tmp_file, "jpeg")
tmp_file.seek(0)
return tmp_file

由于tempfile.NamedTemporaryFile是用with语句创建的,当您返回它并在另一个方法中使用它时,该文件已经关闭并删除。

解决方案

您应该通过使用contextlib.contextmanager修饰方法将_generate_image_file更改为上下文管理器,并使用yied语句而不是返回。

from contextlib import contextmanager
@contextmanager
@classmethod
def _generate_image_file(cls):
with tempfile.NamedTemporaryFile(suffix="jpg") as tmp_file:
image = ImageFile.new("RGB", size=(100, 100))
image.save(tmp_file, "jpeg")
tmp_file.seek(0)
yied tmp_file

test_create_authenticated方法上,将其更改为以下

@tag("create", "authenticated")
def test_create_authenticated(self, *args):
...
with self._generate_image_file() as image_file:
data = self._prepare_create_data(**{"file": image_file})
response = self._call_create(data)
...

现在,在with块退出后,图像文件将被删除。

我建议使用这个:

from django.core.files.uploadedfile import SimpleUploadedFile
with open("/some/path/to/image.jpg", "rb") as image:
image = SimpleUploadedFile("image.jpg", image.read(), content_type="image/jpg")
data = self._prepare_create_data(**{"file": image})
...

发生的情况是,您的序列化程序正在验证正在发送的文件是否无效。确保视图中有parser_classes = (parsers.MultiPartParser,),模型中也有models.ImageField

最新更新