summaryrefslogtreecommitdiff
path: root/tests/model_fields
diff options
context:
space:
mode:
authorFlorian Apolloner <florian@apolloner.eu>2013-02-26 09:53:47 +0100
committerFlorian Apolloner <florian@apolloner.eu>2013-02-26 14:36:57 +0100
commit89f40e36246100df6a11316c31a76712ebc6c501 (patch)
tree6e65639683ddaf2027908d1ecb1739e0e2ff853b /tests/model_fields
parentb3d2ccb5bfbaf6e7fe1f98843baaa48c35a70950 (diff)
Merged regressiontests and modeltests into the test root.
Diffstat (limited to 'tests/model_fields')
-rw-r--r--tests/model_fields/4x8.pngbin0 -> 87 bytes
-rw-r--r--tests/model_fields/8x4.pngbin0 -> 87 bytes
-rw-r--r--tests/model_fields/__init__.py0
-rw-r--r--tests/model_fields/imagefield.py434
-rw-r--r--tests/model_fields/models.py200
-rw-r--r--tests/model_fields/tests.py426
6 files changed, 1060 insertions, 0 deletions
diff --git a/tests/model_fields/4x8.png b/tests/model_fields/4x8.png
new file mode 100644
index 0000000000..ffce444d48
--- /dev/null
+++ b/tests/model_fields/4x8.png
Binary files differ
diff --git a/tests/model_fields/8x4.png b/tests/model_fields/8x4.png
new file mode 100644
index 0000000000..60e6d69ee1
--- /dev/null
+++ b/tests/model_fields/8x4.png
Binary files differ
diff --git a/tests/model_fields/__init__.py b/tests/model_fields/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/model_fields/__init__.py
diff --git a/tests/model_fields/imagefield.py b/tests/model_fields/imagefield.py
new file mode 100644
index 0000000000..df0215db3d
--- /dev/null
+++ b/tests/model_fields/imagefield.py
@@ -0,0 +1,434 @@
+from __future__ import absolute_import
+
+import os
+import shutil
+
+from django.core.files import File
+from django.core.files.images import ImageFile
+from django.test import TestCase
+from django.utils._os import upath
+from django.utils.unittest import skipIf
+
+from .models import Image
+
+if Image:
+ from .models import (Person, PersonWithHeight, PersonWithHeightAndWidth,
+ PersonDimensionsFirst, PersonTwoImages, TestImageFieldFile)
+ from .models import temp_storage_dir
+else:
+ # PIL not available, create dummy classes (tests will be skipped anyway)
+ class Person():
+ pass
+ PersonWithHeight = PersonWithHeightAndWidth = PersonDimensionsFirst = Person
+ PersonTwoImages = Person
+
+
+class ImageFieldTestMixin(object):
+ """
+ Mixin class to provide common functionality to ImageField test classes.
+ """
+
+ # Person model to use for tests.
+ PersonModel = PersonWithHeightAndWidth
+ # File class to use for file instances.
+ File = ImageFile
+
+ def setUp(self):
+ """
+ Creates a pristine temp directory (or deletes and recreates if it
+ already exists) that the model uses as its storage directory.
+
+ Sets up two ImageFile instances for use in tests.
+ """
+ if os.path.exists(temp_storage_dir):
+ shutil.rmtree(temp_storage_dir)
+ os.mkdir(temp_storage_dir)
+
+ file_path1 = os.path.join(os.path.dirname(upath(__file__)), "4x8.png")
+ self.file1 = self.File(open(file_path1, 'rb'))
+
+ file_path2 = os.path.join(os.path.dirname(upath(__file__)), "8x4.png")
+ self.file2 = self.File(open(file_path2, 'rb'))
+
+ def tearDown(self):
+ """
+ Removes temp directory and all its contents.
+ """
+ shutil.rmtree(temp_storage_dir)
+
+ def check_dimensions(self, instance, width, height,
+ field_name='mugshot'):
+ """
+ Asserts that the given width and height values match both the
+ field's height and width attributes and the height and width fields
+ (if defined) the image field is caching to.
+
+ Note, this method will check for dimension fields named by adding
+ "_width" or "_height" to the name of the ImageField. So, the
+ models used in these tests must have their fields named
+ accordingly.
+
+ By default, we check the field named "mugshot", but this can be
+ specified by passing the field_name parameter.
+ """
+ field = getattr(instance, field_name)
+ # Check height/width attributes of field.
+ if width is None and height is None:
+ self.assertRaises(ValueError, getattr, field, 'width')
+ self.assertRaises(ValueError, getattr, field, 'height')
+ else:
+ self.assertEqual(field.width, width)
+ self.assertEqual(field.height, height)
+
+ # Check height/width fields of model, if defined.
+ width_field_name = field_name + '_width'
+ if hasattr(instance, width_field_name):
+ self.assertEqual(getattr(instance, width_field_name), width)
+ height_field_name = field_name + '_height'
+ if hasattr(instance, height_field_name):
+ self.assertEqual(getattr(instance, height_field_name), height)
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class ImageFieldTests(ImageFieldTestMixin, TestCase):
+ """
+ Tests for ImageField that don't need to be run with each of the
+ different test model classes.
+ """
+
+ def test_equal_notequal_hash(self):
+ """
+ Bug #9786: Ensure '==' and '!=' work correctly.
+ Bug #9508: make sure hash() works as expected (equal items must
+ hash to the same value).
+ """
+ # Create two Persons with different mugshots.
+ p1 = self.PersonModel(name="Joe")
+ p1.mugshot.save("mug", self.file1)
+ p2 = self.PersonModel(name="Bob")
+ p2.mugshot.save("mug", self.file2)
+ self.assertEqual(p1.mugshot == p2.mugshot, False)
+ self.assertEqual(p1.mugshot != p2.mugshot, True)
+
+ # Test again with an instance fetched from the db.
+ p1_db = self.PersonModel.objects.get(name="Joe")
+ self.assertEqual(p1_db.mugshot == p2.mugshot, False)
+ self.assertEqual(p1_db.mugshot != p2.mugshot, True)
+
+ # Instance from db should match the local instance.
+ self.assertEqual(p1_db.mugshot == p1.mugshot, True)
+ self.assertEqual(hash(p1_db.mugshot), hash(p1.mugshot))
+ self.assertEqual(p1_db.mugshot != p1.mugshot, False)
+
+ def test_instantiate_missing(self):
+ """
+ If the underlying file is unavailable, still create instantiate the
+ object without error.
+ """
+ p = self.PersonModel(name="Joan")
+ p.mugshot.save("shot", self.file1)
+ p = self.PersonModel.objects.get(name="Joan")
+ path = p.mugshot.path
+ shutil.move(path, path + '.moved')
+ p2 = self.PersonModel.objects.get(name="Joan")
+
+ def test_delete_when_missing(self):
+ """
+ Bug #8175: correctly delete an object where the file no longer
+ exists on the file system.
+ """
+ p = self.PersonModel(name="Fred")
+ p.mugshot.save("shot", self.file1)
+ os.remove(p.mugshot.path)
+ p.delete()
+
+ def test_size_method(self):
+ """
+ Bug #8534: FileField.size should not leave the file open.
+ """
+ p = self.PersonModel(name="Joan")
+ p.mugshot.save("shot", self.file1)
+
+ # Get a "clean" model instance
+ p = self.PersonModel.objects.get(name="Joan")
+ # It won't have an opened file.
+ self.assertEqual(p.mugshot.closed, True)
+
+ # After asking for the size, the file should still be closed.
+ _ = p.mugshot.size
+ self.assertEqual(p.mugshot.closed, True)
+
+ def test_pickle(self):
+ """
+ Tests that ImageField can be pickled, unpickled, and that the
+ image of the unpickled version is the same as the original.
+ """
+ import pickle
+
+ p = Person(name="Joe")
+ p.mugshot.save("mug", self.file1)
+ dump = pickle.dumps(p)
+
+ p2 = Person(name="Bob")
+ p2.mugshot = self.file1
+
+ loaded_p = pickle.loads(dump)
+ self.assertEqual(p.mugshot, loaded_p.mugshot)
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class ImageFieldTwoDimensionsTests(ImageFieldTestMixin, TestCase):
+ """
+ Tests behavior of an ImageField and its dimensions fields.
+ """
+
+ def test_constructor(self):
+ """
+ Tests assigning an image field through the model's constructor.
+ """
+ p = self.PersonModel(name='Joe', mugshot=self.file1)
+ self.check_dimensions(p, 4, 8)
+ p.save()
+ self.check_dimensions(p, 4, 8)
+
+ def test_image_after_constructor(self):
+ """
+ Tests behavior when image is not passed in constructor.
+ """
+ p = self.PersonModel(name='Joe')
+ # TestImageField value will default to being an instance of its
+ # attr_class, a TestImageFieldFile, with name == None, which will
+ # cause it to evaluate as False.
+ self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True)
+ self.assertEqual(bool(p.mugshot), False)
+
+ # Test setting a fresh created model instance.
+ p = self.PersonModel(name='Joe')
+ p.mugshot = self.file1
+ self.check_dimensions(p, 4, 8)
+
+ def test_create(self):
+ """
+ Tests assigning an image in Manager.create().
+ """
+ p = self.PersonModel.objects.create(name='Joe', mugshot=self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ def test_default_value(self):
+ """
+ Tests that the default value for an ImageField is an instance of
+ the field's attr_class (TestImageFieldFile in this case) with no
+ name (name set to None).
+ """
+ p = self.PersonModel()
+ self.assertEqual(isinstance(p.mugshot, TestImageFieldFile), True)
+ self.assertEqual(bool(p.mugshot), False)
+
+ def test_assignment_to_None(self):
+ """
+ Tests that assigning ImageField to None clears dimensions.
+ """
+ p = self.PersonModel(name='Joe', mugshot=self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ # If image assigned to None, dimension fields should be cleared.
+ p.mugshot = None
+ self.check_dimensions(p, None, None)
+
+ p.mugshot = self.file2
+ self.check_dimensions(p, 8, 4)
+
+ def test_field_save_and_delete_methods(self):
+ """
+ Tests assignment using the field's save method and deletion using
+ the field's delete method.
+ """
+ p = self.PersonModel(name='Joe')
+ p.mugshot.save("mug", self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ # A new file should update dimensions.
+ p.mugshot.save("mug", self.file2)
+ self.check_dimensions(p, 8, 4)
+
+ # Field and dimensions should be cleared after a delete.
+ p.mugshot.delete(save=False)
+ self.assertEqual(p.mugshot, None)
+ self.check_dimensions(p, None, None)
+
+ def test_dimensions(self):
+ """
+ Checks that dimensions are updated correctly in various situations.
+ """
+ p = self.PersonModel(name='Joe')
+
+ # Dimensions should get set if file is saved.
+ p.mugshot.save("mug", self.file1)
+ self.check_dimensions(p, 4, 8)
+
+ # Test dimensions after fetching from database.
+ p = self.PersonModel.objects.get(name='Joe')
+ # Bug 11084: Dimensions should not get recalculated if file is
+ # coming from the database. We test this by checking if the file
+ # was opened.
+ self.assertEqual(p.mugshot.was_opened, False)
+ self.check_dimensions(p, 4, 8)
+ # After checking dimensions on the image field, the file will have
+ # opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+ # Dimensions should now be cached, and if we reset was_opened and
+ # check dimensions again, the file should not have opened.
+ p.mugshot.was_opened = False
+ self.check_dimensions(p, 4, 8)
+ self.assertEqual(p.mugshot.was_opened, False)
+
+ # If we assign a new image to the instance, the dimensions should
+ # update.
+ p.mugshot = self.file2
+ self.check_dimensions(p, 8, 4)
+ # Dimensions were recalculated, and hence file should have opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class ImageFieldNoDimensionsTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField with no dimension fields.
+ """
+
+ PersonModel = Person
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class ImageFieldOneDimensionTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField with one dimensions field.
+ """
+
+ PersonModel = PersonWithHeight
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class ImageFieldDimensionsFirstTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField where the dimensions fields are
+ defined before the ImageField.
+ """
+
+ PersonModel = PersonDimensionsFirst
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class ImageFieldUsingFileTests(ImageFieldTwoDimensionsTests):
+ """
+ Tests behavior of an ImageField when assigning it a File instance
+ rather than an ImageFile instance.
+ """
+
+ PersonModel = PersonDimensionsFirst
+ File = File
+
+
+@skipIf(Image is None, "PIL is required to test ImageField")
+class TwoImageFieldTests(ImageFieldTestMixin, TestCase):
+ """
+ Tests a model with two ImageFields.
+ """
+
+ PersonModel = PersonTwoImages
+
+ def test_constructor(self):
+ p = self.PersonModel(mugshot=self.file1, headshot=self.file2)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ p.save()
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ def test_create(self):
+ p = self.PersonModel.objects.create(mugshot=self.file1,
+ headshot=self.file2)
+ self.check_dimensions(p, 4, 8)
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ def test_assignment(self):
+ p = self.PersonModel()
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+
+ p.mugshot = self.file1
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+ p.headshot = self.file2
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ # Clear the ImageFields one at a time.
+ p.mugshot = None
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ p.headshot = None
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+
+ def test_field_save_and_delete_methods(self):
+ p = self.PersonModel(name='Joe')
+ p.mugshot.save("mug", self.file1)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+ p.headshot.save("head", self.file2)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ # We can use save=True when deleting the image field with null=True
+ # dimension fields and the other field has an image.
+ p.headshot.delete(save=True)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+ p.mugshot.delete(save=False)
+ self.check_dimensions(p, None, None, 'mugshot')
+ self.check_dimensions(p, None, None, 'headshot')
+
+ def test_dimensions(self):
+ """
+ Checks that dimensions are updated correctly in various situations.
+ """
+ p = self.PersonModel(name='Joe')
+
+ # Dimensions should get set for the saved file.
+ p.mugshot.save("mug", self.file1)
+ p.headshot.save("head", self.file2)
+ self.check_dimensions(p, 4, 8, 'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+
+ # Test dimensions after fetching from database.
+ p = self.PersonModel.objects.get(name='Joe')
+ # Bug 11084: Dimensions should not get recalculated if file is
+ # coming from the database. We test this by checking if the file
+ # was opened.
+ self.assertEqual(p.mugshot.was_opened, False)
+ self.assertEqual(p.headshot.was_opened, False)
+ self.check_dimensions(p, 4, 8,'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ # After checking dimensions on the image fields, the files will
+ # have been opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+ self.assertEqual(p.headshot.was_opened, True)
+ # Dimensions should now be cached, and if we reset was_opened and
+ # check dimensions again, the file should not have opened.
+ p.mugshot.was_opened = False
+ p.headshot.was_opened = False
+ self.check_dimensions(p, 4, 8,'mugshot')
+ self.check_dimensions(p, 8, 4, 'headshot')
+ self.assertEqual(p.mugshot.was_opened, False)
+ self.assertEqual(p.headshot.was_opened, False)
+
+ # If we assign a new image to the instance, the dimensions should
+ # update.
+ p.mugshot = self.file2
+ p.headshot = self.file1
+ self.check_dimensions(p, 8, 4, 'mugshot')
+ self.check_dimensions(p, 4, 8, 'headshot')
+ # Dimensions were recalculated, and hence file should have opened.
+ self.assertEqual(p.mugshot.was_opened, True)
+ self.assertEqual(p.headshot.was_opened, True)
diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py
new file mode 100644
index 0000000000..1d20f44fae
--- /dev/null
+++ b/tests/model_fields/models.py
@@ -0,0 +1,200 @@
+import os
+import tempfile
+
+# Try to import PIL in either of the two ways it can end up installed.
+# Checking for the existence of Image is enough for CPython, but for PyPy,
+# you need to check for the underlying modules.
+
+try:
+ from PIL import Image, _imaging
+except ImportError:
+ try:
+ import Image, _imaging
+ except ImportError:
+ Image = None
+
+from django.core.files.storage import FileSystemStorage
+from django.db import models
+from django.db.models.fields.files import ImageFieldFile, ImageField
+
+
+class Foo(models.Model):
+ a = models.CharField(max_length=10)
+ d = models.DecimalField(max_digits=5, decimal_places=3)
+
+def get_foo():
+ return Foo.objects.get(id=1)
+
+class Bar(models.Model):
+ b = models.CharField(max_length=10)
+ a = models.ForeignKey(Foo, default=get_foo)
+
+class Whiz(models.Model):
+ CHOICES = (
+ ('Group 1', (
+ (1,'First'),
+ (2,'Second'),
+ )
+ ),
+ ('Group 2', (
+ (3,'Third'),
+ (4,'Fourth'),
+ )
+ ),
+ (0,'Other'),
+ )
+ c = models.IntegerField(choices=CHOICES, null=True)
+
+class BigD(models.Model):
+ d = models.DecimalField(max_digits=38, decimal_places=30)
+
+class BigS(models.Model):
+ s = models.SlugField(max_length=255)
+
+class BigInt(models.Model):
+ value = models.BigIntegerField()
+ null_value = models.BigIntegerField(null = True, blank = True)
+
+class Post(models.Model):
+ title = models.CharField(max_length=100)
+ body = models.TextField()
+
+class NullBooleanModel(models.Model):
+ nbfield = models.NullBooleanField()
+
+class BooleanModel(models.Model):
+ bfield = models.BooleanField()
+ string = models.CharField(max_length=10, default='abc')
+
+class FksToBooleans(models.Model):
+ """Model wih FKs to models with {Null,}BooleanField's, #15040"""
+ bf = models.ForeignKey(BooleanModel)
+ nbf = models.ForeignKey(NullBooleanModel)
+
+class RenamedField(models.Model):
+ modelname = models.IntegerField(name="fieldname", choices=((1,'One'),))
+
+class VerboseNameField(models.Model):
+ id = models.AutoField("verbose pk", primary_key=True)
+ field1 = models.BigIntegerField("verbose field1")
+ field2 = models.BooleanField("verbose field2")
+ field3 = models.CharField("verbose field3", max_length=10)
+ field4 = models.CommaSeparatedIntegerField("verbose field4", max_length=99)
+ field5 = models.DateField("verbose field5")
+ field6 = models.DateTimeField("verbose field6")
+ field7 = models.DecimalField("verbose field7", max_digits=6, decimal_places=1)
+ field8 = models.EmailField("verbose field8")
+ field9 = models.FileField("verbose field9", upload_to="unused")
+ field10 = models.FilePathField("verbose field10")
+ field11 = models.FloatField("verbose field11")
+ # Don't want to depend on PIL in this test
+ #field_image = models.ImageField("verbose field")
+ field12 = models.IntegerField("verbose field12")
+ field13 = models.IPAddressField("verbose field13")
+ field14 = models.GenericIPAddressField("verbose field14", protocol="ipv4")
+ field15 = models.NullBooleanField("verbose field15")
+ field16 = models.PositiveIntegerField("verbose field16")
+ field17 = models.PositiveSmallIntegerField("verbose field17")
+ field18 = models.SlugField("verbose field18")
+ field19 = models.SmallIntegerField("verbose field19")
+ field20 = models.TextField("verbose field20")
+ field21 = models.TimeField("verbose field21")
+ field22 = models.URLField("verbose field22")
+
+# This model isn't used in any test, just here to ensure it validates successfully.
+# See ticket #16570.
+class DecimalLessThanOne(models.Model):
+ d = models.DecimalField(max_digits=3, decimal_places=3)
+
+###############################################################################
+# FileField
+
+class Document(models.Model):
+ myfile = models.FileField(upload_to='unused')
+
+###############################################################################
+# ImageField
+
+# If PIL available, do these tests.
+if Image:
+ class TestImageFieldFile(ImageFieldFile):
+ """
+ Custom Field File class that records whether or not the underlying file
+ was opened.
+ """
+ def __init__(self, *args, **kwargs):
+ self.was_opened = False
+ super(TestImageFieldFile, self).__init__(*args,**kwargs)
+ def open(self):
+ self.was_opened = True
+ super(TestImageFieldFile, self).open()
+
+ class TestImageField(ImageField):
+ attr_class = TestImageFieldFile
+
+ # Set up a temp directory for file storage.
+ temp_storage_dir = tempfile.mkdtemp()
+ temp_storage = FileSystemStorage(temp_storage_dir)
+ temp_upload_to_dir = os.path.join(temp_storage.location, 'tests')
+
+ class Person(models.Model):
+ """
+ Model that defines an ImageField with no dimension fields.
+ """
+ name = models.CharField(max_length=50)
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests')
+
+ class PersonWithHeight(models.Model):
+ """
+ Model that defines an ImageField with only one dimension field.
+ """
+ name = models.CharField(max_length=50)
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height')
+ mugshot_height = models.PositiveSmallIntegerField()
+
+ class PersonWithHeightAndWidth(models.Model):
+ """
+ Model that defines height and width fields after the ImageField.
+ """
+ name = models.CharField(max_length=50)
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height',
+ width_field='mugshot_width')
+ mugshot_height = models.PositiveSmallIntegerField()
+ mugshot_width = models.PositiveSmallIntegerField()
+
+ class PersonDimensionsFirst(models.Model):
+ """
+ Model that defines height and width fields before the ImageField.
+ """
+ name = models.CharField(max_length=50)
+ mugshot_height = models.PositiveSmallIntegerField()
+ mugshot_width = models.PositiveSmallIntegerField()
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height',
+ width_field='mugshot_width')
+
+ class PersonTwoImages(models.Model):
+ """
+ Model that:
+ * Defines two ImageFields
+ * Defines the height/width fields before the ImageFields
+ * Has a nullalble ImageField
+ """
+ name = models.CharField(max_length=50)
+ mugshot_height = models.PositiveSmallIntegerField()
+ mugshot_width = models.PositiveSmallIntegerField()
+ mugshot = TestImageField(storage=temp_storage, upload_to='tests',
+ height_field='mugshot_height',
+ width_field='mugshot_width')
+ headshot_height = models.PositiveSmallIntegerField(
+ blank=True, null=True)
+ headshot_width = models.PositiveSmallIntegerField(
+ blank=True, null=True)
+ headshot = TestImageField(blank=True, null=True,
+ storage=temp_storage, upload_to='tests',
+ height_field='headshot_height',
+ width_field='headshot_width')
+
+###############################################################################
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
new file mode 100644
index 0000000000..8c596ed4f5
--- /dev/null
+++ b/tests/model_fields/tests.py
@@ -0,0 +1,426 @@
+from __future__ import absolute_import, unicode_literals
+
+import datetime
+from decimal import Decimal
+
+from django import test
+from django import forms
+from django.core.exceptions import ValidationError
+from django.db import models
+from django.db.models.fields.files import FieldFile
+from django.utils import six
+from django.utils import unittest
+
+from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
+ NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField,
+ FksToBooleans)
+
+from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests,
+ TwoImageFieldTests, ImageFieldNoDimensionsTests,
+ ImageFieldOneDimensionTests, ImageFieldDimensionsFirstTests,
+ ImageFieldUsingFileTests)
+
+
+class BasicFieldTests(test.TestCase):
+ def test_show_hidden_initial(self):
+ """
+ Regression test for #12913. Make sure fields with choices respect
+ show_hidden_initial as a kwarg to models.Field.formfield()
+ """
+ choices = [(0, 0), (1, 1)]
+ model_field = models.Field(choices=choices)
+ form_field = model_field.formfield(show_hidden_initial=True)
+ self.assertTrue(form_field.show_hidden_initial)
+
+ form_field = model_field.formfield(show_hidden_initial=False)
+ self.assertFalse(form_field.show_hidden_initial)
+
+ def test_nullbooleanfield_blank(self):
+ """
+ Regression test for #13071: NullBooleanField should not throw
+ a validation error when given a value of None.
+
+ """
+ nullboolean = NullBooleanModel(nbfield=None)
+ try:
+ nullboolean.full_clean()
+ except ValidationError as e:
+ self.fail("NullBooleanField failed validation with value of None: %s" % e.messages)
+
+ def test_field_repr(self):
+ """
+ Regression test for #5931: __repr__ of a field also displays its name
+ """
+ f = Foo._meta.get_field('a')
+ self.assertEqual(repr(f), '<django.db.models.fields.CharField: a>')
+ f = models.fields.CharField()
+ self.assertEqual(repr(f), '<django.db.models.fields.CharField>')
+
+ def test_field_name(self):
+ """
+ Regression test for #14695: explicitly defined field name overwritten
+ by model's attribute name.
+ """
+ instance = RenamedField()
+ self.assertTrue(hasattr(instance, 'get_fieldname_display'))
+ self.assertFalse(hasattr(instance, 'get_modelname_display'))
+
+ def test_field_verbose_name(self):
+ m = VerboseNameField
+ for i in range(1, 23):
+ self.assertEqual(m._meta.get_field('field%d' % i).verbose_name,
+ 'verbose field%d' % i)
+
+ self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk')
+
+ def test_formclass_with_choices(self):
+ # regression for 18162
+ class CustomChoiceField(forms.TypedChoiceField):
+ pass
+ choices = [('a@b.cc', 'a@b.cc'), ('b@b.cc', 'b@b.cc')]
+ field = models.CharField(choices=choices)
+ klass = CustomChoiceField
+ self.assertIsInstance(field.formfield(form_class=klass), klass)
+
+
+class DecimalFieldTests(test.TestCase):
+ def test_to_python(self):
+ f = models.DecimalField(max_digits=4, decimal_places=2)
+ self.assertEqual(f.to_python(3), Decimal("3"))
+ self.assertEqual(f.to_python("3.14"), Decimal("3.14"))
+ self.assertRaises(ValidationError, f.to_python, "abc")
+
+ def test_default(self):
+ f = models.DecimalField(default=Decimal("0.00"))
+ self.assertEqual(f.get_default(), Decimal("0.00"))
+
+ def test_format(self):
+ f = models.DecimalField(max_digits=5, decimal_places=1)
+ self.assertEqual(f._format(f.to_python(2)), '2.0')
+ self.assertEqual(f._format(f.to_python('2.6')), '2.6')
+ self.assertEqual(f._format(None), None)
+
+ def test_get_db_prep_lookup(self):
+ from django.db import connection
+ f = models.DecimalField(max_digits=5, decimal_places=1)
+ self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None])
+
+ def test_filter_with_strings(self):
+ """
+ We should be able to filter decimal fields using strings (#8023)
+ """
+ Foo.objects.create(id=1, a='abc', d=Decimal("12.34"))
+ self.assertEqual(list(Foo.objects.filter(d='1.23')), [])
+
+ def test_save_without_float_conversion(self):
+ """
+ Ensure decimals don't go through a corrupting float conversion during
+ save (#5079).
+ """
+ bd = BigD(d="12.9")
+ bd.save()
+ bd = BigD.objects.get(pk=bd.pk)
+ self.assertEqual(bd.d, Decimal("12.9"))
+
+ def test_lookup_really_big_value(self):
+ """
+ Ensure that really big values can be used in a filter statement, even
+ with older Python versions.
+ """
+ # This should not crash. That counts as a win for our purposes.
+ Foo.objects.filter(d__gte=100000000000)
+
+class ForeignKeyTests(test.TestCase):
+ def test_callable_default(self):
+ """Test the use of a lazy callable for ForeignKey.default"""
+ a = Foo.objects.create(id=1, a='abc', d=Decimal("12.34"))
+ b = Bar.objects.create(b="bcd")
+ self.assertEqual(b.a, a)
+
+class DateTimeFieldTests(unittest.TestCase):
+ def test_datetimefield_to_python_usecs(self):
+ """DateTimeField.to_python should support usecs"""
+ f = models.DateTimeField()
+ self.assertEqual(f.to_python('2001-01-02 03:04:05.000006'),
+ datetime.datetime(2001, 1, 2, 3, 4, 5, 6))
+ self.assertEqual(f.to_python('2001-01-02 03:04:05.999999'),
+ datetime.datetime(2001, 1, 2, 3, 4, 5, 999999))
+
+ def test_timefield_to_python_usecs(self):
+ """TimeField.to_python should support usecs"""
+ f = models.TimeField()
+ self.assertEqual(f.to_python('01:02:03.000004'),
+ datetime.time(1, 2, 3, 4))
+ self.assertEqual(f.to_python('01:02:03.999999'),
+ datetime.time(1, 2, 3, 999999))
+
+class BooleanFieldTests(unittest.TestCase):
+ def _test_get_db_prep_lookup(self, f):
+ from django.db import connection
+ self.assertEqual(f.get_db_prep_lookup('exact', True, connection=connection), [True])
+ self.assertEqual(f.get_db_prep_lookup('exact', '1', connection=connection), [True])
+ self.assertEqual(f.get_db_prep_lookup('exact', 1, connection=connection), [True])
+ self.assertEqual(f.get_db_prep_lookup('exact', False, connection=connection), [False])
+ self.assertEqual(f.get_db_prep_lookup('exact', '0', connection=connection), [False])
+ self.assertEqual(f.get_db_prep_lookup('exact', 0, connection=connection), [False])
+ self.assertEqual(f.get_db_prep_lookup('exact', None, connection=connection), [None])
+
+ def _test_to_python(self, f):
+ self.assertTrue(f.to_python(1) is True)
+ self.assertTrue(f.to_python(0) is False)
+
+ def test_booleanfield_get_db_prep_lookup(self):
+ self._test_get_db_prep_lookup(models.BooleanField())
+
+ def test_nullbooleanfield_get_db_prep_lookup(self):
+ self._test_get_db_prep_lookup(models.NullBooleanField())
+
+ def test_booleanfield_to_python(self):
+ self._test_to_python(models.BooleanField())
+
+ def test_nullbooleanfield_to_python(self):
+ self._test_to_python(models.NullBooleanField())
+
+ def test_booleanfield_choices_blank(self):
+ """
+ Test that BooleanField with choices and defaults doesn't generate a
+ formfield with the blank option (#9640, #10549).
+ """
+ choices = [(1, 'Si'), (2, 'No')]
+ f = models.BooleanField(choices=choices, default=1, null=True)
+ self.assertEqual(f.formfield().choices, [('', '---------')] + choices)
+
+ f = models.BooleanField(choices=choices, default=1, null=False)
+ self.assertEqual(f.formfield().choices, choices)
+
+ def test_return_type(self):
+ b = BooleanModel()
+ b.bfield = True
+ b.save()
+ b2 = BooleanModel.objects.get(pk=b.pk)
+ self.assertTrue(isinstance(b2.bfield, bool))
+ self.assertEqual(b2.bfield, True)
+
+ b3 = BooleanModel()
+ b3.bfield = False
+ b3.save()
+ b4 = BooleanModel.objects.get(pk=b3.pk)
+ self.assertTrue(isinstance(b4.bfield, bool))
+ self.assertEqual(b4.bfield, False)
+
+ b = NullBooleanModel()
+ b.nbfield = True
+ b.save()
+ b2 = NullBooleanModel.objects.get(pk=b.pk)
+ self.assertTrue(isinstance(b2.nbfield, bool))
+ self.assertEqual(b2.nbfield, True)
+
+ b3 = NullBooleanModel()
+ b3.nbfield = False
+ b3.save()
+ b4 = NullBooleanModel.objects.get(pk=b3.pk)
+ self.assertTrue(isinstance(b4.nbfield, bool))
+ self.assertEqual(b4.nbfield, False)
+
+ # http://code.djangoproject.com/ticket/13293
+ # Verify that when an extra clause exists, the boolean
+ # conversions are applied with an offset
+ b5 = BooleanModel.objects.all().extra(
+ select={'string_col': 'string'})[0]
+ self.assertFalse(isinstance(b5.pk, bool))
+
+ def test_select_related(self):
+ """
+ Test type of boolean fields when retrieved via select_related() (MySQL,
+ #15040)
+ """
+ bmt = BooleanModel.objects.create(bfield=True)
+ bmf = BooleanModel.objects.create(bfield=False)
+ nbmt = NullBooleanModel.objects.create(nbfield=True)
+ nbmf = NullBooleanModel.objects.create(nbfield=False)
+
+ m1 = FksToBooleans.objects.create(bf=bmt, nbf=nbmt)
+ m2 = FksToBooleans.objects.create(bf=bmf, nbf=nbmf)
+
+ # Test select_related('fk_field_name')
+ ma = FksToBooleans.objects.select_related('bf').get(pk=m1.id)
+ # verify types -- should't be 0/1
+ self.assertIsInstance(ma.bf.bfield, bool)
+ self.assertIsInstance(ma.nbf.nbfield, bool)
+ # verify values
+ self.assertEqual(ma.bf.bfield, True)
+ self.assertEqual(ma.nbf.nbfield, True)
+
+ # Test select_related()
+ mb = FksToBooleans.objects.select_related().get(pk=m1.id)
+ mc = FksToBooleans.objects.select_related().get(pk=m2.id)
+ # verify types -- shouldn't be 0/1
+ self.assertIsInstance(mb.bf.bfield, bool)
+ self.assertIsInstance(mb.nbf.nbfield, bool)
+ self.assertIsInstance(mc.bf.bfield, bool)
+ self.assertIsInstance(mc.nbf.nbfield, bool)
+ # verify values
+ self.assertEqual(mb.bf.bfield, True)
+ self.assertEqual(mb.nbf.nbfield, True)
+ self.assertEqual(mc.bf.bfield, False)
+ self.assertEqual(mc.nbf.nbfield, False)
+
+
+class ChoicesTests(test.TestCase):
+ def test_choices_and_field_display(self):
+ """
+ Check that get_choices and get_flatchoices interact with
+ get_FIELD_display to return the expected values (#7913).
+ """
+ self.assertEqual(Whiz(c=1).get_c_display(), 'First') # A nested value
+ self.assertEqual(Whiz(c=0).get_c_display(), 'Other') # A top level value
+ self.assertEqual(Whiz(c=9).get_c_display(), 9) # Invalid value
+ self.assertEqual(Whiz(c=None).get_c_display(), None) # Blank value
+ self.assertEqual(Whiz(c='').get_c_display(), '') # Empty value
+
+class SlugFieldTests(test.TestCase):
+ def test_slugfield_max_length(self):
+ """
+ Make sure SlugField honors max_length (#9706)
+ """
+ bs = BigS.objects.create(s = 'slug'*50)
+ bs = BigS.objects.get(pk=bs.pk)
+ self.assertEqual(bs.s, 'slug'*50)
+
+
+class ValidationTest(test.TestCase):
+ def test_charfield_raises_error_on_empty_string(self):
+ f = models.CharField()
+ self.assertRaises(ValidationError, f.clean, "", None)
+
+ def test_charfield_cleans_empty_string_when_blank_true(self):
+ f = models.CharField(blank=True)
+ self.assertEqual('', f.clean('', None))
+
+ def test_integerfield_cleans_valid_string(self):
+ f = models.IntegerField()
+ self.assertEqual(2, f.clean('2', None))
+
+ def test_integerfield_raises_error_on_invalid_intput(self):
+ f = models.IntegerField()
+ self.assertRaises(ValidationError, f.clean, "a", None)
+
+ def test_charfield_with_choices_cleans_valid_choice(self):
+ f = models.CharField(max_length=1, choices=[('a','A'), ('b','B')])
+ self.assertEqual('a', f.clean('a', None))
+
+ def test_charfield_with_choices_raises_error_on_invalid_choice(self):
+ f = models.CharField(choices=[('a','A'), ('b','B')])
+ self.assertRaises(ValidationError, f.clean, "not a", None)
+
+ def test_choices_validation_supports_named_groups(self):
+ f = models.IntegerField(choices=(('group',((10,'A'),(20,'B'))),(30,'C')))
+ self.assertEqual(10, f.clean(10, None))
+
+ def test_nullable_integerfield_raises_error_with_blank_false(self):
+ f = models.IntegerField(null=True, blank=False)
+ self.assertRaises(ValidationError, f.clean, None, None)
+
+ def test_nullable_integerfield_cleans_none_on_null_and_blank_true(self):
+ f = models.IntegerField(null=True, blank=True)
+ self.assertEqual(None, f.clean(None, None))
+
+ def test_integerfield_raises_error_on_empty_input(self):
+ f = models.IntegerField(null=False)
+ self.assertRaises(ValidationError, f.clean, None, None)
+ self.assertRaises(ValidationError, f.clean, '', None)
+
+ def test_integerfield_validates_zero_against_choices(self):
+ f = models.IntegerField(choices=((1, 1),))
+ self.assertRaises(ValidationError, f.clean, '0', None)
+
+ def test_charfield_raises_error_on_empty_input(self):
+ f = models.CharField(null=False)
+ self.assertRaises(ValidationError, f.clean, None, None)
+
+ def test_datefield_cleans_date(self):
+ f = models.DateField()
+ self.assertEqual(datetime.date(2008, 10, 10), f.clean('2008-10-10', None))
+
+ def test_boolean_field_doesnt_accept_empty_input(self):
+ f = models.BooleanField()
+ self.assertRaises(ValidationError, f.clean, None, None)
+
+
+class BigIntegerFieldTests(test.TestCase):
+ def test_limits(self):
+ # Ensure that values that are right at the limits can be saved
+ # and then retrieved without corruption.
+ maxval = 9223372036854775807
+ minval = -maxval - 1
+ BigInt.objects.create(value=maxval)
+ qs = BigInt.objects.filter(value__gte=maxval)
+ self.assertEqual(qs.count(), 1)
+ self.assertEqual(qs[0].value, maxval)
+ BigInt.objects.create(value=minval)
+ qs = BigInt.objects.filter(value__lte=minval)
+ self.assertEqual(qs.count(), 1)
+ self.assertEqual(qs[0].value, minval)
+
+ def test_types(self):
+ b = BigInt(value = 0)
+ self.assertTrue(isinstance(b.value, six.integer_types))
+ b.save()
+ self.assertTrue(isinstance(b.value, six.integer_types))
+ b = BigInt.objects.all()[0]
+ self.assertTrue(isinstance(b.value, six.integer_types))
+
+ def test_coercing(self):
+ BigInt.objects.create(value ='10')
+ b = BigInt.objects.get(value = '10')
+ self.assertEqual(b.value, 10)
+
+class TypeCoercionTests(test.TestCase):
+ """
+ Test that database lookups can accept the wrong types and convert
+ them with no error: especially on Postgres 8.3+ which does not do
+ automatic casting at the DB level. See #10015.
+
+ """
+ def test_lookup_integer_in_charfield(self):
+ self.assertEqual(Post.objects.filter(title=9).count(), 0)
+
+ def test_lookup_integer_in_textfield(self):
+ self.assertEqual(Post.objects.filter(body=24).count(), 0)
+
+class FileFieldTests(unittest.TestCase):
+ def test_clearable(self):
+ """
+ Test that FileField.save_form_data will clear its instance attribute
+ value if passed False.
+
+ """
+ d = Document(myfile='something.txt')
+ self.assertEqual(d.myfile, 'something.txt')
+ field = d._meta.get_field('myfile')
+ field.save_form_data(d, False)
+ self.assertEqual(d.myfile, '')
+
+ def test_unchanged(self):
+ """
+ Test that FileField.save_form_data considers None to mean "no change"
+ rather than "clear".
+
+ """
+ d = Document(myfile='something.txt')
+ self.assertEqual(d.myfile, 'something.txt')
+ field = d._meta.get_field('myfile')
+ field.save_form_data(d, None)
+ self.assertEqual(d.myfile, 'something.txt')
+
+ def test_changed(self):
+ """
+ Test that FileField.save_form_data, if passed a truthy value, updates
+ its instance attribute.
+
+ """
+ d = Document(myfile='something.txt')
+ self.assertEqual(d.myfile, 'something.txt')
+ field = d._meta.get_field('myfile')
+ field.save_form_data(d, 'else.txt')
+ self.assertEqual(d.myfile, 'else.txt')