import datetime import os from decimal import Decimal from unittest import mock, skipUnless from django import forms from django.core.exceptions import ( NON_FIELD_ERRORS, FieldError, ImproperlyConfigured, ValidationError, ) from django.core.files.uploadedfile import SimpleUploadedFile from django.db import connection, models from django.db.models.query import EmptyQuerySet from django.forms.models import ( ModelFormMetaclass, construct_instance, fields_for_model, model_to_dict, modelform_factory, ) from django.template import Context, Template from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from .models import ( Article, ArticleStatus, Author, Author1, Award, BetterWriter, BigInt, Book, Category, Character, Colour, ColourfulItem, CustomErrorMessage, CustomFF, CustomFieldForExclusionModel, DateTimePost, DerivedBook, DerivedPost, Document, ExplicitPK, FilePathModel, FlexibleDatePost, Homepage, ImprovedArticle, ImprovedArticleWithParentLink, Inventory, NullableUniqueCharFieldModel, Person, Photo, Post, Price, Product, Publication, PublicationDefaults, StrictAssignmentAll, StrictAssignmentFieldSpecific, Student, StumpJoke, TextFile, Triple, Writer, WriterProfile, test_images, ) if test_images: from .models import ImageFile, NoExtensionImageFile, OptionalImageFile class ImageFileForm(forms.ModelForm): class Meta: model = ImageFile fields = '__all__' class OptionalImageFileForm(forms.ModelForm): class Meta: model = OptionalImageFile fields = '__all__' class NoExtensionImageFileForm(forms.ModelForm): class Meta: model = NoExtensionImageFile fields = '__all__' class ProductForm(forms.ModelForm): class Meta: model = Product fields = '__all__' class PriceForm(forms.ModelForm): class Meta: model = Price fields = '__all__' class BookForm(forms.ModelForm): class Meta: model = Book fields = '__all__' class DerivedBookForm(forms.ModelForm): class Meta: model = DerivedBook fields = '__all__' class ExplicitPKForm(forms.ModelForm): class Meta: model = ExplicitPK fields = ('key', 'desc',) class PostForm(forms.ModelForm): class Meta: model = Post fields = '__all__' class DerivedPostForm(forms.ModelForm): class Meta: model = DerivedPost fields = '__all__' class CustomWriterForm(forms.ModelForm): name = forms.CharField(required=False) class Meta: model = Writer fields = '__all__' class BaseCategoryForm(forms.ModelForm): class Meta: model = Category fields = '__all__' class ArticleForm(forms.ModelForm): class Meta: model = Article fields = '__all__' class RoykoForm(forms.ModelForm): class Meta: model = Writer fields = '__all__' class ArticleStatusForm(forms.ModelForm): class Meta: model = ArticleStatus fields = '__all__' class InventoryForm(forms.ModelForm): class Meta: model = Inventory fields = '__all__' class SelectInventoryForm(forms.Form): items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') class CustomFieldForExclusionForm(forms.ModelForm): class Meta: model = CustomFieldForExclusionModel fields = ['name', 'markup'] class TextFileForm(forms.ModelForm): class Meta: model = TextFile fields = '__all__' class BigIntForm(forms.ModelForm): class Meta: model = BigInt fields = '__all__' class ModelFormWithMedia(forms.ModelForm): class Media: js = ('/some/form/javascript',) css = { 'all': ('/some/form/css',) } class Meta: model = TextFile fields = '__all__' class CustomErrorMessageForm(forms.ModelForm): name1 = forms.CharField(error_messages={'invalid': 'Form custom error message.'}) class Meta: fields = '__all__' model = CustomErrorMessage class ModelFormBaseTest(TestCase): def test_base_form(self): self.assertEqual(list(BaseCategoryForm.base_fields), ['name', 'slug', 'url']) def test_no_model_class(self): class NoModelModelForm(forms.ModelForm): pass with self.assertRaisesMessage(ValueError, 'ModelForm has no model class specified.'): NoModelModelForm() def test_empty_fields_to_fields_for_model(self): """ An argument of fields=() to fields_for_model should return an empty dictionary """ field_dict = fields_for_model(Person, fields=()) self.assertEqual(len(field_dict), 0) def test_empty_fields_on_modelform(self): """ No fields on a ModelForm should actually result in no fields. """ class EmptyPersonForm(forms.ModelForm): class Meta: model = Person fields = () form = EmptyPersonForm() self.assertEqual(len(form.fields), 0) def test_empty_fields_to_construct_instance(self): """ No fields should be set on a model instance if construct_instance receives fields=(). """ form = modelform_factory(Person, fields="__all__")({'name': 'John Doe'}) self.assertTrue(form.is_valid()) instance = construct_instance(form, Person(), fields=()) self.assertEqual(instance.name, '') def test_blank_with_null_foreign_key_field(self): """ #13776 -- ModelForm's with models having a FK set to null=False and required=False should be valid. """ class FormForTestingIsValid(forms.ModelForm): class Meta: model = Student fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['character'].required = False char = Character.objects.create(username='user', last_action=datetime.datetime.today()) data = {'study': 'Engineering'} data2 = {'study': 'Engineering', 'character': char.pk} # form is valid because required=False for field 'character' f1 = FormForTestingIsValid(data) self.assertTrue(f1.is_valid()) f2 = FormForTestingIsValid(data2) self.assertTrue(f2.is_valid()) obj = f2.save() self.assertEqual(obj.character, char) def test_blank_false_with_null_true_foreign_key_field(self): """ A ModelForm with a model having ForeignKey(blank=False, null=True) and the form field set to required=False should allow the field to be unset. """ class AwardForm(forms.ModelForm): class Meta: model = Award fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['character'].required = False character = Character.objects.create(username='user', last_action=datetime.datetime.today()) award = Award.objects.create(name='Best sprinter', character=character) data = {'name': 'Best tester', 'character': ''} # remove character form = AwardForm(data=data, instance=award) self.assertTrue(form.is_valid()) award = form.save() self.assertIsNone(award.character) def test_blank_foreign_key_with_radio(self): class BookForm(forms.ModelForm): class Meta: model = Book fields = ['author'] widgets = {'author': forms.RadioSelect()} writer = Writer.objects.create(name='Joe Doe') form = BookForm() self.assertEqual(list(form.fields['author'].choices), [ ('', '---------'), (writer.pk, 'Joe Doe'), ]) def test_non_blank_foreign_key_with_radio(self): class AwardForm(forms.ModelForm): class Meta: model = Award fields = ['character'] widgets = {'character': forms.RadioSelect()} character = Character.objects.create( username='user', last_action=datetime.datetime.today(), ) form = AwardForm() self.assertEqual( list(form.fields['character'].choices), [(character.pk, 'user')], ) def test_save_blank_false_with_required_false(self): """ A ModelForm with a model with a field set to blank=False and the form field set to required=False should allow the field to be unset. """ obj = Writer.objects.create(name='test') form = CustomWriterForm(data={'name': ''}, instance=obj) self.assertTrue(form.is_valid()) obj = form.save() self.assertEqual(obj.name, '') def test_save_blank_null_unique_charfield_saves_null(self): form_class = modelform_factory(model=NullableUniqueCharFieldModel, fields=['codename']) empty_value = '' if connection.features.interprets_empty_strings_as_nulls else None form = form_class(data={'codename': ''}) self.assertTrue(form.is_valid()) form.save() self.assertEqual(form.instance.codename, empty_value) # Save a second form to verify there isn't a unique constraint violation. form = form_class(data={'codename': ''}) self.assertTrue(form.is_valid()) form.save() self.assertEqual(form.instance.codename, empty_value) def test_missing_fields_attribute(self): message = ( "Creating a ModelForm without either the 'fields' attribute " "or the 'exclude' attribute is prohibited; form " "MissingFieldsForm needs updating." ) with self.assertRaisesMessage(ImproperlyConfigured, message): class MissingFieldsForm(forms.ModelForm): class Meta: model = Category def test_extra_fields(self): class ExtraFields(BaseCategoryForm): some_extra_field = forms.BooleanField() self.assertEqual(list(ExtraFields.base_fields), ['name', 'slug', 'url', 'some_extra_field']) def test_extra_field_model_form(self): with self.assertRaisesMessage(FieldError, 'no-field'): class ExtraPersonForm(forms.ModelForm): """ ModelForm with an extra field """ age = forms.IntegerField() class Meta: model = Person fields = ('name', 'no-field') def test_extra_declared_field_model_form(self): class ExtraPersonForm(forms.ModelForm): """ ModelForm with an extra field """ age = forms.IntegerField() class Meta: model = Person fields = ('name', 'age') def test_extra_field_modelform_factory(self): with self.assertRaisesMessage(FieldError, 'Unknown field(s) (no-field) specified for Person'): modelform_factory(Person, fields=['no-field', 'name']) def test_replace_field(self): class ReplaceField(forms.ModelForm): url = forms.BooleanField() class Meta: model = Category fields = '__all__' self.assertIsInstance(ReplaceField.base_fields['url'], forms.fields.BooleanField) def test_replace_field_variant_2(self): # Should have the same result as before, # but 'fields' attribute specified differently class ReplaceField(forms.ModelForm): url = forms.BooleanField() class Meta: model = Category fields = ['url'] self.assertIsInstance(ReplaceField.base_fields['url'], forms.fields.BooleanField) def test_replace_field_variant_3(self): # Should have the same result as before, # but 'fields' attribute specified differently class ReplaceField(forms.ModelForm): url = forms.BooleanField() class Meta: model = Category fields = [] # url will still appear, since it is explicit above self.assertIsInstance(ReplaceField.base_fields['url'], forms.fields.BooleanField) def test_override_field(self): class WriterForm(forms.ModelForm): book = forms.CharField(required=False) class Meta: model = Writer fields = '__all__' wf = WriterForm({'name': 'Richard Lockridge'}) self.assertTrue(wf.is_valid()) def test_limit_nonexistent_field(self): expected_msg = 'Unknown field(s) (nonexistent) specified for Category' with self.assertRaisesMessage(FieldError, expected_msg): class InvalidCategoryForm(forms.ModelForm): class Meta: model = Category fields = ['nonexistent'] def test_limit_fields_with_string(self): expected_msg = "CategoryForm.Meta.fields cannot be a string. Did you mean to type: ('url',)?" with self.assertRaisesMessage(TypeError, expected_msg): class CategoryForm(forms.ModelForm): class Meta: model = Category fields = ('url') # note the missing comma def test_exclude_fields(self): class ExcludeFields(forms.ModelForm): class Meta: model = Category exclude = ['url'] self.assertEqual(list(ExcludeFields.base_fields), ['name', 'slug']) def test_exclude_nonexistent_field(self): class ExcludeFields(forms.ModelForm): class Meta: model = Category exclude = ['nonexistent'] self.assertEqual(list(ExcludeFields.base_fields), ['name', 'slug', 'url']) def test_exclude_fields_with_string(self): expected_msg = "CategoryForm.Meta.exclude cannot be a string. Did you mean to type: ('url',)?" with self.assertRaisesMessage(TypeError, expected_msg): class CategoryForm(forms.ModelForm): class Meta: model = Category exclude = ('url') # note the missing comma def test_exclude_and_validation(self): # This Price instance generated by this form is not valid because the quantity # field is required, but the form is valid because the field is excluded from # the form. This is for backwards compatibility. class PriceFormWithoutQuantity(forms.ModelForm): class Meta: model = Price exclude = ('quantity',) form = PriceFormWithoutQuantity({'price': '6.00'}) self.assertTrue(form.is_valid()) price = form.save(commit=False) msg = "{'quantity': ['This field cannot be null.']}" with self.assertRaisesMessage(ValidationError, msg): price.full_clean() # The form should not validate fields that it doesn't contain even if they are # specified using 'fields', not 'exclude'. class PriceFormWithoutQuantity(forms.ModelForm): class Meta: model = Price fields = ('price',) form = PriceFormWithoutQuantity({'price': '6.00'}) self.assertTrue(form.is_valid()) # The form should still have an instance of a model that is not complete and # not saved into a DB yet. self.assertEqual(form.instance.price, Decimal('6.00')) self.assertIsNone(form.instance.quantity) self.assertIsNone(form.instance.pk) def test_confused_form(self): class ConfusedForm(forms.ModelForm): """ Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh, "be liberal in what you accept" and all. """ class Meta: model = Category fields = ['name', 'url'] exclude = ['url'] self.assertEqual(list(ConfusedForm.base_fields), ['name']) def test_mixmodel_form(self): class MixModelForm(BaseCategoryForm): """ Don't allow more than one 'model' definition in the inheritance hierarchy. Technically, it would generate a valid form, but the fact that the resulting save method won't deal with multiple objects is likely to trip up people not familiar with the mechanics. """ class Meta: model = Article fields = '__all__' # MixModelForm is now an Article-related thing, because MixModelForm.Meta # overrides BaseCategoryForm.Meta. self.assertEqual( list(MixModelForm.base_fields), ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] ) def test_article_form(self): self.assertEqual( list(ArticleForm.base_fields), ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] ) def test_bad_form(self): # First class with a Meta class wins... class BadForm(ArticleForm, BaseCategoryForm): pass self.assertEqual( list(BadForm.base_fields), ['headline', 'slug', 'pub_date', 'writer', 'article', 'categories', 'status'] ) def test_invalid_meta_model(self): class InvalidModelForm(forms.ModelForm): class Meta: pass # no model # Can't create new form msg = 'ModelForm has no model class specified.' with self.assertRaisesMessage(ValueError, msg): InvalidModelForm() # Even if you provide a model instance with self.assertRaisesMessage(ValueError, msg): InvalidModelForm(instance=Category) def test_subcategory_form(self): class SubCategoryForm(BaseCategoryForm): """ Subclassing without specifying a Meta on the class will use the parent's Meta (or the first parent in the MRO if there are multiple parent classes). """ pass self.assertEqual(list(SubCategoryForm.base_fields), ['name', 'slug', 'url']) def test_subclassmeta_form(self): class SomeCategoryForm(forms.ModelForm): checkbox = forms.BooleanField() class Meta: model = Category fields = '__all__' class SubclassMeta(SomeCategoryForm): """ We can also subclass the Meta inner class to change the fields list. """ class Meta(SomeCategoryForm.Meta): exclude = ['url'] self.assertHTMLEqual( str(SubclassMeta()), """ """ ) def test_orderfields_form(self): class OrderFields(forms.ModelForm): class Meta: model = Category fields = ['url', 'name'] self.assertEqual(list(OrderFields.base_fields), ['url', 'name']) self.assertHTMLEqual( str(OrderFields()), """ """ ) def test_orderfields2_form(self): class OrderFields2(forms.ModelForm): class Meta: model = Category fields = ['slug', 'url', 'name'] exclude = ['url'] self.assertEqual(list(OrderFields2.base_fields), ['slug', 'name']) def test_default_populated_on_optional_field(self): class PubForm(forms.ModelForm): mode = forms.CharField(max_length=255, required=False) class Meta: model = PublicationDefaults fields = ('mode',) # Empty data uses the model field default. mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.mode, 'di') self.assertEqual(m1._meta.get_field('mode').get_default(), 'di') # Blank data doesn't use the model field default. mf2 = PubForm({'mode': ''}) self.assertEqual(mf2.errors, {}) m2 = mf2.save(commit=False) self.assertEqual(m2.mode, '') def test_default_not_populated_on_non_empty_value_in_cleaned_data(self): class PubForm(forms.ModelForm): mode = forms.CharField(max_length=255, required=False) mocked_mode = None def clean(self): self.cleaned_data['mode'] = self.mocked_mode return self.cleaned_data class Meta: model = PublicationDefaults fields = ('mode',) pub_form = PubForm({}) pub_form.mocked_mode = 'de' pub = pub_form.save(commit=False) self.assertEqual(pub.mode, 'de') # Default should be populated on an empty value in cleaned_data. default_mode = 'di' for empty_value in pub_form.fields['mode'].empty_values: with self.subTest(empty_value=empty_value): pub_form = PubForm({}) pub_form.mocked_mode = empty_value pub = pub_form.save(commit=False) self.assertEqual(pub.mode, default_mode) def test_default_not_populated_on_optional_checkbox_input(self): class PubForm(forms.ModelForm): class Meta: model = PublicationDefaults fields = ('active',) # Empty data doesn't use the model default because CheckboxInput # doesn't have a value in HTML form submission. mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertIs(m1.active, False) self.assertIsInstance(mf1.fields['active'].widget, forms.CheckboxInput) self.assertIs(m1._meta.get_field('active').get_default(), True) def test_default_not_populated_on_checkboxselectmultiple(self): class PubForm(forms.ModelForm): mode = forms.CharField(required=False, widget=forms.CheckboxSelectMultiple) class Meta: model = PublicationDefaults fields = ('mode',) # Empty data doesn't use the model default because an unchecked # CheckboxSelectMultiple doesn't have a value in HTML form submission. mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.mode, '') self.assertEqual(m1._meta.get_field('mode').get_default(), 'di') def test_default_not_populated_on_selectmultiple(self): class PubForm(forms.ModelForm): mode = forms.CharField(required=False, widget=forms.SelectMultiple) class Meta: model = PublicationDefaults fields = ('mode',) # Empty data doesn't use the model default because an unselected # SelectMultiple doesn't have a value in HTML form submission. mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.mode, '') self.assertEqual(m1._meta.get_field('mode').get_default(), 'di') def test_prefixed_form_with_default_field(self): class PubForm(forms.ModelForm): prefix = 'form-prefix' class Meta: model = PublicationDefaults fields = ('mode',) mode = 'de' self.assertNotEqual(mode, PublicationDefaults._meta.get_field('mode').get_default()) mf1 = PubForm({'form-prefix-mode': mode}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.mode, mode) def test_renderer_kwarg(self): custom = object() self.assertIs(ProductForm(renderer=custom).renderer, custom) def test_default_splitdatetime_field(self): class PubForm(forms.ModelForm): datetime_published = forms.SplitDateTimeField(required=False) class Meta: model = PublicationDefaults fields = ('datetime_published',) mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.datetime_published, datetime.datetime(2000, 1, 1)) mf2 = PubForm({'datetime_published_0': '2010-01-01', 'datetime_published_1': '0:00:00'}) self.assertEqual(mf2.errors, {}) m2 = mf2.save(commit=False) self.assertEqual(m2.datetime_published, datetime.datetime(2010, 1, 1)) def test_default_filefield(self): class PubForm(forms.ModelForm): class Meta: model = PublicationDefaults fields = ('file',) mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.file.name, 'default.txt') mf2 = PubForm({}, {'file': SimpleUploadedFile('name', b'foo')}) self.assertEqual(mf2.errors, {}) m2 = mf2.save(commit=False) self.assertEqual(m2.file.name, 'name') def test_default_selectdatewidget(self): class PubForm(forms.ModelForm): date_published = forms.DateField(required=False, widget=forms.SelectDateWidget) class Meta: model = PublicationDefaults fields = ('date_published',) mf1 = PubForm({}) self.assertEqual(mf1.errors, {}) m1 = mf1.save(commit=False) self.assertEqual(m1.date_published, datetime.date.today()) mf2 = PubForm({'date_published_year': '2010', 'date_published_month': '1', 'date_published_day': '1'}) self.assertEqual(mf2.errors, {}) m2 = mf2.save(commit=False) self.assertEqual(m2.date_published, datetime.date(2010, 1, 1)) class FieldOverridesByFormMetaForm(forms.ModelForm): class Meta: model = Category fields = ['name', 'url', 'slug'] widgets = { 'name': forms.Textarea, 'url': forms.TextInput(attrs={'class': 'url'}) } labels = { 'name': 'Title', } help_texts = { 'slug': 'Watch out! Letters, numbers, underscores and hyphens only.', } error_messages = { 'slug': { 'invalid': ( "Didn't you read the help text? " "We said letters, numbers, underscores and hyphens only!" ) } } field_classes = { 'url': forms.URLField, } class TestFieldOverridesByFormMeta(SimpleTestCase): def test_widget_overrides(self): form = FieldOverridesByFormMetaForm() self.assertHTMLEqual( str(form['name']), '', ) self.assertHTMLEqual( str(form['url']), '', ) self.assertHTMLEqual( str(form['slug']), '', ) def test_label_overrides(self): form = FieldOverridesByFormMetaForm() self.assertHTMLEqual( str(form['name'].label_tag()), '', ) self.assertHTMLEqual( str(form['url'].label_tag()), '', ) self.assertHTMLEqual( str(form['slug'].label_tag()), '', ) def test_help_text_overrides(self): form = FieldOverridesByFormMetaForm() self.assertEqual( form['slug'].help_text, 'Watch out! Letters, numbers, underscores and hyphens only.', ) def test_error_messages_overrides(self): form = FieldOverridesByFormMetaForm(data={ 'name': 'Category', 'url': 'http://www.example.com/category/', 'slug': '!%#*@', }) form.full_clean() error = [ "Didn't you read the help text? " "We said letters, numbers, underscores and hyphens only!", ] self.assertEqual(form.errors, {'slug': error}) def test_field_type_overrides(self): form = FieldOverridesByFormMetaForm() self.assertIs(Category._meta.get_field('url').__class__, models.CharField) self.assertIsInstance(form.fields['url'], forms.URLField) class IncompleteCategoryFormWithFields(forms.ModelForm): """ A form that replaces the model's url field with a custom one. This should prevent the model field's validation from being called. """ url = forms.CharField(required=False) class Meta: fields = ('name', 'slug') model = Category class IncompleteCategoryFormWithExclude(forms.ModelForm): """ A form that replaces the model's url field with a custom one. This should prevent the model field's validation from being called. """ url = forms.CharField(required=False) class Meta: exclude = ['url'] model = Category class ValidationTest(SimpleTestCase): def test_validates_with_replaced_field_not_specified(self): form = IncompleteCategoryFormWithFields(data={'name': 'some name', 'slug': 'some-slug'}) self.assertIs(form.is_valid(), True) def test_validates_with_replaced_field_excluded(self): form = IncompleteCategoryFormWithExclude(data={'name': 'some name', 'slug': 'some-slug'}) self.assertIs(form.is_valid(), True) def test_notrequired_overrides_notblank(self): form = CustomWriterForm({}) self.assertIs(form.is_valid(), True) class UniqueTest(TestCase): """ unique/unique_together validation. """ @classmethod def setUpTestData(cls): cls.writer = Writer.objects.create(name='Mike Royko') def test_simple_unique(self): form = ProductForm({'slug': 'teddy-bear-blue'}) self.assertTrue(form.is_valid()) obj = form.save() form = ProductForm({'slug': 'teddy-bear-blue'}) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['slug'], ['Product with this Slug already exists.']) form = ProductForm({'slug': 'teddy-bear-blue'}, instance=obj) self.assertTrue(form.is_valid()) def test_unique_together(self): """ModelForm test of unique_together constraint""" form = PriceForm({'price': '6.00', 'quantity': '1'}) self.assertTrue(form.is_valid()) form.save() form = PriceForm({'price': '6.00', 'quantity': '1'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['__all__'], ['Price with this Price and Quantity already exists.']) def test_unique_together_exclusion(self): """ Forms don't validate unique_together constraints when only part of the constraint is included in the form's fields. This allows using form.save(commit=False) and then assigning the missing field(s) to the model instance. """ class BookForm(forms.ModelForm): class Meta: model = DerivedBook fields = ('isbn', 'suffix1') # The unique_together is on suffix1/suffix2 but only suffix1 is part # of the form. The fields must have defaults, otherwise they'll be # skipped by other logic. self.assertEqual(DerivedBook._meta.unique_together, (('suffix1', 'suffix2'),)) for name in ('suffix1', 'suffix2'): with self.subTest(name=name): field = DerivedBook._meta.get_field(name) self.assertEqual(field.default, 0) # The form fails validation with "Derived book with this Suffix1 and # Suffix2 already exists." if the unique_together validation isn't # skipped. DerivedBook.objects.create(isbn='12345') form = BookForm({'isbn': '56789', 'suffix1': '0'}) self.assertTrue(form.is_valid(), form.errors) def test_multiple_field_unique_together(self): """ When the same field is involved in multiple unique_together constraints, we need to make sure we don't remove the data for it before doing all the validation checking (not just failing after the first one). """ class TripleForm(forms.ModelForm): class Meta: model = Triple fields = '__all__' Triple.objects.create(left=1, middle=2, right=3) form = TripleForm({'left': '1', 'middle': '2', 'right': '3'}) self.assertFalse(form.is_valid()) form = TripleForm({'left': '1', 'middle': '3', 'right': '1'}) self.assertTrue(form.is_valid()) @skipUnlessDBFeature('supports_nullable_unique_constraints') def test_unique_null(self): title = 'I May Be Wrong But I Doubt It' form = BookForm({'title': title, 'author': self.writer.pk}) self.assertTrue(form.is_valid()) form.save() form = BookForm({'title': title, 'author': self.writer.pk}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.']) form = BookForm({'title': title}) self.assertTrue(form.is_valid()) form.save() form = BookForm({'title': title}) self.assertTrue(form.is_valid()) def test_inherited_unique(self): title = 'Boss' Book.objects.create(title=title, author=self.writer, special_id=1) form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'special_id': '1', 'isbn': '12345'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['special_id'], ['Book with this Special id already exists.']) def test_inherited_unique_together(self): title = 'Boss' form = BookForm({'title': title, 'author': self.writer.pk}) self.assertTrue(form.is_valid()) form.save() form = DerivedBookForm({'title': title, 'author': self.writer.pk, 'isbn': '12345'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['__all__'], ['Book with this Title and Author already exists.']) def test_abstract_inherited_unique(self): title = 'Boss' isbn = '12345' DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn) form = DerivedBookForm({ 'title': 'Other', 'author': self.writer.pk, 'isbn': isbn, 'suffix1': '1', 'suffix2': '2', }) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['isbn'], ['Derived book with this Isbn already exists.']) def test_abstract_inherited_unique_together(self): title = 'Boss' isbn = '12345' DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn) form = DerivedBookForm({ 'title': 'Other', 'author': self.writer.pk, 'isbn': '9876', 'suffix1': '0', 'suffix2': '0' }) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual( form.errors['__all__'], ['Derived book with this Suffix1 and Suffix2 already exists.'], ) def test_explicitpk_unspecified(self): """Test for primary_key being in the form and failing validation.""" form = ExplicitPKForm({'key': '', 'desc': ''}) self.assertFalse(form.is_valid()) def test_explicitpk_unique(self): """Ensure keys and blank character strings are tested for uniqueness.""" form = ExplicitPKForm({'key': 'key1', 'desc': ''}) self.assertTrue(form.is_valid()) form.save() form = ExplicitPKForm({'key': 'key1', 'desc': ''}) self.assertFalse(form.is_valid()) if connection.features.interprets_empty_strings_as_nulls: self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['key'], ['Explicit pk with this Key already exists.']) else: self.assertEqual(len(form.errors), 3) self.assertEqual(form.errors['__all__'], ['Explicit pk with this Key and Desc already exists.']) self.assertEqual(form.errors['desc'], ['Explicit pk with this Desc already exists.']) self.assertEqual(form.errors['key'], ['Explicit pk with this Key already exists.']) def test_unique_for_date(self): p = Post.objects.create( title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3), ) form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['title'], ['Title must be unique for Posted date.']) form = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'}) self.assertTrue(form.is_valid()) form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'}) self.assertTrue(form.is_valid()) form = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['slug'], ['Slug must be unique for Posted year.']) form = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'}) self.assertFalse(form.is_valid()) self.assertEqual(form.errors['subtitle'], ['Subtitle must be unique for Posted month.']) data = {'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'} form = PostForm(data, instance=p) self.assertTrue(form.is_valid()) form = PostForm({'title': "Django 1.0 is released"}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['posted'], ['This field is required.']) def test_unique_for_date_in_exclude(self): """ If the date for unique_for_* constraints is excluded from the ModelForm (in this case 'posted' has editable=False, then the constraint should be ignored. """ class DateTimePostForm(forms.ModelForm): class Meta: model = DateTimePost fields = '__all__' DateTimePost.objects.create( title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.datetime(2008, 9, 3, 10, 10, 1), ) # 'title' has unique_for_date='posted' form = DateTimePostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) self.assertTrue(form.is_valid()) # 'slug' has unique_for_year='posted' form = DateTimePostForm({'slug': "Django 1.0", 'posted': '2008-01-01'}) self.assertTrue(form.is_valid()) # 'subtitle' has unique_for_month='posted' form = DateTimePostForm({'subtitle': "Finally", 'posted': '2008-09-30'}) self.assertTrue(form.is_valid()) def test_inherited_unique_for_date(self): p = Post.objects.create( title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3), ) form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['title'], ['Title must be unique for Posted date.']) form = DerivedPostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'}) self.assertTrue(form.is_valid()) form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'}) self.assertTrue(form.is_valid()) form = DerivedPostForm({'slug': "Django 1.0", 'posted': '2008-01-01'}) self.assertFalse(form.is_valid()) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['slug'], ['Slug must be unique for Posted year.']) form = DerivedPostForm({'subtitle': "Finally", 'posted': '2008-09-30'}) self.assertFalse(form.is_valid()) self.assertEqual(form.errors['subtitle'], ['Subtitle must be unique for Posted month.']) data = {'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'} form = DerivedPostForm(data, instance=p) self.assertTrue(form.is_valid()) def test_unique_for_date_with_nullable_date(self): class FlexDatePostForm(forms.ModelForm): class Meta: model = FlexibleDatePost fields = '__all__' p = FlexibleDatePost.objects.create( title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3), ) form = FlexDatePostForm({'title': "Django 1.0 is released"}) self.assertTrue(form.is_valid()) form = FlexDatePostForm({'slug': "Django 1.0"}) self.assertTrue(form.is_valid()) form = FlexDatePostForm({'subtitle': "Finally"}) self.assertTrue(form.is_valid()) data = {'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0"} form = FlexDatePostForm(data, instance=p) self.assertTrue(form.is_valid()) def test_override_unique_message(self): class CustomProductForm(ProductForm): class Meta(ProductForm.Meta): error_messages = { 'slug': { 'unique': "%(model_name)s's %(field_label)s not unique.", } } Product.objects.create(slug='teddy-bear-blue') form = CustomProductForm({'slug': 'teddy-bear-blue'}) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['slug'], ["Product's Slug not unique."]) def test_override_unique_together_message(self): class CustomPriceForm(PriceForm): class Meta(PriceForm.Meta): error_messages = { NON_FIELD_ERRORS: { 'unique_together': "%(model_name)s's %(field_labels)s not unique.", } } Price.objects.create(price=6.00, quantity=1) form = CustomPriceForm({'price': '6.00', 'quantity': '1'}) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors[NON_FIELD_ERRORS], ["Price's Price and Quantity not unique."]) def test_override_unique_for_date_message(self): class CustomPostForm(PostForm): class Meta(PostForm.Meta): error_messages = { 'title': { 'unique_for_date': ( "%(model_name)s's %(field_label)s not unique " "for %(date_field_label)s date." ), } } Post.objects.create( title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3), ) form = CustomPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) self.assertEqual(len(form.errors), 1) self.assertEqual(form.errors['title'], ["Post's Title not unique for Posted date."]) class ModelFormBasicTests(TestCase): def create_basic_data(self): self.c1 = Category.objects.create(name='Entertainment', slug='entertainment', url='entertainment') self.c2 = Category.objects.create(name="It's a test", slug='its-test', url='test') self.c3 = Category.objects.create(name='Third test', slug='third-test', url='third') self.w_royko = Writer.objects.create(name='Mike Royko') self.w_woodward = Writer.objects.create(name='Bob Woodward') def test_base_form(self): self.assertEqual(Category.objects.count(), 0) f = BaseCategoryForm() self.assertHTMLEqual( str(f), """ """ ) self.assertHTMLEqual( str(f.as_ul()), """
  • """ ) self.assertHTMLEqual( str(f["name"]), """""") def test_auto_id(self): f = BaseCategoryForm(auto_id=False) self.assertHTMLEqual( str(f.as_ul()), """
  • Name:
  • Slug:
  • The URL:
  • """ ) def test_initial_values(self): self.create_basic_data() # Initial values can be provided for model forms f = ArticleForm( auto_id=False, initial={ 'headline': 'Your headline here', 'categories': [str(self.c1.id), str(self.c2.id)] }) self.assertHTMLEqual( f.as_ul(), '''
  • Headline:
  • Slug:
  • Pub date:
  • Writer:
  • Article:
  • Categories:
  • Status:
  • ''' % (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk)) # When the ModelForm is passed an instance, that instance's current values are # inserted as 'initial' data in each Field. f = RoykoForm(auto_id=False, instance=self.w_royko) self.assertHTMLEqual( str(f), '''Name:
    Use both first and last names.''' ) art = Article.objects.create( headline='Test article', slug='test-article', pub_date=datetime.date(1988, 1, 4), writer=self.w_royko, article='Hello.' ) art_id_1 = art.id f = ArticleForm(auto_id=False, instance=art) self.assertHTMLEqual( f.as_ul(), '''
  • Headline:
  • Slug:
  • Pub date:
  • Writer:
  • Article:
  • Categories:
  • Status:
  • ''' % (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk)) f = ArticleForm({ 'headline': 'Test headline', 'slug': 'test-headline', 'pub_date': '1984-02-06', 'writer': str(self.w_royko.pk), 'article': 'Hello.' }, instance=art) self.assertEqual(f.errors, {}) self.assertTrue(f.is_valid()) test_art = f.save() self.assertEqual(test_art.id, art_id_1) test_art = Article.objects.get(id=art_id_1) self.assertEqual(test_art.headline, 'Test headline') def test_m2m_initial_callable(self): """ Regression for #10349: A callable can be provided as the initial value for an m2m field """ self.maxDiff = 1200 self.create_basic_data() # Set up a callable initial value def formfield_for_dbfield(db_field, **kwargs): if db_field.name == 'categories': kwargs['initial'] = lambda: Category.objects.all().order_by('name')[:2] return db_field.formfield(**kwargs) # Create a ModelForm, instantiate it, and check that the output is as expected ModelForm = modelform_factory( Article, fields=['headline', 'categories'], formfield_callback=formfield_for_dbfield, ) form = ModelForm() self.assertHTMLEqual( form.as_ul(), """
  • """ % (self.c1.pk, self.c2.pk, self.c3.pk)) def test_basic_creation(self): self.assertEqual(Category.objects.count(), 0) f = BaseCategoryForm({ 'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment', }) self.assertTrue(f.is_valid()) self.assertEqual(f.cleaned_data['name'], 'Entertainment') self.assertEqual(f.cleaned_data['slug'], 'entertainment') self.assertEqual(f.cleaned_data['url'], 'entertainment') c1 = f.save() # Testing whether the same object is returned from the # ORM... not the fastest way... self.assertEqual(Category.objects.count(), 1) self.assertEqual(c1, Category.objects.all()[0]) self.assertEqual(c1.name, "Entertainment") def test_save_commit_false(self): # If you call save() with commit=False, then it will return an object that # hasn't yet been saved to the database. In this case, it's up to you to call # save() on the resulting model instance. f = BaseCategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) self.assertTrue(f.is_valid()) c1 = f.save(commit=False) self.assertEqual(c1.name, "Third test") self.assertEqual(Category.objects.count(), 0) c1.save() self.assertEqual(Category.objects.count(), 1) def test_save_with_data_errors(self): # If you call save() with invalid data, you'll get a ValueError. f = BaseCategoryForm({'name': '', 'slug': 'not a slug!', 'url': 'foo'}) self.assertEqual(f.errors['name'], ['This field is required.']) self.assertEqual( f.errors['slug'], ['Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.'] ) self.assertEqual(f.cleaned_data, {'url': 'foo'}) msg = "The Category could not be created because the data didn't validate." with self.assertRaisesMessage(ValueError, msg): f.save() f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'}) with self.assertRaisesMessage(ValueError, msg): f.save() def test_multi_fields(self): self.create_basic_data() self.maxDiff = None # ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any # fields with the 'choices' attribute are represented by a ChoiceField. f = ArticleForm(auto_id=False) self.assertHTMLEqual( str(f), '''Headline: Slug: Pub date: Writer: Article: Categories: Status:''' % (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk)) # Add some categories and test the many-to-many form output. new_art = Article.objects.create( article="Hello.", headline="New headline", slug="new-headline", pub_date=datetime.date(1988, 1, 4), writer=self.w_royko) new_art.categories.add(Category.objects.get(name='Entertainment')) self.assertQuerysetEqual(new_art.categories.all(), ["Entertainment"]) f = ArticleForm(auto_id=False, instance=new_art) self.assertHTMLEqual( f.as_ul(), '''
  • Headline:
  • Slug:
  • Pub date:
  • Writer:
  • Article:
  • Categories:
  • Status:
  • ''' % (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk)) def test_subset_fields(self): # You can restrict a form to a subset of the complete list of fields # by providing a 'fields' argument. If you try to save a # model created with such a form, you need to ensure that the fields # that are _not_ on the form have default values, or are allowed to have # a value of None. If a field isn't specified on a form, the object created # from the form can't provide a value for that field! class PartialArticleForm(forms.ModelForm): class Meta: model = Article fields = ('headline', 'pub_date') f = PartialArticleForm(auto_id=False) self.assertHTMLEqual( str(f), '''Headline: Pub date:''') class PartialArticleFormWithSlug(forms.ModelForm): class Meta: model = Article fields = ('headline', 'slug', 'pub_date') w_royko = Writer.objects.create(name='Mike Royko') art = Article.objects.create( article="Hello.", headline="New headline", slug="new-headline", pub_date=datetime.date(1988, 1, 4), writer=w_royko) f = PartialArticleFormWithSlug({ 'headline': 'New headline', 'slug': 'new-headline', 'pub_date': '1988-01-04' }, auto_id=False, instance=art) self.assertHTMLEqual( f.as_ul(), '''
  • Headline:
  • Slug:
  • Pub date:
  • ''' ) self.assertTrue(f.is_valid()) new_art = f.save() self.assertEqual(new_art.id, art.id) new_art = Article.objects.get(id=art.id) self.assertEqual(new_art.headline, 'New headline') def test_m2m_editing(self): self.create_basic_data() form_data = { 'headline': 'New headline', 'slug': 'new-headline', 'pub_date': '1988-01-04', 'writer': str(self.w_royko.pk), 'article': 'Hello.', 'categories': [str(self.c1.id), str(self.c2.id)] } # Create a new article, with categories, via the form. f = ArticleForm(form_data) new_art = f.save() new_art = Article.objects.get(id=new_art.id) art_id_1 = new_art.id self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"]) # Now, submit form data with no categories. This deletes the existing categories. form_data['categories'] = [] f = ArticleForm(form_data, instance=new_art) new_art = f.save() self.assertEqual(new_art.id, art_id_1) new_art = Article.objects.get(id=art_id_1) self.assertQuerysetEqual(new_art.categories.all(), []) # Create a new article, with no categories, via the form. f = ArticleForm(form_data) new_art = f.save() art_id_2 = new_art.id self.assertNotIn(art_id_2, (None, art_id_1)) new_art = Article.objects.get(id=art_id_2) self.assertQuerysetEqual(new_art.categories.all(), []) # Create a new article, with categories, via the form, but use commit=False. # The m2m data won't be saved until save_m2m() is invoked on the form. form_data['categories'] = [str(self.c1.id), str(self.c2.id)] f = ArticleForm(form_data) new_art = f.save(commit=False) # Manually save the instance new_art.save() art_id_3 = new_art.id self.assertNotIn(art_id_3, (None, art_id_1, art_id_2)) # The instance doesn't have m2m data yet new_art = Article.objects.get(id=art_id_3) self.assertQuerysetEqual(new_art.categories.all(), []) # Save the m2m data on the form f.save_m2m() self.assertQuerysetEqual(new_art.categories.order_by('name'), ["Entertainment", "It's a test"]) def test_custom_form_fields(self): # Here, we define a custom ModelForm. Because it happens to have the same fields as # the Category model, we can just call the form's save() to apply its changes to an # existing Category instance. class ShortCategory(forms.ModelForm): name = forms.CharField(max_length=5) slug = forms.CharField(max_length=5) url = forms.CharField(max_length=3) class Meta: model = Category fields = '__all__' cat = Category.objects.create(name='Third test') form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat) self.assertEqual(form.save().name, 'Third') self.assertEqual(Category.objects.get(id=cat.id).name, 'Third') def test_runtime_choicefield_populated(self): self.maxDiff = None # Here, we demonstrate that choices for a ForeignKey ChoiceField are determined # at runtime, based on the data in the database when the form is displayed, not # the data in the database when the form is instantiated. self.create_basic_data() f = ArticleForm(auto_id=False) self.assertHTMLEqual( f.as_ul(), '''
  • Headline:
  • Slug:
  • Pub date:
  • Writer:
  • Article:
  • Categories:
  • Status:
  • ''' % (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk)) c4 = Category.objects.create(name='Fourth', url='4th') w_bernstein = Writer.objects.create(name='Carl Bernstein') self.assertHTMLEqual( f.as_ul(), '''
  • Headline:
  • Slug:
  • Pub date:
  • Writer:
  • Article:
  • Categories:
  • Status:
  • ''' % (self.w_woodward.pk, w_bernstein.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk, c4.pk)) def test_recleaning_model_form_instance(self): """ Re-cleaning an instance that was added via a ModelForm shouldn't raise a pk uniqueness error. """ class AuthorForm(forms.ModelForm): class Meta: model = Author fields = '__all__' form = AuthorForm({'full_name': 'Bob'}) self.assertTrue(form.is_valid()) obj = form.save() obj.name = 'Alice' obj.full_clean() def test_validate_foreign_key_uses_default_manager(self): class MyForm(forms.ModelForm): class Meta: model = Article fields = '__all__' # Archived writers are filtered out by the default manager. w = Writer.objects.create(name='Randy', archived=True) data = { 'headline': 'My Article', 'slug': 'my-article', 'pub_date': datetime.date.today(), 'writer': w.pk, 'article': 'lorem ipsum', } form = MyForm(data) self.assertIs(form.is_valid(), False) self.assertEqual( form.errors, {'writer': ['Select a valid choice. That choice is not one of the available choices.']}, ) def test_validate_foreign_key_to_model_with_overridden_manager(self): class MyForm(forms.ModelForm): class Meta: model = Article fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Allow archived authors. self.fields['writer'].queryset = Writer._base_manager.all() w = Writer.objects.create(name='Randy', archived=True) data = { 'headline': 'My Article', 'slug': 'my-article', 'pub_date': datetime.date.today(), 'writer': w.pk, 'article': 'lorem ipsum', } form = MyForm(data) self.assertIs(form.is_valid(), True) article = form.save() self.assertEqual(article.writer, w) class ModelMultipleChoiceFieldTests(TestCase): @classmethod def setUpTestData(cls): cls.c1 = Category.objects.create(name='Entertainment', slug='entertainment', url='entertainment') cls.c2 = Category.objects.create(name="It's a test", slug='its-test', url='test') cls.c3 = Category.objects.create(name='Third', slug='third-test', url='third') def test_model_multiple_choice_field(self): f = forms.ModelMultipleChoiceField(Category.objects.all()) self.assertEqual(list(f.choices), [ (self.c1.pk, 'Entertainment'), (self.c2.pk, "It's a test"), (self.c3.pk, 'Third')]) with self.assertRaises(ValidationError): f.clean(None) with self.assertRaises(ValidationError): f.clean([]) self.assertQuerysetEqual(f.clean([self.c1.id]), ["Entertainment"]) self.assertQuerysetEqual(f.clean([self.c2.id]), ["It's a test"]) self.assertQuerysetEqual(f.clean([str(self.c1.id)]), ["Entertainment"]) self.assertQuerysetEqual( f.clean([str(self.c1.id), str(self.c2.id)]), ["Entertainment", "It's a test"], ordered=False ) self.assertQuerysetEqual( f.clean([self.c1.id, str(self.c2.id)]), ["Entertainment", "It's a test"], ordered=False ) self.assertQuerysetEqual( f.clean((self.c1.id, str(self.c2.id))), ["Entertainment", "It's a test"], ordered=False ) with self.assertRaises(ValidationError): f.clean(['100']) with self.assertRaises(ValidationError): f.clean('hello') with self.assertRaises(ValidationError): f.clean(['fail']) # Invalid types that require TypeError to be caught (#22808). with self.assertRaises(ValidationError): f.clean([['fail']]) with self.assertRaises(ValidationError): f.clean([{'foo': 'bar'}]) # Add a Category object *after* the ModelMultipleChoiceField has already been # instantiated. This proves clean() checks the database during clean() rather # than caching it at time of instantiation. # Note, we are using an id of 1006 here since tests that run before # this may create categories with primary keys up to 6. Use # a number that will not conflict. c6 = Category.objects.create(id=1006, name='Sixth', url='6th') self.assertQuerysetEqual(f.clean([c6.id]), ["Sixth"]) # Delete a Category object *after* the ModelMultipleChoiceField has already been # instantiated. This proves clean() checks the database during clean() rather # than caching it at time of instantiation. Category.objects.get(url='6th').delete() with self.assertRaises(ValidationError): f.clean([c6.id]) def test_model_multiple_choice_required_false(self): f = forms.ModelMultipleChoiceField(Category.objects.all(), required=False) self.assertIsInstance(f.clean([]), EmptyQuerySet) self.assertIsInstance(f.clean(()), EmptyQuerySet) with self.assertRaises(ValidationError): f.clean(['0']) with self.assertRaises(ValidationError): f.clean([str(self.c3.id), '0']) with self.assertRaises(ValidationError): f.clean([str(self.c1.id), '0']) # queryset can be changed after the field is created. f.queryset = Category.objects.exclude(name='Third') self.assertEqual(list(f.choices), [ (self.c1.pk, 'Entertainment'), (self.c2.pk, "It's a test")]) self.assertQuerysetEqual(f.clean([self.c2.id]), ["It's a test"]) with self.assertRaises(ValidationError): f.clean([self.c3.id]) with self.assertRaises(ValidationError): f.clean([str(self.c2.id), str(self.c3.id)]) f.queryset = Category.objects.all() f.label_from_instance = lambda obj: "multicategory " + str(obj) self.assertEqual(list(f.choices), [ (self.c1.pk, 'multicategory Entertainment'), (self.c2.pk, "multicategory It's a test"), (self.c3.pk, 'multicategory Third')]) def test_model_multiple_choice_number_of_queries(self): """ ModelMultipleChoiceField does O(1) queries instead of O(n) (#10156). """ persons = [Writer.objects.create(name="Person %s" % i) for i in range(30)] f = forms.ModelMultipleChoiceField(queryset=Writer.objects.all()) self.assertNumQueries(1, f.clean, [p.pk for p in persons[1:11:2]]) def test_model_multiple_choice_run_validators(self): """ ModelMultipleChoiceField run given validators (#14144). """ for i in range(30): Writer.objects.create(name="Person %s" % i) self._validator_run = False def my_validator(value): self._validator_run = True f = forms.ModelMultipleChoiceField(queryset=Writer.objects.all(), validators=[my_validator]) f.clean([p.pk for p in Writer.objects.all()[8:9]]) self.assertTrue(self._validator_run) def test_model_multiple_choice_show_hidden_initial(self): """ Test support of show_hidden_initial by ModelMultipleChoiceField. """ class WriterForm(forms.Form): persons = forms.ModelMultipleChoiceField(show_hidden_initial=True, queryset=Writer.objects.all()) person1 = Writer.objects.create(name="Person 1") person2 = Writer.objects.create(name="Person 2") form = WriterForm( initial={'persons': [person1, person2]}, data={ 'initial-persons': [str(person1.pk), str(person2.pk)], 'persons': [str(person1.pk), str(person2.pk)], }, ) self.assertTrue(form.is_valid()) self.assertFalse(form.has_changed()) form = WriterForm( initial={'persons': [person1, person2]}, data={ 'initial-persons': [str(person1.pk), str(person2.pk)], 'persons': [str(person2.pk)], }, ) self.assertTrue(form.is_valid()) self.assertTrue(form.has_changed()) def test_model_multiple_choice_field_22745(self): """ #22745 -- Make sure that ModelMultipleChoiceField with CheckboxSelectMultiple widget doesn't produce unnecessary db queries when accessing its BoundField's attrs. """ class ModelMultipleChoiceForm(forms.Form): categories = forms.ModelMultipleChoiceField(Category.objects.all(), widget=forms.CheckboxSelectMultiple) form = ModelMultipleChoiceForm() field = form['categories'] # BoundField template = Template('{{ field.name }}{{ field }}{{ field.help_text }}') with self.assertNumQueries(1): template.render(Context({'field': field})) def test_show_hidden_initial_changed_queries_efficiently(self): class WriterForm(forms.Form): persons = forms.ModelMultipleChoiceField( show_hidden_initial=True, queryset=Writer.objects.all()) writers = (Writer.objects.create(name=str(x)) for x in range(0, 50)) writer_pks = tuple(x.pk for x in writers) form = WriterForm(data={'initial-persons': writer_pks}) with self.assertNumQueries(1): self.assertTrue(form.has_changed()) def test_clean_does_deduplicate_values(self): class PersonForm(forms.Form): persons = forms.ModelMultipleChoiceField(queryset=Person.objects.all()) person1 = Person.objects.create(name='Person 1') form = PersonForm(data={}) queryset = form.fields['persons'].clean([str(person1.pk)] * 50) sql, params = queryset.query.sql_with_params() self.assertEqual(len(params), 1) def test_to_field_name_with_initial_data(self): class ArticleCategoriesForm(forms.ModelForm): categories = forms.ModelMultipleChoiceField(Category.objects.all(), to_field_name='slug') class Meta: model = Article fields = ['categories'] article = Article.objects.create( headline='Test article', slug='test-article', pub_date=datetime.date(1988, 1, 4), writer=Writer.objects.create(name='Test writer'), article='Hello.', ) article.categories.add(self.c2, self.c3) form = ArticleCategoriesForm(instance=article) self.assertCountEqual(form['categories'].value(), [self.c2.slug, self.c3.slug]) class ModelOneToOneFieldTests(TestCase): def test_modelform_onetoonefield(self): class ImprovedArticleForm(forms.ModelForm): class Meta: model = ImprovedArticle fields = '__all__' class ImprovedArticleWithParentLinkForm(forms.ModelForm): class Meta: model = ImprovedArticleWithParentLink fields = '__all__' self.assertEqual(list(ImprovedArticleForm.base_fields), ['article']) self.assertEqual(list(ImprovedArticleWithParentLinkForm.base_fields), []) def test_modelform_subclassed_model(self): class BetterWriterForm(forms.ModelForm): class Meta: # BetterWriter model is a subclass of Writer with an additional `score` field model = BetterWriter fields = '__all__' bw = BetterWriter.objects.create(name='Joe Better', score=10) self.assertEqual(sorted(model_to_dict(bw)), ['id', 'name', 'score', 'writer_ptr']) self.assertEqual(sorted(model_to_dict(bw, fields=[])), []) self.assertEqual(sorted(model_to_dict(bw, fields=['id', 'name'])), ['id', 'name']) self.assertEqual(sorted(model_to_dict(bw, exclude=[])), ['id', 'name', 'score', 'writer_ptr']) self.assertEqual(sorted(model_to_dict(bw, exclude=['id', 'name'])), ['score', 'writer_ptr']) form = BetterWriterForm({'name': 'Some Name', 'score': 12}) self.assertTrue(form.is_valid()) bw2 = form.save() self.assertEqual(bw2.score, 12) def test_onetoonefield(self): class WriterProfileForm(forms.ModelForm): class Meta: # WriterProfile has a OneToOneField to Writer model = WriterProfile fields = '__all__' self.w_royko = Writer.objects.create(name='Mike Royko') self.w_woodward = Writer.objects.create(name='Bob Woodward') form = WriterProfileForm() self.assertHTMLEqual( form.as_p(), '''

    ''' % ( self.w_woodward.pk, self.w_royko.pk, ) ) data = { 'writer': str(self.w_woodward.pk), 'age': '65', } form = WriterProfileForm(data) instance = form.save() self.assertEqual(str(instance), 'Bob Woodward is 65') form = WriterProfileForm(instance=instance) self.assertHTMLEqual( form.as_p(), '''

    ''' % ( self.w_woodward.pk, self.w_royko.pk, ) ) def test_assignment_of_none(self): class AuthorForm(forms.ModelForm): class Meta: model = Author fields = ['publication', 'full_name'] publication = Publication.objects.create(title="Pravda", date_published=datetime.date(1991, 8, 22)) author = Author.objects.create(publication=publication, full_name='John Doe') form = AuthorForm({'publication': '', 'full_name': 'John Doe'}, instance=author) self.assertTrue(form.is_valid()) self.assertIsNone(form.cleaned_data['publication']) author = form.save() # author object returned from form still retains original publication object # that's why we need to retrieve it from database again new_author = Author.objects.get(pk=author.pk) self.assertIsNone(new_author.publication) def test_assignment_of_none_null_false(self): class AuthorForm(forms.ModelForm): class Meta: model = Author1 fields = ['publication', 'full_name'] publication = Publication.objects.create(title="Pravda", date_published=datetime.date(1991, 8, 22)) author = Author1.objects.create(publication=publication, full_name='John Doe') form = AuthorForm({'publication': '', 'full_name': 'John Doe'}, instance=author) self.assertFalse(form.is_valid()) class FileAndImageFieldTests(TestCase): def test_clean_false(self): """ If the ``clean`` method on a non-required FileField receives False as the data (meaning clear the field value), it returns False, regardless of the value of ``initial``. """ f = forms.FileField(required=False) self.assertIs(f.clean(False), False) self.assertIs(f.clean(False, 'initial'), False) def test_clean_false_required(self): """ If the ``clean`` method on a required FileField receives False as the data, it has the same effect as None: initial is returned if non-empty, otherwise the validation catches the lack of a required value. """ f = forms.FileField(required=True) self.assertEqual(f.clean(False, 'initial'), 'initial') with self.assertRaises(ValidationError): f.clean(False) def test_full_clear(self): """ Integration happy-path test that a model FileField can actually be set and cleared via a ModelForm. """ class DocumentForm(forms.ModelForm): class Meta: model = Document fields = '__all__' form = DocumentForm() self.assertIn('name="myfile"', str(form)) self.assertNotIn('myfile-clear', str(form)) form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', b'content')}) self.assertTrue(form.is_valid()) doc = form.save(commit=False) self.assertEqual(doc.myfile.name, 'something.txt') form = DocumentForm(instance=doc) self.assertIn('myfile-clear', str(form)) form = DocumentForm(instance=doc, data={'myfile-clear': 'true'}) doc = form.save(commit=False) self.assertFalse(doc.myfile) def test_clear_and_file_contradiction(self): """ If the user submits a new file upload AND checks the clear checkbox, they get a validation error, and the bound redisplay of the form still includes the current file and the clear checkbox. """ class DocumentForm(forms.ModelForm): class Meta: model = Document fields = '__all__' form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', b'content')}) self.assertTrue(form.is_valid()) doc = form.save(commit=False) form = DocumentForm( instance=doc, files={'myfile': SimpleUploadedFile('something.txt', b'content')}, data={'myfile-clear': 'true'}, ) self.assertTrue(not form.is_valid()) self.assertEqual(form.errors['myfile'], ['Please either submit a file or check the clear checkbox, not both.']) rendered = str(form) self.assertIn('something.txt', rendered) self.assertIn('myfile-clear', rendered) def test_render_empty_file_field(self): class DocumentForm(forms.ModelForm): class Meta: model = Document fields = '__all__' doc = Document.objects.create() form = DocumentForm(instance=doc) self.assertHTMLEqual( str(form['myfile']), '' ) def test_file_field_data(self): # Test conditions when files is either not given or empty. f = TextFileForm(data={'description': 'Assistance'}) self.assertFalse(f.is_valid()) f = TextFileForm(data={'description': 'Assistance'}, files={}) self.assertFalse(f.is_valid()) # Upload a file and ensure it all works as expected. f = TextFileForm( data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', b'hello world')}, ) self.assertTrue(f.is_valid()) self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile) instance = f.save() self.assertEqual(instance.file.name, 'tests/test1.txt') instance.file.delete() # If the previous file has been deleted, the file name can be reused f = TextFileForm( data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test1.txt', b'hello world')}, ) self.assertTrue(f.is_valid()) self.assertEqual(type(f.cleaned_data['file']), SimpleUploadedFile) instance = f.save() self.assertEqual(instance.file.name, 'tests/test1.txt') # Check if the max_length attribute has been inherited from the model. f = TextFileForm( data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test-maxlength.txt', b'hello world')}, ) self.assertFalse(f.is_valid()) # Edit an instance that already has the file defined in the model. This will not # save the file again, but leave it exactly as it is. f = TextFileForm({'description': 'Assistance'}, instance=instance) self.assertTrue(f.is_valid()) self.assertEqual(f.cleaned_data['file'].name, 'tests/test1.txt') instance = f.save() self.assertEqual(instance.file.name, 'tests/test1.txt') # Delete the current file since this is not done by Django. instance.file.delete() # Override the file by uploading a new one. f = TextFileForm( data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test2.txt', b'hello world')}, instance=instance, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.file.name, 'tests/test2.txt') # Delete the current file since this is not done by Django. instance.file.delete() instance.delete() def test_filefield_required_false(self): # Test the non-required FileField f = TextFileForm(data={'description': 'Assistance'}) f.fields['file'].required = False self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.file.name, '') f = TextFileForm( data={'description': 'Assistance'}, files={'file': SimpleUploadedFile('test3.txt', b'hello world')}, instance=instance, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.file.name, 'tests/test3.txt') # Instance can be edited w/out re-uploading the file and existing file should be preserved. f = TextFileForm({'description': 'New Description'}, instance=instance) f.fields['file'].required = False self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.description, 'New Description') self.assertEqual(instance.file.name, 'tests/test3.txt') # Delete the current file since this is not done by Django. instance.file.delete() instance.delete() def test_custom_file_field_save(self): """ Regression for #11149: save_form_data should be called only once """ class CFFForm(forms.ModelForm): class Meta: model = CustomFF fields = '__all__' # It's enough that the form saves without error -- the custom save routine will # generate an AssertionError if it is called more than once during save. form = CFFForm(data={'f': None}) form.save() def test_file_field_multiple_save(self): """ Simulate a file upload and check how many times Model.save() gets called. Test for bug #639. """ class PhotoForm(forms.ModelForm): class Meta: model = Photo fields = '__all__' # Grab an image for testing. filename = os.path.join(os.path.dirname(__file__), 'test.png') with open(filename, "rb") as fp: img = fp.read() # Fake a POST QueryDict and FILES MultiValueDict. data = {'title': 'Testing'} files = {"image": SimpleUploadedFile('test.png', img, 'image/png')} form = PhotoForm(data=data, files=files) p = form.save() try: # Check the savecount stored on the object (see the model). self.assertEqual(p._savecount, 1) finally: # Delete the "uploaded" file to avoid clogging /tmp. p = Photo.objects.get() p.image.delete(save=False) def test_file_path_field_blank(self): """FilePathField(blank=True) includes the empty option.""" class FPForm(forms.ModelForm): class Meta: model = FilePathModel fields = '__all__' form = FPForm() self.assertEqual([name for _, name in form['path'].field.choices], ['---------', 'models.py']) @skipUnless(test_images, "Pillow not installed") def test_image_field(self): # ImageField and FileField are nearly identical, but they differ slightly when # it comes to validation. This specifically tests that #6302 is fixed for # both file fields and image fields. with open(os.path.join(os.path.dirname(__file__), 'test.png'), 'rb') as fp: image_data = fp.read() with open(os.path.join(os.path.dirname(__file__), 'test2.png'), 'rb') as fp: image_data2 = fp.read() f = ImageFileForm( data={'description': 'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}, ) self.assertTrue(f.is_valid()) self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile) instance = f.save() self.assertEqual(instance.image.name, 'tests/test.png') self.assertEqual(instance.width, 16) self.assertEqual(instance.height, 16) # Delete the current file since this is not done by Django, but don't save # because the dimension fields are not null=True. instance.image.delete(save=False) f = ImageFileForm( data={'description': 'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}, ) self.assertTrue(f.is_valid()) self.assertEqual(type(f.cleaned_data['image']), SimpleUploadedFile) instance = f.save() self.assertEqual(instance.image.name, 'tests/test.png') self.assertEqual(instance.width, 16) self.assertEqual(instance.height, 16) # Edit an instance that already has the (required) image defined in the model. This will not # save the image again, but leave it exactly as it is. f = ImageFileForm(data={'description': 'Look, it changed'}, instance=instance) self.assertTrue(f.is_valid()) self.assertEqual(f.cleaned_data['image'].name, 'tests/test.png') instance = f.save() self.assertEqual(instance.image.name, 'tests/test.png') self.assertEqual(instance.height, 16) self.assertEqual(instance.width, 16) # Delete the current file since this is not done by Django, but don't save # because the dimension fields are not null=True. instance.image.delete(save=False) # Override the file by uploading a new one. f = ImageFileForm( data={'description': 'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}, instance=instance, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, 'tests/test2.png') self.assertEqual(instance.height, 32) self.assertEqual(instance.width, 48) # Delete the current file since this is not done by Django, but don't save # because the dimension fields are not null=True. instance.image.delete(save=False) instance.delete() f = ImageFileForm( data={'description': 'Changed it'}, files={'image': SimpleUploadedFile('test2.png', image_data2)}, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, 'tests/test2.png') self.assertEqual(instance.height, 32) self.assertEqual(instance.width, 48) # Delete the current file since this is not done by Django, but don't save # because the dimension fields are not null=True. instance.image.delete(save=False) instance.delete() # Test the non-required ImageField # Note: In Oracle, we expect a null ImageField to return '' instead of # None. if connection.features.interprets_empty_strings_as_nulls: expected_null_imagefield_repr = '' else: expected_null_imagefield_repr = None f = OptionalImageFileForm(data={'description': 'Test'}) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, expected_null_imagefield_repr) self.assertIsNone(instance.width) self.assertIsNone(instance.height) f = OptionalImageFileForm( data={'description': 'And a final one'}, files={'image': SimpleUploadedFile('test3.png', image_data)}, instance=instance, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, 'tests/test3.png') self.assertEqual(instance.width, 16) self.assertEqual(instance.height, 16) # Editing the instance without re-uploading the image should not affect # the image or its width/height properties. f = OptionalImageFileForm({'description': 'New Description'}, instance=instance) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.description, 'New Description') self.assertEqual(instance.image.name, 'tests/test3.png') self.assertEqual(instance.width, 16) self.assertEqual(instance.height, 16) # Delete the current file since this is not done by Django. instance.image.delete() instance.delete() f = OptionalImageFileForm( data={'description': 'And a final one'}, files={'image': SimpleUploadedFile('test4.png', image_data2)} ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, 'tests/test4.png') self.assertEqual(instance.width, 48) self.assertEqual(instance.height, 32) instance.delete() # Test callable upload_to behavior that's dependent on the value of another field in the model f = ImageFileForm( data={'description': 'And a final one', 'path': 'foo'}, files={'image': SimpleUploadedFile('test4.png', image_data)}, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, 'foo/test4.png') instance.delete() # Editing an instance that has an image without an extension shouldn't # fail validation. First create: f = NoExtensionImageFileForm( data={'description': 'An image'}, files={'image': SimpleUploadedFile('test.png', image_data)}, ) self.assertTrue(f.is_valid()) instance = f.save() self.assertEqual(instance.image.name, 'tests/no_extension') # Then edit: f = NoExtensionImageFileForm(data={'description': 'Edited image'}, instance=instance) self.assertTrue(f.is_valid()) class ModelOtherFieldTests(SimpleTestCase): def test_big_integer_field(self): bif = BigIntForm({'biggie': '-9223372036854775808'}) self.assertTrue(bif.is_valid()) bif = BigIntForm({'biggie': '-9223372036854775809'}) self.assertFalse(bif.is_valid()) self.assertEqual( bif.errors, {'biggie': ['Ensure this value is greater than or equal to -9223372036854775808.']} ) bif = BigIntForm({'biggie': '9223372036854775807'}) self.assertTrue(bif.is_valid()) bif = BigIntForm({'biggie': '9223372036854775808'}) self.assertFalse(bif.is_valid()) self.assertEqual(bif.errors, {'biggie': ['Ensure this value is less than or equal to 9223372036854775807.']}) def test_url_on_modelform(self): "Check basic URL field validation on model forms" class HomepageForm(forms.ModelForm): class Meta: model = Homepage fields = '__all__' self.assertFalse(HomepageForm({'url': 'foo'}).is_valid()) self.assertFalse(HomepageForm({'url': 'http://'}).is_valid()) self.assertFalse(HomepageForm({'url': 'http://example'}).is_valid()) self.assertFalse(HomepageForm({'url': 'http://example.'}).is_valid()) self.assertFalse(HomepageForm({'url': 'http://com.'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://localhost'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://example.com'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://www.example.com'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://www.example.com/test'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000/test'}).is_valid()) self.assertTrue(HomepageForm({'url': 'http://example.com/foo/bar'}).is_valid()) def test_modelform_non_editable_field(self): """ When explicitly including a non-editable field in a ModelForm, the error message should be explicit. """ # 'created', non-editable, is excluded by default self.assertNotIn('created', ArticleForm().fields) msg = "'created' cannot be specified for Article model form as it is a non-editable field" with self.assertRaisesMessage(FieldError, msg): class InvalidArticleForm(forms.ModelForm): class Meta: model = Article fields = ('headline', 'created') def test_http_prefixing(self): """ If the http:// prefix is omitted on form input, the field adds it again. (Refs #13613) """ class HomepageForm(forms.ModelForm): class Meta: model = Homepage fields = '__all__' form = HomepageForm({'url': 'example.com'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['url'], 'http://example.com') form = HomepageForm({'url': 'example.com/test'}) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['url'], 'http://example.com/test') class OtherModelFormTests(TestCase): def test_media_on_modelform(self): # Similar to a regular Form class you can define custom media to be used on # the ModelForm. f = ModelFormWithMedia() self.assertHTMLEqual( str(f.media), ''' ''' ) def test_choices_type(self): # Choices on CharField and IntegerField f = ArticleForm() with self.assertRaises(ValidationError): f.fields['status'].clean('42') f = ArticleStatusForm() with self.assertRaises(ValidationError): f.fields['status'].clean('z') def test_prefetch_related_queryset(self): """ ModelChoiceField should respect a prefetch_related() on its queryset. """ blue = Colour.objects.create(name='blue') red = Colour.objects.create(name='red') multicolor_item = ColourfulItem.objects.create() multicolor_item.colours.add(blue, red) red_item = ColourfulItem.objects.create() red_item.colours.add(red) class ColorModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): return ', '.join(c.name for c in obj.colours.all()) field = ColorModelChoiceField(ColourfulItem.objects.prefetch_related('colours')) with self.assertNumQueries(3): # would be 4 if prefetch is ignored self.assertEqual(tuple(field.choices), ( ('', '---------'), (multicolor_item.pk, 'blue, red'), (red_item.pk, 'red'), )) def test_foreignkeys_which_use_to_field(self): apple = Inventory.objects.create(barcode=86, name='Apple') Inventory.objects.create(barcode=22, name='Pear') core = Inventory.objects.create(barcode=87, name='Core', parent=apple) field = forms.ModelChoiceField(Inventory.objects.all(), to_field_name='barcode') self.assertEqual(tuple(field.choices), ( ('', '---------'), (86, 'Apple'), (87, 'Core'), (22, 'Pear'))) form = InventoryForm(instance=core) self.assertHTMLEqual(str(form['parent']), '''''') data = model_to_dict(core) data['parent'] = '22' form = InventoryForm(data=data, instance=core) core = form.save() self.assertEqual(core.parent.name, 'Pear') class CategoryForm(forms.ModelForm): description = forms.CharField() class Meta: model = Category fields = ['description', 'url'] self.assertEqual(list(CategoryForm.base_fields), ['description', 'url']) self.assertHTMLEqual( str(CategoryForm()), ''' ''' ) # to_field_name should also work on ModelMultipleChoiceField ################## field = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode') self.assertEqual(tuple(field.choices), ((86, 'Apple'), (87, 'Core'), (22, 'Pear'))) self.assertQuerysetEqual(field.clean([86]), ['Apple']) form = SelectInventoryForm({'items': [87, 22]}) self.assertTrue(form.is_valid()) self.assertEqual(len(form.cleaned_data), 1) self.assertQuerysetEqual(form.cleaned_data['items'], ['Core', 'Pear']) def test_model_field_that_returns_none_to_exclude_itself_with_explicit_fields(self): self.assertEqual(list(CustomFieldForExclusionForm.base_fields), ['name']) self.assertHTMLEqual( str(CustomFieldForExclusionForm()), ''' ''' ) def test_iterable_model_m2m(self): class ColourfulItemForm(forms.ModelForm): class Meta: model = ColourfulItem fields = '__all__' colour = Colour.objects.create(name='Blue') form = ColourfulItemForm() self.maxDiff = 1024 self.assertHTMLEqual( form.as_p(), """

    """ % {'blue_pk': colour.pk}) def test_callable_field_default(self): class PublicationDefaultsForm(forms.ModelForm): class Meta: model = PublicationDefaults fields = ('title', 'date_published', 'mode', 'category') self.maxDiff = 2000 form = PublicationDefaultsForm() today_str = str(datetime.date.today()) self.assertHTMLEqual( form.as_p(), """

    """.format(today_str) ) empty_data = { 'title': '', 'date_published': today_str, 'initial-date_published': today_str, 'mode': 'di', 'initial-mode': 'di', 'category': '3', 'initial-category': '3', } bound_form = PublicationDefaultsForm(empty_data) self.assertFalse(bound_form.has_changed()) class ModelFormCustomErrorTests(SimpleTestCase): def test_custom_error_messages(self): data = {'name1': '@#$!!**@#$', 'name2': '@#$!!**@#$'} errors = CustomErrorMessageForm(data).errors self.assertHTMLEqual( str(errors['name1']), '

    ' ) self.assertHTMLEqual( str(errors['name2']), '' ) def test_model_clean_error_messages(self): data = {'name1': 'FORBIDDEN_VALUE', 'name2': 'ABC'} form = CustomErrorMessageForm(data) self.assertFalse(form.is_valid()) self.assertHTMLEqual( str(form.errors['name1']), '' ) data = {'name1': 'FORBIDDEN_VALUE2', 'name2': 'ABC'} form = CustomErrorMessageForm(data) self.assertFalse(form.is_valid()) self.assertHTMLEqual( str(form.errors['name1']), '' ) data = {'name1': 'GLOBAL_ERROR', 'name2': 'ABC'} form = CustomErrorMessageForm(data) self.assertFalse(form.is_valid()) self.assertEqual(form.errors['__all__'], ['Global error message.']) class CustomCleanTests(TestCase): def test_override_clean(self): """ Regression for #12596: Calling super from ModelForm.clean() should be optional. """ class TripleFormWithCleanOverride(forms.ModelForm): class Meta: model = Triple fields = '__all__' def clean(self): if not self.cleaned_data['left'] == self.cleaned_data['right']: raise ValidationError('Left and right should be equal') return self.cleaned_data form = TripleFormWithCleanOverride({'left': 1, 'middle': 2, 'right': 1}) self.assertTrue(form.is_valid()) # form.instance.left will be None if the instance was not constructed # by form.full_clean(). self.assertEqual(form.instance.left, 1) def test_model_form_clean_applies_to_model(self): """ Regression test for #12960. Make sure the cleaned_data returned from ModelForm.clean() is applied to the model instance. """ class CategoryForm(forms.ModelForm): class Meta: model = Category fields = '__all__' def clean(self): self.cleaned_data['name'] = self.cleaned_data['name'].upper() return self.cleaned_data data = {'name': 'Test', 'slug': 'test', 'url': '/test'} form = CategoryForm(data) category = form.save() self.assertEqual(category.name, 'TEST') class ModelFormInheritanceTests(SimpleTestCase): def test_form_subclass_inheritance(self): class Form(forms.Form): age = forms.IntegerField() class ModelForm(forms.ModelForm, Form): class Meta: model = Writer fields = '__all__' self.assertEqual(list(ModelForm().fields), ['name', 'age']) def test_field_removal(self): class ModelForm(forms.ModelForm): class Meta: model = Writer fields = '__all__' class Mixin: age = None class Form(forms.Form): age = forms.IntegerField() class Form2(forms.Form): foo = forms.IntegerField() self.assertEqual(list(ModelForm().fields), ['name']) self.assertEqual(list(type('NewForm', (Mixin, Form), {})().fields), []) self.assertEqual(list(type('NewForm', (Form2, Mixin, Form), {})().fields), ['foo']) self.assertEqual(list(type('NewForm', (Mixin, ModelForm, Form), {})().fields), ['name']) self.assertEqual(list(type('NewForm', (ModelForm, Mixin, Form), {})().fields), ['name']) self.assertEqual(list(type('NewForm', (ModelForm, Form, Mixin), {})().fields), ['name', 'age']) self.assertEqual(list(type('NewForm', (ModelForm, Form), {'age': None})().fields), ['name']) def test_field_removal_name_clashes(self): """ Form fields can be removed in subclasses by setting them to None (#22510). """ class MyForm(forms.ModelForm): media = forms.CharField() class Meta: model = Writer fields = '__all__' class SubForm(MyForm): media = None self.assertIn('media', MyForm().fields) self.assertNotIn('media', SubForm().fields) self.assertTrue(hasattr(MyForm, 'media')) self.assertTrue(hasattr(SubForm, 'media')) class StumpJokeForm(forms.ModelForm): class Meta: model = StumpJoke fields = '__all__' class CustomFieldWithQuerysetButNoLimitChoicesTo(forms.Field): queryset = 42 class StumpJokeWithCustomFieldForm(forms.ModelForm): custom = CustomFieldWithQuerysetButNoLimitChoicesTo() class Meta: model = StumpJoke fields = () class LimitChoicesToTests(TestCase): """ Tests the functionality of ``limit_choices_to``. """ @classmethod def setUpTestData(cls): cls.threepwood = Character.objects.create( username='threepwood', last_action=datetime.datetime.today() + datetime.timedelta(days=1), ) cls.marley = Character.objects.create( username='marley', last_action=datetime.datetime.today() - datetime.timedelta(days=1), ) def test_limit_choices_to_callable_for_fk_rel(self): """ A ForeignKey can use limit_choices_to as a callable (#2554). """ stumpjokeform = StumpJokeForm() self.assertSequenceEqual(stumpjokeform.fields['most_recently_fooled'].queryset, [self.threepwood]) def test_limit_choices_to_callable_for_m2m_rel(self): """ A ManyToManyField can use limit_choices_to as a callable (#2554). """ stumpjokeform = StumpJokeForm() self.assertSequenceEqual(stumpjokeform.fields['most_recently_fooled'].queryset, [self.threepwood]) def test_custom_field_with_queryset_but_no_limit_choices_to(self): """ A custom field with a `queryset` attribute but no `limit_choices_to` works (#23795). """ f = StumpJokeWithCustomFieldForm() self.assertEqual(f.fields['custom'].queryset, 42) def test_fields_for_model_applies_limit_choices_to(self): fields = fields_for_model(StumpJoke, ['has_fooled_today']) self.assertSequenceEqual(fields['has_fooled_today'].queryset, [self.threepwood]) def test_callable_called_each_time_form_is_instantiated(self): field = StumpJokeForm.base_fields['most_recently_fooled'] with mock.patch.object(field, 'limit_choices_to') as today_callable_dict: StumpJokeForm() self.assertEqual(today_callable_dict.call_count, 1) StumpJokeForm() self.assertEqual(today_callable_dict.call_count, 2) StumpJokeForm() self.assertEqual(today_callable_dict.call_count, 3) class FormFieldCallbackTests(SimpleTestCase): def test_baseform_with_widgets_in_meta(self): """Regression for #13095: Using base forms with widgets defined in Meta should not raise errors.""" widget = forms.Textarea() class BaseForm(forms.ModelForm): class Meta: model = Person widgets = {'name': widget} fields = "__all__" Form = modelform_factory(Person, form=BaseForm) self.assertIsInstance(Form.base_fields['name'].widget, forms.Textarea) def test_factory_with_widget_argument(self): """ Regression for #15315: modelform_factory should accept widgets argument """ widget = forms.Textarea() # Without a widget should not set the widget to textarea Form = modelform_factory(Person, fields="__all__") self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea) # With a widget should not set the widget to textarea Form = modelform_factory(Person, fields="__all__", widgets={'name': widget}) self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea) def test_modelform_factory_without_fields(self): """ Regression for #19733 """ message = ( "Calling modelform_factory without defining 'fields' or 'exclude' " "explicitly is prohibited." ) with self.assertRaisesMessage(ImproperlyConfigured, message): modelform_factory(Person) def test_modelform_factory_with_all_fields(self): """ Regression for #19733 """ form = modelform_factory(Person, fields="__all__") self.assertEqual(list(form.base_fields), ["name"]) def test_custom_callback(self): """A custom formfield_callback is used if provided""" callback_args = [] def callback(db_field, **kwargs): callback_args.append((db_field, kwargs)) return db_field.formfield(**kwargs) widget = forms.Textarea() class BaseForm(forms.ModelForm): class Meta: model = Person widgets = {'name': widget} fields = "__all__" modelform_factory(Person, form=BaseForm, formfield_callback=callback) id_field, name_field = Person._meta.fields self.assertEqual(callback_args, [(id_field, {}), (name_field, {'widget': widget})]) def test_bad_callback(self): # A bad callback provided by user still gives an error with self.assertRaises(TypeError): modelform_factory(Person, fields="__all__", formfield_callback='not a function or callable') def test_inherit_after_custom_callback(self): def callback(db_field, **kwargs): if isinstance(db_field, models.CharField): return forms.CharField(widget=forms.Textarea) return db_field.formfield(**kwargs) class BaseForm(forms.ModelForm): class Meta: model = Person fields = '__all__' NewForm = modelform_factory(Person, form=BaseForm, formfield_callback=callback) class InheritedForm(NewForm): pass for name in NewForm.base_fields: self.assertEqual( type(InheritedForm.base_fields[name].widget), type(NewForm.base_fields[name].widget) ) class LocalizedModelFormTest(TestCase): def test_model_form_applies_localize_to_some_fields(self): class PartiallyLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = ('left', 'right',) fields = '__all__' f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10}) self.assertTrue(f.is_valid()) self.assertTrue(f.fields['left'].localize) self.assertFalse(f.fields['middle'].localize) self.assertTrue(f.fields['right'].localize) def test_model_form_applies_localize_to_all_fields(self): class FullyLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = '__all__' fields = '__all__' f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10}) self.assertTrue(f.is_valid()) self.assertTrue(f.fields['left'].localize) self.assertTrue(f.fields['middle'].localize) self.assertTrue(f.fields['right'].localize) def test_model_form_refuses_arbitrary_string(self): msg = ( "BrokenLocalizedTripleForm.Meta.localized_fields " "cannot be a string. Did you mean to type: ('foo',)?" ) with self.assertRaisesMessage(TypeError, msg): class BrokenLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = "foo" class CustomMetaclass(ModelFormMetaclass): def __new__(cls, name, bases, attrs): new = super().__new__(cls, name, bases, attrs) new.base_fields = {} return new class CustomMetaclassForm(forms.ModelForm, metaclass=CustomMetaclass): pass class CustomMetaclassTestCase(SimpleTestCase): def test_modelform_factory_metaclass(self): new_cls = modelform_factory(Person, fields="__all__", form=CustomMetaclassForm) self.assertEqual(new_cls.base_fields, {}) class StrictAssignmentTests(SimpleTestCase): """ Should a model do anything special with __setattr__() or descriptors which raise a ValidationError, a model form should catch the error (#24706). """ def test_setattr_raises_validation_error_field_specific(self): """ A model ValidationError using the dict form should put the error message into the correct key of form.errors. """ form_class = modelform_factory(model=StrictAssignmentFieldSpecific, fields=['title']) form = form_class(data={'title': 'testing setattr'}, files=None) # This line turns on the ValidationError; it avoids the model erroring # when its own __init__() is called when creating form.instance. form.instance._should_error = True self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { 'title': ['Cannot set attribute', 'This field cannot be blank.'] }) def test_setattr_raises_validation_error_non_field(self): """ A model ValidationError not using the dict form should put the error message into __all__ (i.e. non-field errors) on the form. """ form_class = modelform_factory(model=StrictAssignmentAll, fields=['title']) form = form_class(data={'title': 'testing setattr'}, files=None) # This line turns on the ValidationError; it avoids the model erroring # when its own __init__() is called when creating form.instance. form.instance._should_error = True self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { '__all__': ['Cannot set attribute'], 'title': ['This field cannot be blank.'] }) class ModelToDictTests(TestCase): def test_many_to_many(self): """Data for a ManyToManyField is a list rather than a lazy QuerySet.""" blue = Colour.objects.create(name='blue') red = Colour.objects.create(name='red') item = ColourfulItem.objects.create() item.colours.set([blue]) data = model_to_dict(item)['colours'] self.assertEqual(data, [blue]) item.colours.set([red]) # If data were a QuerySet, it would be reevaluated here and give "red" # instead of the original value. self.assertEqual(data, [blue])