使用ImageField进行Django ModelForm的单元测试,测试显示表单无效。

huangapple go评论74阅读模式
英文:

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=_(&quot;user&quot;), on_delete=models.CASCADE)
image = models.ImageField(upload_to=&quot;profile&quot;)
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(&quot;RGB&quot;)
output = BytesIO()
image_resized = rgb_image.resize((300, 300))
image_resized.save(output, format=&quot;JPEG&quot;, quality=100)
output.seek(0)
self.image = InMemoryUploadedFile(
output, &quot;ImageField&quot;, &quot;{}.jpg&quot;.format(self.image.name.split(&#39;.&#39;)[0]),
&quot;image/jpeg&quot;, 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 = [&quot;image&quot;]
def clean(self):
image = self.cleaned_data.get(&quot;image&quot;)
if image:
image = Image.open(image)
width, height = image.size
image_format = image.format
if image_format not in (&quot;PNG&quot;, &quot;JPEG&quot;, &quot;MPO&quot;):
msg = _(&quot;Unsupported image type. Please upload a *png or *jpg image.&quot;)
self._errors[&quot;image&quot;] = ErrorList([msg])
del image
if width &lt; 300 or height &lt; 300:
msg = _(&quot;Image is too small! Please upload image of size 300px x 300px or More.&quot;)
self._errors[&quot;image&quot;] = 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):
&quot;&quot;&quot;
Generate image for test purpose
Args:
height(int): image height
width(int): image width
Returns:
image(str): image path
&quot;&quot;&quot;
image = tempfile.NamedTemporaryFile(suffix=&quot;.png&quot;, dir=settings.MEDIA_ROOT, delete=True).name
imarray = numpy.random.rand(height, width, 3) * 255
im = Image.fromarray(imarray.astype(&quot;uint8&quot;)).convert(&quot;RGBA&quot;)
im.save(image)
return image
def delete_test_image(image):
&quot;&quot;&quot;
Delete image generated for test purpose
Args:
image(str): image path
Returns:
&quot;&quot;&quot;
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 = {&quot;image&quot;: 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, &quot;rb&quot;) as f:
file_data = f.read()
file_name = f.name
form_data = {&quot;image&quot;: 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.

&gt;&gt;&gt; form
&gt;&gt;&gt; &lt;ProfileImageForm bound=True, valid=False, fields=(image)&gt;
&gt;&gt;&gt; form.errors
&gt;&gt;&gt; {&#39;image&#39;: [&#39;This field is required.&#39;]}
&gt;&gt;&gt; form.non_field_errors()
&gt;&gt;&gt; []

答案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({}, {&quot;image&quot;: image})

huangapple
  • 本文由 发表于 2020年1月6日 15:22:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/59608148.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定