diff --git a/tests/model_fields/models.py b/tests/model_fields/models.py index 925caea888..aeeb6e88da 100644 --- a/tests/model_fields/models.py +++ b/tests/model_fields/models.py @@ -121,7 +121,7 @@ class NullBooleanModel(models.Model): class BooleanModel(models.Model): - bfield = models.BooleanField(default=None) + bfield = models.BooleanField() string = models.CharField(max_length=10, default='abc') diff --git a/tests/model_fields/test_binaryfield.py b/tests/model_fields/test_binaryfield.py new file mode 100644 index 0000000000..641dc17287 --- /dev/null +++ b/tests/model_fields/test_binaryfield.py @@ -0,0 +1,28 @@ +from django.core.exceptions import ValidationError +from django.test import TestCase +from django.utils import six + +from .models import DataModel + + +class BinaryFieldTests(TestCase): + binary_data = b'\x00\x46\xFE' + + def test_set_and_retrieve(self): + data_set = (self.binary_data, six.memoryview(self.binary_data)) + for bdata in data_set: + dm = DataModel(data=bdata) + dm.save() + dm = DataModel.objects.get(pk=dm.pk) + self.assertEqual(bytes(dm.data), bytes(bdata)) + # Resave (=update) + dm.save() + dm = DataModel.objects.get(pk=dm.pk) + self.assertEqual(bytes(dm.data), bytes(bdata)) + # Test default value + self.assertEqual(bytes(dm.short_data), b'\x08') + + def test_max_length(self): + dm = DataModel(short_data=self.binary_data * 4) + with self.assertRaises(ValidationError): + dm.full_clean() diff --git a/tests/model_fields/test_booleanfield.py b/tests/model_fields/test_booleanfield.py new file mode 100644 index 0000000000..2975f4723c --- /dev/null +++ b/tests/model_fields/test_booleanfield.py @@ -0,0 +1,119 @@ +from django.core.exceptions import ValidationError +from django.db import IntegrityError, connection, models, transaction +from django.test import SimpleTestCase, TestCase + +from .models import BooleanModel, FksToBooleans, NullBooleanModel + + +class BooleanFieldTests(TestCase): + def _test_get_db_prep_lookup(self, f): + 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.assertIs(f.to_python(1), True) + self.assertIs(f.to_python(0), 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): + """ + 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=False) + self.assertEqual(f.formfield().choices, choices) + + def test_return_type(self): + b = BooleanModel.objects.create(bfield=True) + b.refresh_from_db() + self.assertEqual(b.bfield, True) + + b2 = BooleanModel.objects.create(bfield=False) + b2.refresh_from_db() + self.assertEqual(b2.bfield, False) + + b3 = NullBooleanModel.objects.create(nbfield=True) + b3.refresh_from_db() + self.assertEqual(b3.nbfield, True) + + b4 = NullBooleanModel.objects.create(nbfield=False) + b4.refresh_from_db() + self.assertEqual(b4.nbfield, False) + + # When an extra clause exists, the boolean conversions are applied with + # an offset (#13293). + b5 = BooleanModel.objects.all().extra(select={'string_col': 'string'})[0] + self.assertNotIsInstance(b5.pk, bool) + + def test_select_related(self): + """ + Boolean fields retrieved via select_related() should return booleans. + """ + 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) + + # select_related('fk_field_name') + ma = FksToBooleans.objects.select_related('bf').get(pk=m1.id) + self.assertEqual(ma.bf.bfield, True) + self.assertEqual(ma.nbf.nbfield, True) + + # select_related() + mb = FksToBooleans.objects.select_related().get(pk=m1.id) + mc = FksToBooleans.objects.select_related().get(pk=m2.id) + self.assertEqual(mb.bf.bfield, True) + self.assertEqual(mb.nbf.nbfield, True) + self.assertEqual(mc.bf.bfield, False) + self.assertEqual(mc.nbf.nbfield, False) + + def test_null_default(self): + """ + A BooleanField defaults to None, which isn't a valid value (#15124). + """ + boolean_field = BooleanModel._meta.get_field('bfield') + self.assertFalse(boolean_field.has_default()) + b = BooleanModel() + self.assertIsNone(b.bfield) + with transaction.atomic(): + with self.assertRaises(IntegrityError): + b.save() + + nb = NullBooleanModel() + self.assertIsNone(nb.nbfield) + nb.save() # no error + + +class ValidationTest(SimpleTestCase): + + def test_boolean_field_doesnt_accept_empty_input(self): + f = models.BooleanField() + with self.assertRaises(ValidationError): + f.clean(None, None) + + def test_nullbooleanfield_blank(self): + """ + NullBooleanField shouldn't throw a validation error when given a value + of None. + """ + nullboolean = NullBooleanModel(nbfield=None) + nullboolean.full_clean() diff --git a/tests/model_fields/test_charfield.py b/tests/model_fields/test_charfield.py new file mode 100644 index 0000000000..ca6e5ef9fe --- /dev/null +++ b/tests/model_fields/test_charfield.py @@ -0,0 +1,59 @@ +from django.core.exceptions import ValidationError +from django.db import models +from django.test import SimpleTestCase, TestCase +from django.utils.functional import lazy + +from .models import Post + + +class TestCharField(TestCase): + + def test_max_length_passed_to_formfield(self): + """ + CharField passes its max_length attribute to form fields created using + the formfield() method. + """ + cf1 = models.CharField() + cf2 = models.CharField(max_length=1234) + self.assertIsNone(cf1.formfield().max_length) + self.assertEqual(1234, cf2.formfield().max_length) + + def test_lookup_integer_in_charfield(self): + self.assertEqual(Post.objects.filter(title=9).count(), 0) + + +class ValidationTests(SimpleTestCase): + + def test_charfield_raises_error_on_empty_string(self): + f = models.CharField() + with 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_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')]) + with self.assertRaises(ValidationError): + f.clean('not a', None) + + def test_charfield_get_choices_with_blank_defined(self): + f = models.CharField(choices=[('', '<><>'), ('a', 'A')]) + self.assertEqual(f.get_choices(True), [('', '<><>'), ('a', 'A')]) + + def test_charfield_get_choices_doesnt_evaluate_lazy_strings(self): + # Regression test for #23098 + # Will raise ZeroDivisionError if lazy is evaluated + lazy_func = lazy(lambda x: 0 / 0, int) + f = models.CharField(choices=[(lazy_func('group'), (('a', 'A'), ('b', 'B')))]) + self.assertEqual(f.get_choices(True)[0], ('', '---------')) + + def test_charfield_raises_error_on_empty_input(self): + f = models.CharField(null=False) + with self.assertRaises(ValidationError): + f.clean(None, None) diff --git a/tests/model_fields/test_custom_fields.py b/tests/model_fields/test_custom_fields.py new file mode 100644 index 0000000000..c41e19416e --- /dev/null +++ b/tests/model_fields/test_custom_fields.py @@ -0,0 +1,22 @@ +from django.db import connection, models +from django.test import SimpleTestCase + + +class CustomFieldTests(SimpleTestCase): + + def test_get_prep_value_count(self): + """ + Field values are not prepared twice in get_db_prep_lookup() (#14786). + """ + class NoopField(models.TextField): + def __init__(self, *args, **kwargs): + self.prep_value_count = 0 + super(NoopField, self).__init__(*args, **kwargs) + + def get_prep_value(self, value): + self.prep_value_count += 1 + return super(NoopField, self).get_prep_value(value) + + field = NoopField() + field.get_db_prep_lookup('exact', 'TEST', connection=connection, prepared=False) + self.assertEqual(field.prep_value_count, 1) diff --git a/tests/model_fields/test_datetimefield.py b/tests/model_fields/test_datetimefield.py new file mode 100644 index 0000000000..60e519c609 --- /dev/null +++ b/tests/model_fields/test_datetimefield.py @@ -0,0 +1,77 @@ +import datetime + +from django.db import models +from django.test import ( + SimpleTestCase, TestCase, override_settings, skipUnlessDBFeature, +) +from django.test.utils import requires_tz_support +from django.utils import timezone + +from .models import DateTimeModel + + +class DateTimeFieldTests(TestCase): + + def test_datetimefield_to_python_microseconds(self): + """DateTimeField.to_python() supports microseconds.""" + 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_microseconds(self): + """TimeField.to_python() supports microseconds.""" + 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)) + + @skipUnlessDBFeature('supports_microsecond_precision') + def test_datetimes_save_completely(self): + dat = datetime.date(2014, 3, 12) + datetim = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000) + tim = datetime.time(21, 22, 23, 240000) + DateTimeModel.objects.create(d=dat, dt=datetim, t=tim) + obj = DateTimeModel.objects.first() + self.assertTrue(obj) + self.assertEqual(obj.d, dat) + self.assertEqual(obj.dt, datetim) + self.assertEqual(obj.t, tim) + + @override_settings(USE_TZ=False) + def test_lookup_date_without_use_tz(self): + d = datetime.date(2014, 3, 12) + dt1 = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000) + dt2 = datetime.datetime(2014, 3, 11, 21, 22, 23, 240000) + t = datetime.time(21, 22, 23, 240000) + m = DateTimeModel.objects.create(d=d, dt=dt1, t=t) + # Other model with different datetime. + DateTimeModel.objects.create(d=d, dt=dt2, t=t) + self.assertEqual(m, DateTimeModel.objects.get(dt__date=d)) + + @requires_tz_support + @skipUnlessDBFeature('has_zoneinfo_database') + @override_settings(USE_TZ=True, TIME_ZONE='America/Vancouver') + def test_lookup_date_with_use_tz(self): + d = datetime.date(2014, 3, 12) + # The following is equivalent to UTC 2014-03-12 18:34:23.24000. + dt1 = datetime.datetime(2014, 3, 12, 10, 22, 23, 240000, tzinfo=timezone.get_current_timezone()) + # The following is equivalent to UTC 2014-03-13 05:34:23.24000. + dt2 = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000, tzinfo=timezone.get_current_timezone()) + t = datetime.time(21, 22, 23, 240000) + m1 = DateTimeModel.objects.create(d=d, dt=dt1, t=t) + m2 = DateTimeModel.objects.create(d=d, dt=dt2, t=t) + # In Vancouver, we expect both results. + self.assertQuerysetEqual( + DateTimeModel.objects.filter(dt__date=d), + [repr(m1), repr(m2)], + ordered=False + ) + with self.settings(TIME_ZONE='UTC'): + # But in UTC, the __date only matches one of them. + self.assertQuerysetEqual(DateTimeModel.objects.filter(dt__date=d), [repr(m1)]) + + +class ValidationTest(SimpleTestCase): + + def test_datefield_cleans_date(self): + f = models.DateField() + self.assertEqual(datetime.date(2008, 10, 10), f.clean('2008-10-10', None)) diff --git a/tests/model_fields/test_decimalfield.py b/tests/model_fields/test_decimalfield.py new file mode 100644 index 0000000000..af9bd6c4be --- /dev/null +++ b/tests/model_fields/test_decimalfield.py @@ -0,0 +1,74 @@ +from decimal import Decimal + +from django.core import validators +from django.core.exceptions import ValidationError +from django.db import connection, models +from django.test import TestCase + +from .models import BigD, Foo + + +class DecimalFieldTests(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')) + with 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): + 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): + """ + Should be able to filter decimal fields using strings (#8023). + """ + foo = Foo.objects.create(a='abc', d=Decimal('12.34')) + self.assertEqual(list(Foo.objects.filter(d='12.34')), [foo]) + + 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): + """ + Really big values can be used in a filter statement. + """ + # This should not crash. + Foo.objects.filter(d__gte=100000000000) + + def test_max_digits_validation(self): + field = models.DecimalField(max_digits=2) + expected_message = validators.DecimalValidator.messages['max_digits'] % {'max': 2} + with self.assertRaisesMessage(ValidationError, expected_message): + field.clean(100, None) + + def test_max_decimal_places_validation(self): + field = models.DecimalField(decimal_places=1) + expected_message = validators.DecimalValidator.messages['max_decimal_places'] % {'max': 1} + with self.assertRaisesMessage(ValidationError, expected_message): + field.clean(Decimal('0.99'), None) + + def test_max_whole_digits_validation(self): + field = models.DecimalField(max_digits=3, decimal_places=1) + expected_message = validators.DecimalValidator.messages['max_whole_digits'] % {'max': 2} + with self.assertRaisesMessage(ValidationError, expected_message): + field.clean(Decimal('999'), None) diff --git a/tests/model_fields/test_filefield.py b/tests/model_fields/test_filefield.py new file mode 100644 index 0000000000..3b0ef5726d --- /dev/null +++ b/tests/model_fields/test_filefield.py @@ -0,0 +1,52 @@ +from django.test import TestCase + +from .models import Document + + +class FileFieldTests(TestCase): + + def test_clearable(self): + """ + 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): + """ + 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): + """ + 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') + + def test_delete_when_file_unset(self): + """ + Calling delete on an unset FileField should not call the file deletion + process, but fail silently (#20660). + """ + d = Document() + d.myfile.delete() + + def test_refresh_from_db(self): + d = Document.objects.create(myfile='something.txt') + d.refresh_from_db() + self.assertIs(d.myfile.instance, d) diff --git a/tests/model_fields/test_floatfield.py b/tests/model_fields/test_floatfield.py new file mode 100644 index 0000000000..52f61c88d5 --- /dev/null +++ b/tests/model_fields/test_floatfield.py @@ -0,0 +1,34 @@ +from django.db import transaction +from django.test import TestCase + +from .models import FloatModel + + +class TestFloatField(TestCase): + + def test_float_validates_object(self): + instance = FloatModel(size=2.5) + # Try setting float field to unsaved object + instance.size = instance + with transaction.atomic(): + with self.assertRaises(TypeError): + instance.save() + # Set value to valid and save + instance.size = 2.5 + instance.save() + self.assertTrue(instance.id) + # Set field to object on saved instance + instance.size = instance + msg = ( + 'Tried to update field model_fields.FloatModel.size with a model ' + 'instance, . Use a value ' + 'compatible with FloatField.' + ) + with transaction.atomic(): + with self.assertRaisesMessage(TypeError, msg): + instance.save() + # Try setting field to object on retrieved object + obj = FloatModel.objects.get(pk=instance.id) + obj.size = obj + with self.assertRaises(TypeError): + obj.save() diff --git a/tests/model_fields/test_foreignkey.py b/tests/model_fields/test_foreignkey.py new file mode 100644 index 0000000000..a521d1aa23 --- /dev/null +++ b/tests/model_fields/test_foreignkey.py @@ -0,0 +1,96 @@ +from decimal import Decimal + +from django.apps import apps +from django.core import checks +from django.db import models +from django.test import TestCase, skipIfDBFeature +from django.test.utils import isolate_apps +from django.utils import six + +from .models import Bar, FkToChar, Foo, PrimaryKeyCharModel + + +class ForeignKeyTests(TestCase): + + def test_callable_default(self): + """A lazy callable may be used 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) + + @skipIfDBFeature('interprets_empty_strings_as_nulls') + def test_empty_string_fk(self): + """ + Empty strings foreign key values don't get converted to None (#19299). + """ + char_model_empty = PrimaryKeyCharModel.objects.create(string='') + fk_model_empty = FkToChar.objects.create(out=char_model_empty) + fk_model_empty = FkToChar.objects.select_related('out').get(id=fk_model_empty.pk) + self.assertEqual(fk_model_empty.out, char_model_empty) + + @isolate_apps('model_fields') + def test_warning_when_unique_true_on_fk(self): + class Foo(models.Model): + pass + + class FKUniqueTrue(models.Model): + fk_field = models.ForeignKey(Foo, models.CASCADE, unique=True) + + model = FKUniqueTrue() + expected_warnings = [ + checks.Warning( + 'Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.', + hint='ForeignKey(unique=True) is usually better served by a OneToOneField.', + obj=FKUniqueTrue.fk_field.field, + id='fields.W342', + ) + ] + warnings = model.check() + self.assertEqual(warnings, expected_warnings) + + def test_related_name_converted_to_text(self): + rel_name = Bar._meta.get_field('a').remote_field.related_name + self.assertIsInstance(rel_name, six.text_type) + + def test_abstract_model_pending_operations(self): + """ + Foreign key fields declared on abstract models should not add lazy + relations to resolve relationship declared as string (#24215). + """ + pending_ops_before = list(apps._pending_operations.items()) + + class AbstractForeignKeyModel(models.Model): + fk = models.ForeignKey('missing.FK', models.CASCADE) + + class Meta: + abstract = True + + self.assertIs(AbstractForeignKeyModel._meta.apps, apps) + self.assertEqual( + pending_ops_before, + list(apps._pending_operations.items()), + 'Pending lookup added for a foreign key on an abstract model' + ) + + @isolate_apps('model_fields', 'model_fields.tests') + def test_abstract_model_app_relative_foreign_key(self): + class AbstractReferent(models.Model): + reference = models.ForeignKey('Refered', on_delete=models.CASCADE) + + class Meta: + app_label = 'model_fields' + abstract = True + + def assert_app_model_resolved(label): + class Refered(models.Model): + class Meta: + app_label = label + + class ConcreteReferent(AbstractReferent): + class Meta: + app_label = label + + self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) + + assert_app_model_resolved('model_fields') + assert_app_model_resolved('tests') diff --git a/tests/model_fields/test_genericipaddressfield.py b/tests/model_fields/test_genericipaddressfield.py new file mode 100644 index 0000000000..7a3705ef18 --- /dev/null +++ b/tests/model_fields/test_genericipaddressfield.py @@ -0,0 +1,35 @@ +from django.core.exceptions import ValidationError +from django.db import models +from django.test import TestCase + +from .models import GenericIPAddress + + +class GenericIPAddressFieldTests(TestCase): + + def test_genericipaddressfield_formfield_protocol(self): + """ + GenericIPAddressField with a specified protocol does not generate a + formfield without a protocol. + """ + model_field = models.GenericIPAddressField(protocol='IPv4') + form_field = model_field.formfield() + with self.assertRaises(ValidationError): + form_field.clean('::1') + model_field = models.GenericIPAddressField(protocol='IPv6') + form_field = model_field.formfield() + with self.assertRaises(ValidationError): + form_field.clean('127.0.0.1') + + def test_null_value(self): + """ + Null values should be resolved to None. + """ + GenericIPAddress.objects.create() + o = GenericIPAddress.objects.get() + self.assertIsNone(o.ip) + + def test_save_load(self): + instance = GenericIPAddress.objects.create(ip='::1') + loaded = GenericIPAddress.objects.get() + self.assertEqual(loaded.ip, instance.ip) diff --git a/tests/model_fields/test_integerfield.py b/tests/model_fields/test_integerfield.py new file mode 100644 index 0000000000..27cbdb5259 --- /dev/null +++ b/tests/model_fields/test_integerfield.py @@ -0,0 +1,161 @@ +from django.core import validators +from django.core.exceptions import ValidationError +from django.db import connection, models +from django.test import SimpleTestCase, TestCase +from django.utils import six + +from .models import ( + BigIntegerModel, IntegerModel, PositiveIntegerModel, + PositiveSmallIntegerModel, SmallIntegerModel, +) + + +class IntegerFieldTests(TestCase): + model = IntegerModel + documented_range = (-2147483648, 2147483647) + + @property + def backend_range(self): + field = self.model._meta.get_field('value') + internal_type = field.get_internal_type() + return connection.ops.integer_field_range(internal_type) + + def test_documented_range(self): + """ + Values within the documented safe range pass validation, and can be + saved and retrieved without corruption. + """ + min_value, max_value = self.documented_range + + instance = self.model(value=min_value) + instance.full_clean() + instance.save() + qs = self.model.objects.filter(value__lte=min_value) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, min_value) + + instance = self.model(value=max_value) + instance.full_clean() + instance.save() + qs = self.model.objects.filter(value__gte=max_value) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, max_value) + + def test_backend_range_save(self): + """ + Backend specific ranges can be saved without corruption. + """ + min_value, max_value = self.backend_range + + if min_value is not None: + instance = self.model(value=min_value) + instance.full_clean() + instance.save() + qs = self.model.objects.filter(value__lte=min_value) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, min_value) + + if max_value is not None: + instance = self.model(value=max_value) + instance.full_clean() + instance.save() + qs = self.model.objects.filter(value__gte=max_value) + self.assertEqual(qs.count(), 1) + self.assertEqual(qs[0].value, max_value) + + def test_backend_range_validation(self): + """ + Backend specific ranges are enforced at the model validation level + (#12030). + """ + min_value, max_value = self.backend_range + + if min_value is not None: + instance = self.model(value=min_value - 1) + expected_message = validators.MinValueValidator.message % { + 'limit_value': min_value, + } + with self.assertRaisesMessage(ValidationError, expected_message): + instance.full_clean() + instance.value = min_value + instance.full_clean() + + if max_value is not None: + instance = self.model(value=max_value + 1) + expected_message = validators.MaxValueValidator.message % { + 'limit_value': max_value, + } + with self.assertRaisesMessage(ValidationError, expected_message): + instance.full_clean() + instance.value = max_value + instance.full_clean() + + def test_types(self): + instance = self.model(value=0) + self.assertIsInstance(instance.value, six.integer_types) + instance.save() + self.assertIsInstance(instance.value, six.integer_types) + instance = self.model.objects.get() + self.assertIsInstance(instance.value, six.integer_types) + + def test_coercing(self): + self.model.objects.create(value='10') + instance = self.model.objects.get(value='10') + self.assertEqual(instance.value, 10) + + +class SmallIntegerFieldTests(IntegerFieldTests): + model = SmallIntegerModel + documented_range = (-32768, 32767) + + +class BigIntegerFieldTests(IntegerFieldTests): + model = BigIntegerModel + documented_range = (-9223372036854775808, 9223372036854775807) + + +class PositiveSmallIntegerFieldTests(IntegerFieldTests): + model = PositiveSmallIntegerModel + documented_range = (0, 32767) + + +class PositiveIntegerFieldTests(IntegerFieldTests): + model = PositiveIntegerModel + documented_range = (0, 2147483647) + + +class ValidationTests(SimpleTestCase): + + def test_integerfield_cleans_valid_string(self): + f = models.IntegerField() + self.assertEqual(f.clean('2', None), 2) + + def test_integerfield_raises_error_on_invalid_intput(self): + f = models.IntegerField() + with self.assertRaises(ValidationError): + f.clean('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) + with 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.assertIsNone(f.clean(None, None)) + + def test_integerfield_raises_error_on_empty_input(self): + f = models.IntegerField(null=False) + with self.assertRaises(ValidationError): + f.clean(None, None) + with self.assertRaises(ValidationError): + f.clean('', None) + + def test_integerfield_validates_zero_against_choices(self): + f = models.IntegerField(choices=((1, 1),)) + with self.assertRaises(ValidationError): + f.clean('0', None) diff --git a/tests/model_fields/test_manytomanyfield.py b/tests/model_fields/test_manytomanyfield.py new file mode 100644 index 0000000000..f368c18403 --- /dev/null +++ b/tests/model_fields/test_manytomanyfield.py @@ -0,0 +1,58 @@ +from django.apps import apps +from django.db import models +from django.test import SimpleTestCase +from django.test.utils import isolate_apps + + +class ManyToManyFieldTests(SimpleTestCase): + + def test_abstract_model_pending_operations(self): + """ + Many-to-many fields declared on abstract models should not add lazy + relations to resolve relationship declared as string (#24215). + """ + pending_ops_before = list(apps._pending_operations.items()) + + class AbstractManyToManyModel(models.Model): + fk = models.ForeignKey('missing.FK', models.CASCADE) + + class Meta: + abstract = True + + self.assertIs(AbstractManyToManyModel._meta.apps, apps) + self.assertEqual( + pending_ops_before, + list(apps._pending_operations.items()), + 'Pending lookup added for a many-to-many field on an abstract model' + ) + + @isolate_apps('model_fields', 'model_fields.tests') + def test_abstract_model_app_relative_foreign_key(self): + class AbstractReferent(models.Model): + reference = models.ManyToManyField('Refered', through='Through') + + class Meta: + app_label = 'model_fields' + abstract = True + + def assert_app_model_resolved(label): + class Refered(models.Model): + class Meta: + app_label = label + + class Through(models.Model): + refered = models.ForeignKey('Refered', on_delete=models.CASCADE) + referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE) + + class Meta: + app_label = label + + class ConcreteReferent(AbstractReferent): + class Meta: + app_label = label + + self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) + self.assertEqual(ConcreteReferent.reference.through, Through) + + assert_app_model_resolved('model_fields') + assert_app_model_resolved('tests') diff --git a/tests/model_fields/test_promises.py b/tests/model_fields/test_promises.py new file mode 100644 index 0000000000..c1b476e75e --- /dev/null +++ b/tests/model_fields/test_promises.py @@ -0,0 +1,138 @@ +from __future__ import unicode_literals + +import datetime +import unittest +from decimal import Decimal + +from django.db.models.fields import ( + AutoField, BigIntegerField, BinaryField, BooleanField, CharField, + CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField, + EmailField, FilePathField, FloatField, GenericIPAddressField, IntegerField, + IPAddressField, NullBooleanField, PositiveIntegerField, + PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField, + TimeField, URLField, +) +from django.db.models.fields.files import FileField, ImageField +from django.test import SimpleTestCase +from django.utils import six +from django.utils.functional import lazy + + +class PromiseTest(SimpleTestCase): + + def test_AutoField(self): + lazy_func = lazy(lambda: 1, int) + self.assertIsInstance(AutoField(primary_key=True).get_prep_value(lazy_func()), int) + + @unittest.skipIf(six.PY3, 'Python 3 has no `long` type.') + def test_BigIntegerField(self): + lazy_func = lazy(lambda: long(9999999999999999999), long) # NOQA: long undefined on PY3 + self.assertIsInstance(BigIntegerField().get_prep_value(lazy_func()), long) # NOQA + + def test_BinaryField(self): + lazy_func = lazy(lambda: b'', bytes) + self.assertIsInstance(BinaryField().get_prep_value(lazy_func()), bytes) + + def test_BooleanField(self): + lazy_func = lazy(lambda: True, bool) + self.assertIsInstance(BooleanField().get_prep_value(lazy_func()), bool) + + def test_CharField(self): + lazy_func = lazy(lambda: '', six.text_type) + self.assertIsInstance(CharField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(CharField().get_prep_value(lazy_func()), six.text_type) + + def test_CommaSeparatedIntegerField(self): + lazy_func = lazy(lambda: '1,2', six.text_type) + self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value(lazy_func()), six.text_type) + + def test_DateField(self): + lazy_func = lazy(lambda: datetime.date.today(), datetime.date) + self.assertIsInstance(DateField().get_prep_value(lazy_func()), datetime.date) + + def test_DateTimeField(self): + lazy_func = lazy(lambda: datetime.datetime.now(), datetime.datetime) + self.assertIsInstance(DateTimeField().get_prep_value(lazy_func()), datetime.datetime) + + def test_DecimalField(self): + lazy_func = lazy(lambda: Decimal('1.2'), Decimal) + self.assertIsInstance(DecimalField().get_prep_value(lazy_func()), Decimal) + + def test_EmailField(self): + lazy_func = lazy(lambda: 'mailbox@domain.com', six.text_type) + self.assertIsInstance(EmailField().get_prep_value(lazy_func()), six.text_type) + + def test_FileField(self): + lazy_func = lazy(lambda: 'filename.ext', six.text_type) + self.assertIsInstance(FileField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(FileField().get_prep_value(lazy_func()), six.text_type) + + def test_FilePathField(self): + lazy_func = lazy(lambda: 'tests.py', six.text_type) + self.assertIsInstance(FilePathField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(FilePathField().get_prep_value(lazy_func()), six.text_type) + + def test_FloatField(self): + lazy_func = lazy(lambda: 1.2, float) + self.assertIsInstance(FloatField().get_prep_value(lazy_func()), float) + + def test_ImageField(self): + lazy_func = lazy(lambda: 'filename.ext', six.text_type) + self.assertIsInstance(ImageField().get_prep_value(lazy_func()), six.text_type) + + def test_IntegerField(self): + lazy_func = lazy(lambda: 1, int) + self.assertIsInstance(IntegerField().get_prep_value(lazy_func()), int) + + def test_IPAddressField(self): + lazy_func = lazy(lambda: '127.0.0.1', six.text_type) + self.assertIsInstance(IPAddressField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(IPAddressField().get_prep_value(lazy_func()), six.text_type) + + def test_GenericIPAddressField(self): + lazy_func = lazy(lambda: '127.0.0.1', six.text_type) + self.assertIsInstance(GenericIPAddressField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(GenericIPAddressField().get_prep_value(lazy_func()), six.text_type) + + def test_NullBooleanField(self): + lazy_func = lazy(lambda: True, bool) + self.assertIsInstance(NullBooleanField().get_prep_value(lazy_func()), bool) + + def test_PositiveIntegerField(self): + lazy_func = lazy(lambda: 1, int) + self.assertIsInstance(PositiveIntegerField().get_prep_value(lazy_func()), int) + + def test_PositiveSmallIntegerField(self): + lazy_func = lazy(lambda: 1, int) + self.assertIsInstance(PositiveSmallIntegerField().get_prep_value(lazy_func()), int) + + def test_SlugField(self): + lazy_func = lazy(lambda: 'slug', six.text_type) + self.assertIsInstance(SlugField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(SlugField().get_prep_value(lazy_func()), six.text_type) + + def test_SmallIntegerField(self): + lazy_func = lazy(lambda: 1, int) + self.assertIsInstance(SmallIntegerField().get_prep_value(lazy_func()), int) + + def test_TextField(self): + lazy_func = lazy(lambda: 'Abc', six.text_type) + self.assertIsInstance(TextField().get_prep_value(lazy_func()), six.text_type) + lazy_func = lazy(lambda: 0, int) + self.assertIsInstance(TextField().get_prep_value(lazy_func()), six.text_type) + + def test_TimeField(self): + lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time) + self.assertIsInstance(TimeField().get_prep_value(lazy_func()), datetime.time) + + def test_URLField(self): + lazy_func = lazy(lambda: 'http://domain.com', six.text_type) + self.assertIsInstance(URLField().get_prep_value(lazy_func()), six.text_type) diff --git a/tests/model_fields/test_slugfield.py b/tests/model_fields/test_slugfield.py new file mode 100644 index 0000000000..45a4527ad9 --- /dev/null +++ b/tests/model_fields/test_slugfield.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.test import TestCase + +from .models import BigS, UnicodeSlugField + + +class SlugFieldTests(TestCase): + + def test_slugfield_max_length(self): + """ + SlugField honors max_length. + """ + bs = BigS.objects.create(s='slug' * 50) + bs = BigS.objects.get(pk=bs.pk) + self.assertEqual(bs.s, 'slug' * 50) + + def test_slugfield_unicode_max_length(self): + """ + SlugField with allow_unicode=True honors max_length. + """ + bs = UnicodeSlugField.objects.create(s='你好你好' * 50) + bs = UnicodeSlugField.objects.get(pk=bs.pk) + self.assertEqual(bs.s, '你好你好' * 50) diff --git a/tests/model_fields/test_textfield.py b/tests/model_fields/test_textfield.py new file mode 100644 index 0000000000..1c7a6891a8 --- /dev/null +++ b/tests/model_fields/test_textfield.py @@ -0,0 +1,25 @@ +from django.db import models +from django.test import TestCase + +from .models import Post + + +class TextFieldTests(TestCase): + + def test_max_length_passed_to_formfield(self): + """ + TextField passes its max_length attribute to form fields created using + their formfield() method. + """ + tf1 = models.TextField() + tf2 = models.TextField(max_length=2345) + self.assertIsNone(tf1.formfield().max_length) + self.assertEqual(2345, tf2.formfield().max_length) + + def test_to_python(self): + """TextField.to_python() should return a string.""" + f = models.TextField() + self.assertEqual(f.to_python(1), '1') + + def test_lookup_integer_in_textfield(self): + self.assertEqual(Post.objects.filter(body=24).count(), 0) diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 70f039e30e..7b2e8f3aa7 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -1,44 +1,19 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import datetime -import unittest -from decimal import Decimal - -from django import forms, test -from django.apps import apps -from django.core import checks, validators -from django.core.exceptions import ValidationError -from django.db import IntegrityError, connection, models, transaction -from django.db.models.fields import ( - NOT_PROVIDED, AutoField, BigIntegerField, BinaryField, BooleanField, - CharField, CommaSeparatedIntegerField, DateField, DateTimeField, - DecimalField, EmailField, FilePathField, FloatField, GenericIPAddressField, - IntegerField, IPAddressField, NullBooleanField, PositiveIntegerField, - PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField, - TimeField, URLField, -) -from django.db.models.fields.files import FileField, ImageField -from django.test.utils import isolate_apps, requires_tz_support -from django.utils import six, timezone +from django import forms +from django.db import models +from django.test import SimpleTestCase, TestCase from django.utils.encoding import force_str -from django.utils.functional import lazy from .models import ( - Bar, BigD, BigIntegerModel, BigS, BooleanModel, DataModel, DateTimeModel, - Document, FksToBooleans, FkToChar, FloatModel, Foo, GenericIPAddress, - IntegerModel, NullBooleanModel, PositiveIntegerModel, - PositiveSmallIntegerModel, Post, PrimaryKeyCharModel, RenamedField, - SmallIntegerModel, UnicodeSlugField, VerboseNameField, Whiz, WhizIter, - WhizIterEmpty, + Foo, RenamedField, VerboseNameField, Whiz, WhizIter, WhizIterEmpty, ) -class BasicFieldTests(test.TestCase): +class BasicFieldTests(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() + Fields with choices respect show_hidden_initial as a kwarg to + formfield(). """ choices = [(0, 0), (1, 1)] model_field = models.Field(choices=choices) @@ -48,20 +23,9 @@ class BasicFieldTests(test.TestCase): 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 + __repr__() of a field displays its name. """ f = Foo._meta.get_field('a') self.assertEqual(repr(f), '') @@ -70,8 +34,8 @@ class BasicFieldTests(test.TestCase): def test_field_name(self): """ - Regression test for #14695: explicitly defined field name overwritten - by model's attribute name. + A defined field name (name="fieldname") is used instead of the model + model's attribute name (modelname). """ instance = RenamedField() self.assertTrue(hasattr(instance, 'get_fieldname_display')) @@ -80,40 +44,12 @@ class BasicFieldTests(test.TestCase): def test_field_verbose_name(self): m = VerboseNameField for i in range(1, 24): - self.assertEqual(m._meta.get_field('field%d' % i).verbose_name, - 'verbose field%d' % i) + 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_float_validates_object(self): - instance = FloatModel(size=2.5) - # Try setting float field to unsaved object - instance.size = instance - with transaction.atomic(): - with self.assertRaises(TypeError): - instance.save() - # Set value to valid and save - instance.size = 2.5 - instance.save() - self.assertTrue(instance.id) - # Set field to object on saved instance - instance.size = instance - msg = ( - "Tried to update field model_fields.FloatModel.size with a model " - "instance, . Use a value " - "compatible with FloatField." - ) - with transaction.atomic(): - with self.assertRaisesMessage(TypeError, msg): - instance.save() - # Try setting field to object on retrieved object - obj = FloatModel.objects.get(pk=instance.id) - obj.size = obj - with self.assertRaises(TypeError): - obj.save() - def test_choices_form_class(self): - """Can supply a custom choices form class. Regression for #20999.""" + """Can supply a custom choices form class to Field.formfield()""" choices = [('a', 'a')] field = models.CharField(choices=choices) klass = forms.TypedMultipleChoiceField @@ -121,448 +57,15 @@ class BasicFieldTests(test.TestCase): def test_field_str(self): f = Foo._meta.get_field('a') - self.assertEqual(force_str(f), "model_fields.Foo.a") + self.assertEqual(force_str(f), 'model_fields.Foo.a') -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")) - with self.assertRaises(ValidationError): - f.to_python("abc") +class ChoicesTests(SimpleTestCase): - 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): - 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) - - def test_max_digits_validation(self): - field = models.DecimalField(max_digits=2) - expected_message = validators.DecimalValidator.messages['max_digits'] % {'max': 2} - with self.assertRaisesMessage(ValidationError, expected_message): - field.clean(100, None) - - def test_max_decimal_places_validation(self): - field = models.DecimalField(decimal_places=1) - expected_message = validators.DecimalValidator.messages['max_decimal_places'] % {'max': 1} - with self.assertRaisesMessage(ValidationError, expected_message): - field.clean(Decimal('0.99'), None) - - def test_max_whole_digits_validation(self): - field = models.DecimalField(max_digits=3, decimal_places=1) - expected_message = validators.DecimalValidator.messages['max_whole_digits'] % {'max': 2} - with self.assertRaisesMessage(ValidationError, expected_message): - field.clean(Decimal('999'), None) - - -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) - - @test.skipIfDBFeature('interprets_empty_strings_as_nulls') - def test_empty_string_fk(self): - """ - Test that foreign key values to empty strings don't get converted - to None (#19299) - """ - char_model_empty = PrimaryKeyCharModel.objects.create(string='') - fk_model_empty = FkToChar.objects.create(out=char_model_empty) - fk_model_empty = FkToChar.objects.select_related('out').get(id=fk_model_empty.pk) - self.assertEqual(fk_model_empty.out, char_model_empty) - - @isolate_apps('model_fields') - def test_warning_when_unique_true_on_fk(self): - class Foo(models.Model): - pass - - class FKUniqueTrue(models.Model): - fk_field = models.ForeignKey(Foo, models.CASCADE, unique=True) - - model = FKUniqueTrue() - expected_warnings = [ - checks.Warning( - 'Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.', - hint='ForeignKey(unique=True) is usually better served by a OneToOneField.', - obj=FKUniqueTrue.fk_field.field, - id='fields.W342', - ) - ] - warnings = model.check() - self.assertEqual(warnings, expected_warnings) - - def test_related_name_converted_to_text(self): - rel_name = Bar._meta.get_field('a').remote_field.related_name - self.assertIsInstance(rel_name, six.text_type) - - def test_abstract_model_pending_operations(self): - """ - Foreign key fields declared on abstract models should not add lazy relations to - resolve relationship declared as string. refs #24215 - """ - pending_ops_before = list(apps._pending_operations.items()) - - class AbstractForeignKeyModel(models.Model): - fk = models.ForeignKey('missing.FK', models.CASCADE) - - class Meta: - abstract = True - - self.assertIs(AbstractForeignKeyModel._meta.apps, apps) - self.assertEqual( - pending_ops_before, - list(apps._pending_operations.items()), - "Pending lookup added for a foreign key on an abstract model" - ) - - @isolate_apps('model_fields', 'model_fields.tests') - def test_abstract_model_app_relative_foreign_key(self): - class AbstractReferent(models.Model): - reference = models.ForeignKey('Refered', on_delete=models.CASCADE) - - class Meta: - app_label = 'model_fields' - abstract = True - - def assert_app_model_resolved(label): - class Refered(models.Model): - class Meta: - app_label = label - - class ConcreteReferent(AbstractReferent): - class Meta: - app_label = label - - self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) - - assert_app_model_resolved('model_fields') - assert_app_model_resolved('tests') - - -class ManyToManyFieldTests(test.SimpleTestCase): - def test_abstract_model_pending_operations(self): - """ - Many-to-many fields declared on abstract models should not add lazy relations to - resolve relationship declared as string. refs #24215 - """ - pending_ops_before = list(apps._pending_operations.items()) - - class AbstractManyToManyModel(models.Model): - fk = models.ForeignKey('missing.FK', models.CASCADE) - - class Meta: - abstract = True - - self.assertIs(AbstractManyToManyModel._meta.apps, apps) - self.assertEqual( - pending_ops_before, - list(apps._pending_operations.items()), - "Pending lookup added for a many-to-many field on an abstract model" - ) - - @isolate_apps('model_fields', 'model_fields.tests') - def test_abstract_model_app_relative_foreign_key(self): - class AbstractReferent(models.Model): - reference = models.ManyToManyField('Refered', through='Through') - - class Meta: - app_label = 'model_fields' - abstract = True - - def assert_app_model_resolved(label): - class Refered(models.Model): - class Meta: - app_label = label - - class Through(models.Model): - refered = models.ForeignKey('Refered', on_delete=models.CASCADE) - referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE) - - class Meta: - app_label = label - - class ConcreteReferent(AbstractReferent): - class Meta: - app_label = label - - self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered) - self.assertEqual(ConcreteReferent.reference.through, Through) - - assert_app_model_resolved('model_fields') - assert_app_model_resolved('tests') - - -class TextFieldTests(test.TestCase): - def test_to_python(self): - """TextField.to_python() should return a string""" - f = models.TextField() - self.assertEqual(f.to_python(1), '1') - - -class DateTimeFieldTests(test.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)) - - @test.skipUnlessDBFeature("supports_microsecond_precision") - def test_datetimes_save_completely(self): - dat = datetime.date(2014, 3, 12) - datetim = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000) - tim = datetime.time(21, 22, 23, 240000) - DateTimeModel.objects.create(d=dat, dt=datetim, t=tim) - obj = DateTimeModel.objects.first() - self.assertTrue(obj) - self.assertEqual(obj.d, dat) - self.assertEqual(obj.dt, datetim) - self.assertEqual(obj.t, tim) - - @test.override_settings(USE_TZ=False) - def test_lookup_date_without_use_tz(self): - d = datetime.date(2014, 3, 12) - dt1 = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000) - dt2 = datetime.datetime(2014, 3, 11, 21, 22, 23, 240000) - t = datetime.time(21, 22, 23, 240000) - m = DateTimeModel.objects.create(d=d, dt=dt1, t=t) - # Other model with different datetime. - DateTimeModel.objects.create(d=d, dt=dt2, t=t) - self.assertEqual(m, DateTimeModel.objects.get(dt__date=d)) - - @requires_tz_support - @test.skipUnlessDBFeature('has_zoneinfo_database') - @test.override_settings(USE_TZ=True, TIME_ZONE='America/Vancouver') - def test_lookup_date_with_use_tz(self): - d = datetime.date(2014, 3, 12) - # The following is equivalent to UTC 2014-03-12 18:34:23.24000. - dt1 = datetime.datetime( - 2014, 3, 12, 10, 22, 23, 240000, - tzinfo=timezone.get_current_timezone() - ) - # The following is equivalent to UTC 2014-03-13 05:34:23.24000. - dt2 = datetime.datetime( - 2014, 3, 12, 21, 22, 23, 240000, - tzinfo=timezone.get_current_timezone() - ) - t = datetime.time(21, 22, 23, 240000) - m1 = DateTimeModel.objects.create(d=d, dt=dt1, t=t) - m2 = DateTimeModel.objects.create(d=d, dt=dt2, t=t) - # In Vancouver, we expect both results. - self.assertQuerysetEqual( - DateTimeModel.objects.filter(dt__date=d), - [repr(m1), repr(m2)], - ordered=False - ) - with self.settings(TIME_ZONE='UTC'): - # But in UTC, the __date only matches one of them. - self.assertQuerysetEqual( - DateTimeModel.objects.filter(dt__date=d), - [repr(m1)] - ) - - -class BooleanFieldTests(test.TestCase): - def _test_get_db_prep_lookup(self, f): - 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.assertIs(f.to_python(1), True) - self.assertIs(f.to_python(0), 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_charfield_textfield_max_length_passed_to_formfield(self): - """ - Test that CharField and TextField pass their max_length attributes to - form fields created using their .formfield() method (#22206). - """ - cf1 = models.CharField() - cf2 = models.CharField(max_length=1234) - self.assertIsNone(cf1.formfield().max_length) - self.assertEqual(1234, cf2.formfield().max_length) - - tf1 = models.TextField() - tf2 = models.TextField(max_length=2345) - self.assertIsNone(tf1.formfield().max_length) - self.assertEqual(2345, tf2.formfield().max_length) - - 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=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.assertIsInstance(b2.bfield, bool) - self.assertEqual(b2.bfield, True) - - b3 = BooleanModel() - b3.bfield = False - b3.save() - b4 = BooleanModel.objects.get(pk=b3.pk) - self.assertIsInstance(b4.bfield, bool) - self.assertEqual(b4.bfield, False) - - b = NullBooleanModel() - b.nbfield = True - b.save() - b2 = NullBooleanModel.objects.get(pk=b.pk) - self.assertIsInstance(b2.nbfield, bool) - self.assertEqual(b2.nbfield, True) - - b3 = NullBooleanModel() - b3.nbfield = False - b3.save() - b4 = NullBooleanModel.objects.get(pk=b3.pk) - self.assertIsInstance(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.assertNotIsInstance(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 -- shouldn'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) - - def test_null_default(self): - """ - Check that a BooleanField defaults to None -- which isn't - a valid value (#15124). - """ - # Patch the boolean field's default value. We give it a default - # value when defining the model to satisfy the check tests - # #20895. - boolean_field = BooleanModel._meta.get_field('bfield') - self.assertTrue(boolean_field.has_default()) - old_default = boolean_field.default - try: - boolean_field.default = NOT_PROVIDED - # check patch was successful - self.assertFalse(boolean_field.has_default()) - b = BooleanModel() - self.assertIsNone(b.bfield) - with transaction.atomic(): - with self.assertRaises(IntegrityError): - b.save() - finally: - boolean_field.default = old_default - - nb = NullBooleanModel() - self.assertIsNone(nb.nbfield) - nb.save() # no error - - -class ChoicesTests(test.SimpleTestCase): def test_choices_and_field_display(self): """ - Check that get_choices() interacts with get_FIELD_display() to return - the expected values (#7913). + get_choices() interacts with get_FIELD_display() to return the expected + values. """ 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 @@ -572,7 +75,7 @@ class ChoicesTests(test.SimpleTestCase): def test_iterator_choices(self): """ - Check that get_choices works with Iterators (#23112). + get_choices() works with Iterators. """ self.assertEqual(WhizIter(c=1).c, 1) # A nested value self.assertEqual(WhizIter(c=9).c, 9) # Invalid value @@ -581,547 +84,9 @@ class ChoicesTests(test.SimpleTestCase): def test_empty_iterator_choices(self): """ - Check that get_choices works with empty iterators (#23112). + get_choices() works with empty iterators. """ self.assertEqual(WhizIterEmpty(c="a").c, "a") # A nested value self.assertEqual(WhizIterEmpty(c="b").c, "b") # Invalid value self.assertEqual(WhizIterEmpty(c=None).c, None) # Blank value self.assertEqual(WhizIterEmpty(c='').c, '') # 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) - - def test_slugfield_unicode_max_length(self): - """ - SlugField with allow_unicode=True should honor max_length. - """ - bs = UnicodeSlugField.objects.create(s='你好你好' * 50) - bs = UnicodeSlugField.objects.get(pk=bs.pk) - self.assertEqual(bs.s, '你好你好' * 50) - - -class ValidationTest(test.SimpleTestCase): - def test_charfield_raises_error_on_empty_string(self): - f = models.CharField() - with 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() - with 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')]) - with self.assertRaises(ValidationError): - f.clean("not a", None) - - def test_charfield_get_choices_with_blank_defined(self): - f = models.CharField(choices=[('', '<><>'), ('a', 'A')]) - self.assertEqual(f.get_choices(True), [('', '<><>'), ('a', 'A')]) - - def test_charfield_get_choices_doesnt_evaluate_lazy_strings(self): - # Regression test for #23098 - # Will raise ZeroDivisionError if lazy is evaluated - lazy_func = lazy(lambda x: 0 / 0, int) - f = models.CharField(choices=[(lazy_func('group'), (('a', 'A'), ('b', 'B')))]) - self.assertEqual(f.get_choices(True)[0], ('', '---------')) - - 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) - with 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.assertIsNone(f.clean(None, None)) - - def test_integerfield_raises_error_on_empty_input(self): - f = models.IntegerField(null=False) - with self.assertRaises(ValidationError): - f.clean(None, None) - with self.assertRaises(ValidationError): - f.clean('', None) - - def test_integerfield_validates_zero_against_choices(self): - f = models.IntegerField(choices=((1, 1),)) - with self.assertRaises(ValidationError): - f.clean('0', None) - - def test_charfield_raises_error_on_empty_input(self): - f = models.CharField(null=False) - with 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() - with self.assertRaises(ValidationError): - f.clean(None, None) - - -class IntegerFieldTests(test.TestCase): - model = IntegerModel - documented_range = (-2147483648, 2147483647) - - @property - def backend_range(self): - field = self.model._meta.get_field('value') - internal_type = field.get_internal_type() - return connection.ops.integer_field_range(internal_type) - - def test_documented_range(self): - """ - Ensure that values within the documented safe range pass validation, - can be saved and retrieved without corruption. - """ - min_value, max_value = self.documented_range - - instance = self.model(value=min_value) - instance.full_clean() - instance.save() - qs = self.model.objects.filter(value__lte=min_value) - self.assertEqual(qs.count(), 1) - self.assertEqual(qs[0].value, min_value) - - instance = self.model(value=max_value) - instance.full_clean() - instance.save() - qs = self.model.objects.filter(value__gte=max_value) - self.assertEqual(qs.count(), 1) - self.assertEqual(qs[0].value, max_value) - - def test_backend_range_save(self): - """ - Ensure that backend specific range can be saved without corruption. - """ - min_value, max_value = self.backend_range - - if min_value is not None: - instance = self.model(value=min_value) - instance.full_clean() - instance.save() - qs = self.model.objects.filter(value__lte=min_value) - self.assertEqual(qs.count(), 1) - self.assertEqual(qs[0].value, min_value) - - if max_value is not None: - instance = self.model(value=max_value) - instance.full_clean() - instance.save() - qs = self.model.objects.filter(value__gte=max_value) - self.assertEqual(qs.count(), 1) - self.assertEqual(qs[0].value, max_value) - - def test_backend_range_validation(self): - """ - Ensure that backend specific range are enforced at the model - validation level. ref #12030. - """ - min_value, max_value = self.backend_range - - if min_value is not None: - instance = self.model(value=min_value - 1) - expected_message = validators.MinValueValidator.message % { - 'limit_value': min_value - } - with self.assertRaisesMessage(ValidationError, expected_message): - instance.full_clean() - instance.value = min_value - instance.full_clean() - - if max_value is not None: - instance = self.model(value=max_value + 1) - expected_message = validators.MaxValueValidator.message % { - 'limit_value': max_value - } - with self.assertRaisesMessage(ValidationError, expected_message): - instance.full_clean() - instance.value = max_value - instance.full_clean() - - def test_types(self): - instance = self.model(value=0) - self.assertIsInstance(instance.value, six.integer_types) - instance.save() - self.assertIsInstance(instance.value, six.integer_types) - instance = self.model.objects.get() - self.assertIsInstance(instance.value, six.integer_types) - - def test_coercing(self): - self.model.objects.create(value='10') - instance = self.model.objects.get(value='10') - self.assertEqual(instance.value, 10) - - -class SmallIntegerFieldTests(IntegerFieldTests): - model = SmallIntegerModel - documented_range = (-32768, 32767) - - -class BigIntegerFieldTests(IntegerFieldTests): - model = BigIntegerModel - documented_range = (-9223372036854775808, 9223372036854775807) - - -class PositiveSmallIntegerFieldTests(IntegerFieldTests): - model = PositiveSmallIntegerModel - documented_range = (0, 32767) - - -class PositiveIntegerFieldTests(IntegerFieldTests): - model = PositiveIntegerModel - documented_range = (0, 2147483647) - - -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') - - def test_delete_when_file_unset(self): - """ - Calling delete on an unset FileField should not call the file deletion - process, but fail silently (#20660). - """ - d = Document() - try: - d.myfile.delete() - except OSError: - self.fail("Deleting an unset FileField should not raise OSError.") - - def test_refresh_from_db(self): - d = Document.objects.create(myfile='something.txt') - d.refresh_from_db() - self.assertIs(d.myfile.instance, d) - - -class BinaryFieldTests(test.TestCase): - binary_data = b'\x00\x46\xFE' - - def test_set_and_retrieve(self): - data_set = (self.binary_data, six.memoryview(self.binary_data)) - for bdata in data_set: - dm = DataModel(data=bdata) - dm.save() - dm = DataModel.objects.get(pk=dm.pk) - self.assertEqual(bytes(dm.data), bytes(bdata)) - # Resave (=update) - dm.save() - dm = DataModel.objects.get(pk=dm.pk) - self.assertEqual(bytes(dm.data), bytes(bdata)) - # Test default value - self.assertEqual(bytes(dm.short_data), b'\x08') - - def test_max_length(self): - dm = DataModel(short_data=self.binary_data * 4) - with self.assertRaises(ValidationError): - dm.full_clean() - - -class GenericIPAddressFieldTests(test.TestCase): - def test_genericipaddressfield_formfield_protocol(self): - """ - Test that GenericIPAddressField with a specified protocol does not - generate a formfield with no specified protocol. See #20740. - """ - model_field = models.GenericIPAddressField(protocol='IPv4') - form_field = model_field.formfield() - with self.assertRaises(ValidationError): - form_field.clean('::1') - model_field = models.GenericIPAddressField(protocol='IPv6') - form_field = model_field.formfield() - with self.assertRaises(ValidationError): - form_field.clean('127.0.0.1') - - def test_null_value(self): - """ - Null values should be resolved to None in Python (#24078). - """ - GenericIPAddress.objects.create() - o = GenericIPAddress.objects.get() - self.assertIsNone(o.ip) - - def test_save_load(self): - instance = GenericIPAddress.objects.create(ip='::1') - loaded = GenericIPAddress.objects.get() - self.assertEqual(loaded.ip, instance.ip) - - -class PromiseTest(test.SimpleTestCase): - def test_AutoField(self): - lazy_func = lazy(lambda: 1, int) - self.assertIsInstance( - AutoField(primary_key=True).get_prep_value(lazy_func()), - int) - - @unittest.skipIf(six.PY3, "Python 3 has no `long` type.") - def test_BigIntegerField(self): - # NOQA: long undefined on PY3 - lazy_func = lazy(lambda: long(9999999999999999999), long) # NOQA - self.assertIsInstance( - BigIntegerField().get_prep_value(lazy_func()), - long) # NOQA - - def test_BinaryField(self): - lazy_func = lazy(lambda: b'', bytes) - self.assertIsInstance( - BinaryField().get_prep_value(lazy_func()), - bytes) - - def test_BooleanField(self): - lazy_func = lazy(lambda: True, bool) - self.assertIsInstance( - BooleanField().get_prep_value(lazy_func()), - bool) - - def test_CharField(self): - lazy_func = lazy(lambda: '', six.text_type) - self.assertIsInstance( - CharField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - CharField().get_prep_value(lazy_func()), - six.text_type) - - def test_CommaSeparatedIntegerField(self): - lazy_func = lazy(lambda: '1,2', six.text_type) - self.assertIsInstance( - CommaSeparatedIntegerField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - CommaSeparatedIntegerField().get_prep_value(lazy_func()), - six.text_type) - - def test_DateField(self): - lazy_func = lazy(lambda: datetime.date.today(), datetime.date) - self.assertIsInstance( - DateField().get_prep_value(lazy_func()), - datetime.date) - - def test_DateTimeField(self): - lazy_func = lazy(lambda: datetime.datetime.now(), datetime.datetime) - self.assertIsInstance( - DateTimeField().get_prep_value(lazy_func()), - datetime.datetime) - - def test_DecimalField(self): - lazy_func = lazy(lambda: Decimal('1.2'), Decimal) - self.assertIsInstance( - DecimalField().get_prep_value(lazy_func()), - Decimal) - - def test_EmailField(self): - lazy_func = lazy(lambda: 'mailbox@domain.com', six.text_type) - self.assertIsInstance( - EmailField().get_prep_value(lazy_func()), - six.text_type) - - def test_FileField(self): - lazy_func = lazy(lambda: 'filename.ext', six.text_type) - self.assertIsInstance( - FileField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - FileField().get_prep_value(lazy_func()), - six.text_type) - - def test_FilePathField(self): - lazy_func = lazy(lambda: 'tests.py', six.text_type) - self.assertIsInstance( - FilePathField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - FilePathField().get_prep_value(lazy_func()), - six.text_type) - - def test_FloatField(self): - lazy_func = lazy(lambda: 1.2, float) - self.assertIsInstance( - FloatField().get_prep_value(lazy_func()), - float) - - def test_ImageField(self): - lazy_func = lazy(lambda: 'filename.ext', six.text_type) - self.assertIsInstance( - ImageField().get_prep_value(lazy_func()), - six.text_type) - - def test_IntegerField(self): - lazy_func = lazy(lambda: 1, int) - self.assertIsInstance( - IntegerField().get_prep_value(lazy_func()), - int) - - def test_IPAddressField(self): - lazy_func = lazy(lambda: '127.0.0.1', six.text_type) - self.assertIsInstance( - IPAddressField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - IPAddressField().get_prep_value(lazy_func()), - six.text_type) - - def test_GenericIPAddressField(self): - lazy_func = lazy(lambda: '127.0.0.1', six.text_type) - self.assertIsInstance( - GenericIPAddressField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - GenericIPAddressField().get_prep_value(lazy_func()), - six.text_type) - - def test_NullBooleanField(self): - lazy_func = lazy(lambda: True, bool) - self.assertIsInstance( - NullBooleanField().get_prep_value(lazy_func()), - bool) - - def test_PositiveIntegerField(self): - lazy_func = lazy(lambda: 1, int) - self.assertIsInstance( - PositiveIntegerField().get_prep_value(lazy_func()), - int) - - def test_PositiveSmallIntegerField(self): - lazy_func = lazy(lambda: 1, int) - self.assertIsInstance( - PositiveSmallIntegerField().get_prep_value(lazy_func()), - int) - - def test_SlugField(self): - lazy_func = lazy(lambda: 'slug', six.text_type) - self.assertIsInstance( - SlugField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - SlugField().get_prep_value(lazy_func()), - six.text_type) - - def test_SmallIntegerField(self): - lazy_func = lazy(lambda: 1, int) - self.assertIsInstance( - SmallIntegerField().get_prep_value(lazy_func()), - int) - - def test_TextField(self): - lazy_func = lazy(lambda: 'Abc', six.text_type) - self.assertIsInstance( - TextField().get_prep_value(lazy_func()), - six.text_type) - lazy_func = lazy(lambda: 0, int) - self.assertIsInstance( - TextField().get_prep_value(lazy_func()), - six.text_type) - - def test_TimeField(self): - lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time) - self.assertIsInstance( - TimeField().get_prep_value(lazy_func()), - datetime.time) - - def test_URLField(self): - lazy_func = lazy(lambda: 'http://domain.com', six.text_type) - self.assertIsInstance( - URLField().get_prep_value(lazy_func()), - six.text_type) - - -class CustomFieldTests(unittest.TestCase): - - def test_14786(self): - """ - Regression test for #14786 -- Test that field values are not prepared - twice in get_db_prep_lookup(). - """ - class NoopField(models.TextField): - def __init__(self, *args, **kwargs): - self.prep_value_count = 0 - super(NoopField, self).__init__(*args, **kwargs) - - def get_prep_value(self, value): - self.prep_value_count += 1 - return super(NoopField, self).get_prep_value(value) - - field = NoopField() - field.get_db_prep_lookup( - 'exact', 'TEST', connection=connection, prepared=False - ) - self.assertEqual(field.prep_value_count, 1)