英文:
Unit testing Django ModelForm with ImageField, tests show invalid form
问题
尝试测试Django ModelForm,但测试用例失败。尽管传递了所需的数据,但仍然显示“image字段是必需的”,并且测试显示表单无效。
models.py
# models.py
import sys
from PIL import Image
from io import BytesIO
from django.contrib.auth.models import User
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from django.utils.translation import ugettext as _
class ProfileImage(models.Model):
user = models.OneToOneField(User, verbose_name=_("user"), on_delete=models.CASCADE)
image = models.ImageField(upload_to="profile")
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
def save(self, *args, **kwargs):
if not self.id:
uploaded_image = Image.open(self.image)
rgb_image = uploaded_image.convert("RGB")
output = BytesIO()
image_resized = rgb_image.resize((300, 300))
image_resized.save(output, format="JPEG", quality=100)
output.seek(0)
self.image = InMemoryUploadedFile(
output, "ImageField", "{}.jpg".format(self.image.name.split('.')[0]),
"image/jpeg", sys.getsizeof(output), None
)
super().save(*args, **kwargs)
forms.py
# forms.py
from PIL import Image
from django import forms
from django.forms.utils import ErrorList
from django.utils.translation import ugettext as _
from .models import (
ProfileImage,
)
class ProfileImageForm(forms.ModelForm):
class Meta:
model = ProfileImage
fields = ["image"]
def clean(self):
image = self.cleaned_data.get("image")
if image:
image = Image.open(image)
width, height = image.size
image_format = image.format
if image_format not in ("PNG", "JPEG", "MPO"):
msg = _("Unsupported image type. Please upload a *png or *jpg image.")
self._errors["image"] = ErrorList([msg])
del image
if width < 300 or height < 300:
msg = _("Image is too small! Please upload an image of size 300px x 300px or larger.")
self._errors["image"] = ErrorList([msg])
del image
return self.cleaned_data
test_forms.py
# test_forms.py
import os
import tempfile
from mixer.backend.django import mixer
import numpy
from PIL import Image
from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from base.models import (
ProfileImage
)
from base.forms import (
ProfileImageForm,
)
# utils functions
def get_test_image(height=100, width=100):
"""
Generate an image for test purposes
Args:
height(int): image height
width(int): image width
Returns:
image(str): image path
"""
image = tempfile.NamedTemporaryFile(suffix=".png", dir=settings.MEDIA_ROOT, delete=True).name
imarray = numpy.random.rand(height, width, 3) * 255
im = Image.fromarray(imarray.astype("uint8")).convert("RGBA")
im.save(image)
return image
def delete_test_image(image):
"""
Delete an image generated for test purposes
Args:
image(str): image path
"""
if os.path.exists(image):
os.remove(image)
class TestFormData(TestCase):
def setUp(self):
self.standard_profile_test_image = get_test_image(300, 300)
# 1st way, Fails
def test_image_form_valid_1(self):
image_path = self.standard_profile_test_image
profile_img_instance = mixer.blend(ProfileImage, image=image_path)
image = profile_img_instance.image
form_data = {"image": image}
form = ProfileImageForm(form_data)
self.assertTrue(form.is_valid())
delete_test_image(profile_img_instance.image.path)
# 2nd way, Fails
def test_image_form_valid_2(self):
image_path = self.standard_profile_test_image
with open(image_path, "rb") as f:
file_data = f.read()
file_name = f.name
form_data = {"image": SimpleUploadedFile(file_name, file_data)}
form = ProfileImageForm(data=form_data)
self.assertTrue(form.is_valid())
def tearDown(self):
delete_test_image(self.standard_profile_test_image)
当我运行这两个测试时,它们都会抛出错误:表单无效
表单显示“image字段是必需的”,尽管我传递了所需的数据。
英文:
Trying to test a Django ModelForm, but the test cases are failing. Passing the required data but, it still shows "The image field is required" and tests show invalid form.
models.py
# models.py
import sys
from PIL import Image
from io import BytesIO
from django.contrib.auth.models import User
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from django.utils.translation import ugettext as _
class ProfileImage(models.Model):
user = models.OneToOneField(User, verbose_name=_("user"), on_delete=models.CASCADE)
image = models.ImageField(upload_to="profile")
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
def save(self, *args, **kwargs):
if not self.id:
uploaded_image = Image.open(self.image)
rgb_image = uploaded_image.convert("RGB")
output = BytesIO()
image_resized = rgb_image.resize((300, 300))
image_resized.save(output, format="JPEG", quality=100)
output.seek(0)
self.image = InMemoryUploadedFile(
output, "ImageField", "{}.jpg".format(self.image.name.split('.')[0]),
"image/jpeg", sys.getsizeof(output), None
)
super().save(*args, **kwargs)
forms.py
# forms.py
from PIL import Image
from django import forms
from django.forms.utils import ErrorList
from django.utils.translation import ugettext as _
from .models import (
ProfileImage,
)
class ProfileImageForm(forms.ModelForm):
class Meta:
model = ProfileImage
fields = ["image"]
def clean(self):
image = self.cleaned_data.get("image")
if image:
image = Image.open(image)
width, height = image.size
image_format = image.format
if image_format not in ("PNG", "JPEG", "MPO"):
msg = _("Unsupported image type. Please upload a *png or *jpg image.")
self._errors["image"] = ErrorList([msg])
del image
if width < 300 or height < 300:
msg = _("Image is too small! Please upload image of size 300px x 300px or More.")
self._errors["image"] = ErrorList([msg])
del image
return self.cleaned_data
test_forms.py
# test_forms.py
import os
import tempfile
from mixer.backend.django import mixer
import numpy
from PIL import Image
from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from base.models import (
ProfileImage
)
from base.forms import (
ProfileImageForm,
)
# utils functions
def get_test_image(height=100, width=100):
"""
Generate image for test purpose
Args:
height(int): image height
width(int): image width
Returns:
image(str): image path
"""
image = tempfile.NamedTemporaryFile(suffix=".png", dir=settings.MEDIA_ROOT, delete=True).name
imarray = numpy.random.rand(height, width, 3) * 255
im = Image.fromarray(imarray.astype("uint8")).convert("RGBA")
im.save(image)
return image
def delete_test_image(image):
"""
Delete image generated for test purpose
Args:
image(str): image path
Returns:
"""
if os.path.exists(image):
os.remove(image)
class TestFormData(TestCase):
def setUp(self):
self.standard_profile_test_image = get_test_image(300, 300)
# 1st way, Fails
def test_image_form_valid_1(self):
image_path = self.standard_profile_test_image
profile_img_instance = mixer.blend(ProfileImage, image=image_path)
image = profile_img_instance.image
form_data = {"image": image}
form = ProfileImageForm(form_data)
self.assertTrue(form.is_valid())
delete_test_image(profile_img_instance.image.path)
# 2nd way, Fails
def test_image_form_valid_2(self):
image_path = self.standard_profile_test_image
with open(image_path, "rb") as f:
file_data = f.read()
file_name = f.name
form_data = {"image": SimpleUploadedFile(file_name, file_data)}
form = ProfileImageForm(data=form_data)
self.assertTrue(form.is_valid())
def tearDown(self):
delete_test_image(self.standard_profile_test_image)
When I ran these two tests both of them threw errors: form is invalid
The form shows the image field is required
even though I passed the required data.
>>> form
>>> <ProfileImageForm bound=True, valid=False, fields=(image)>
>>> form.errors
>>> {'image': ['This field is required.']}
>>> form.non_field_errors()
>>> []
答案1
得分: 3
POST数据(非文件字段)应该是使用带有文件字段的表单时的第一个参数,文件数据(文件字段)应该是第二个参数。
form = ProfileImageForm({}, {"image": image})
英文:
POST data (non-file fields) should be the first argument and FILE data (file fields) should be the second when using forms with file fields
form = ProfileImageForm({}, {"image": image})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论