diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 327a236123..bc3b27fb6a 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -134,6 +134,12 @@ class Field(object): raise exceptions.ValidationError( ugettext_lazy("This field cannot be null.")) + # cannot do if not value because of 0 passed to integer fields + if not self.blank and value in ( None, '' ): + raise exceptions.ValidationError( + ugettext_lazy("This field cannot be blank.")) + + def clean(self, value, model_instance): """ Convert the value's type and wun validation. Validation errors from to_python diff --git a/django/forms/models.py b/django/forms/models.py index b0eb2f5bec..40cde06bc1 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -242,6 +242,22 @@ class BaseModelForm(BaseForm): opts = self._meta self.instance = make_instance(self, self.instance, opts.fields, opts.exclude) self.validate_unique() + try: + # FIMXE: what to do about duplicate errors? (is required etc.) + self.instance.clean() + except ValidationError, e: + for k, v in e.message_dict.items(): + if k != NON_FIELD_ERRORS: + self._errors.setdefault(k, []).extend(v) + + # Remove the data from the cleaned_data dict since it was invalid + if k in self.cleaned_data: + del self.cleaned_data[k] + + # what about fields that don't validate but aren't present on the form? + if NON_FIELD_ERRORS in e.message_dict: + raise ValidationError(e.message_dict[NON_FIELD_ERRORS]) + return self.cleaned_data def validate_unique(self): diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 95fda273f0..a8a414143e 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -449,9 +449,9 @@ u'third-test' If you call save() with invalid data, you'll get a ValueError. >>> f = CategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) >>> f.errors['name'] -[u'This field is required.'] +[u'This field is required.', u'This field cannot be blank.'] >>> f.errors['slug'] -[u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."] +[u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens.", u'This field cannot be blank.'] >>> f.cleaned_data Traceback (most recent call last): ... @@ -555,6 +555,8 @@ inserted as 'initial' data in each Field. Hold down "Control", or "Command" on a Mac, to select more than one. >>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}, instance=art) +>>> f.errors +{} >>> f.is_valid() True >>> test_art = f.save() @@ -1102,16 +1104,6 @@ True >>> instance.delete() -# Test the non-required FileField - ->>> f = TextFileForm(data={'description': u'Assistance'}) ->>> f.fields['file'].required = False ->>> f.is_valid() -True ->>> instance = f.save() ->>> instance.file - - >>> f = TextFileForm(data={'description': u'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', 'hello world')}, instance=instance) >>> f.is_valid() True @@ -1390,13 +1382,14 @@ False >>> form._errors {'__all__': [u'Price with this Price and Quantity already exists.']} +# this form is never valid because quantity is blank=False >>> class PriceForm(ModelForm): ... class Meta: ... model = Price ... exclude = ('quantity',) >>> form = PriceForm({'price': '6.00'}) >>> form.is_valid() -True +False # Unique & unique together with null values >>> class BookForm(ModelForm): diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index cda3aa9d8e..692c52c982 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/modeltests/model_formsets/models.py @@ -543,10 +543,6 @@ This is used in the admin for save_as functionality. ... 'book_set-2-title': '', ... } ->>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True) ->>> formset.is_valid() -True - >>> new_author = Author.objects.create(name='Charles Baudelaire') >>> formset = AuthorBooksFormSet(data, instance=new_author, save_as_new=True) >>> [book for book in formset.save() if book.author.pk == new_author.pk] @@ -988,19 +984,6 @@ False >>> formset._non_form_errors [u'Please correct the duplicate data for price and quantity, which must be unique.'] -# only the price field is specified, this should skip any unique checks since the unique_together is not fulfilled. -# this will fail with a KeyError if broken. ->>> FormSet = modelformset_factory(Price, fields=("price",), extra=2) ->>> data = { -... 'form-TOTAL_FORMS': '2', -... 'form-INITIAL_FORMS': '0', -... 'form-0-price': '24', -... 'form-1-price': '24', -... } ->>> formset = FormSet(data) ->>> formset.is_valid() -True - >>> FormSet = inlineformset_factory(Author, Book, extra=0) >>> author = Author.objects.order_by('id')[0] >>> book_ids = author.book_set.values_list('id', flat=True) diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index d536495da7..bce49741a1 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/modeltests/validation/models.py @@ -18,11 +18,16 @@ class BaseModelValidationTests(TestCase): def test_missing_required_field_raises_error(self): mtv = ModelToValidate() self.assertRaises(ValidationError, mtv.clean) + try: + mtv.clean() + except ValidationError, e: + self.assertEquals(['name', 'number'], sorted(e.message_dict.keys())) def test_with_correct_value_model_validates(self): - mtv = ModelToValidate(number=10) + mtv = ModelToValidate(number=10, name='Some Name') self.assertEqual(None, mtv.clean()) def test_custom_validate_method_is_called(self): mtv = ModelToValidate(number=11) self.assertRaises(ValidationError, mtv.clean) + diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py index 7335723978..4706c210a7 100644 --- a/tests/regressiontests/model_fields/tests.py +++ b/tests/regressiontests/model_fields/tests.py @@ -133,8 +133,12 @@ class SlugFieldTests(django.test.TestCase): self.assertEqual(bs.s, 'slug'*50) class ValidationTest(django.test.TestCase): - def test_charfield_cleans_empty_string(self): + 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): @@ -153,8 +157,12 @@ class ValidationTest(django.test.TestCase): f = models.CharField(choices=[('a','A'), ('b','B')]) self.assertRaises(ValidationError, f.clean, "not a", None) - def test_nullable_integerfield_cleans_none(self): - f = models.IntegerField(null=True) + 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):