123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- from __future__ import absolute_import, unicode_literals
- from datetime import date
- import warnings
- from django import forms
- from django.core.exceptions import FieldError, ValidationError
- from django.core.files.uploadedfile import SimpleUploadedFile
- from django.forms.models import (modelform_factory, ModelChoiceField,
- fields_for_model, construct_instance, ModelFormMetaclass)
- from django.utils import six
- from django.utils import unittest
- from django.test import TestCase
- from .models import (Person, RealPerson, Triple, FilePathModel, Article,
- Publication, CustomFF, Author, Author1, Homepage, Document, Edition)
- class ModelMultipleChoiceFieldTests(TestCase):
- def test_model_multiple_choice_number_of_queries(self):
- """
- Test that ModelMultipleChoiceField does O(1) queries instead of
- O(n) (#10156).
- """
- persons = [Person.objects.create(name="Person %s" % i) for i in range(30)]
- f = forms.ModelMultipleChoiceField(queryset=Person.objects.all())
- self.assertNumQueries(1, f.clean, [p.pk for p in persons[1:11:2]])
- def test_model_multiple_choice_run_validators(self):
- """
- Test that ModelMultipleChoiceField run given validators (#14144).
- """
- for i in range(30):
- Person.objects.create(name="Person %s" % i)
- self._validator_run = False
- def my_validator(value):
- self._validator_run = True
- f = forms.ModelMultipleChoiceField(queryset=Person.objects.all(),
- validators=[my_validator])
- f.clean([p.pk for p in Person.objects.all()[8:9]])
- self.assertTrue(self._validator_run)
- class TripleForm(forms.ModelForm):
- class Meta:
- model = Triple
- fields = '__all__'
- class UniqueTogetherTests(TestCase):
- 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).
- """
- 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())
- class TripleFormWithCleanOverride(forms.ModelForm):
- class Meta:
- model = Triple
- fields = '__all__'
- def clean(self):
- if not self.cleaned_data['left'] == self.cleaned_data['right']:
- raise forms.ValidationError('Left and right should be equal')
- return self.cleaned_data
- class OverrideCleanTests(TestCase):
- def test_override_clean(self):
- """
- Regression for #12596: Calling super from ModelForm.clean() should be
- optional.
- """
- 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)
- class PartiallyLocalizedTripleForm(forms.ModelForm):
- class Meta:
- model = Triple
- localized_fields = ('left', 'right',)
- class FullyLocalizedTripleForm(forms.ModelForm):
- class Meta:
- model = Triple
- localized_fields = "__all__"
- class LocalizedModelFormTest(TestCase):
- def test_model_form_applies_localize_to_some_fields(self):
- 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):
- 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):
- with self.assertRaises(TypeError):
- class BrokenLocalizedTripleForm(forms.ModelForm):
- class Meta:
- model = Triple
- localized_fields = "foo"
- # Regression test for #12960.
- # Make sure the cleaned_data returned from ModelForm.clean() is applied to the
- # model instance.
- class PublicationForm(forms.ModelForm):
- def clean(self):
- self.cleaned_data['title'] = self.cleaned_data['title'].upper()
- return self.cleaned_data
- class Meta:
- model = Publication
- fields = '__all__'
- class ModelFormCleanTest(TestCase):
- def test_model_form_clean_applies_to_model(self):
- data = {'title': 'test', 'date_published': '2010-2-25'}
- form = PublicationForm(data)
- publication = form.save()
- self.assertEqual(publication.title, 'TEST')
- class FPForm(forms.ModelForm):
- class Meta:
- model = FilePathModel
- fields = '__all__'
- class FilePathFieldTests(TestCase):
- def test_file_path_field_blank(self):
- """
- Regression test for #8842: FilePathField(blank=True)
- """
- form = FPForm()
- names = [p[1] for p in form['path'].field.choices]
- names.sort()
- self.assertEqual(names, ['---------', '__init__.py', 'models.py', 'tests.py'])
- class ManyToManyCallableInitialTests(TestCase):
- def test_callable(self):
- "Regression for #10349: A callable can be provided as the initial value for an m2m field"
- # Set up a callable initial value
- def formfield_for_dbfield(db_field, **kwargs):
- if db_field.name == 'publications':
- kwargs['initial'] = lambda: Publication.objects.all().order_by('date_published')[:2]
- return db_field.formfield(**kwargs)
- # Set up some Publications to use as data
- book1 = Publication.objects.create(title="First Book", date_published=date(2007,1,1))
- book2 = Publication.objects.create(title="Second Book", date_published=date(2008,1,1))
- book3 = Publication.objects.create(title="Third Book", date_published=date(2009,1,1))
- # Create a ModelForm, instantiate it, and check that the output is as expected
- ModelForm = modelform_factory(Article, fields="__all__",
- formfield_callback=formfield_for_dbfield)
- form = ModelForm()
- self.assertHTMLEqual(form.as_ul(), """<li><label for="id_headline">Headline:</label> <input id="id_headline" type="text" name="headline" maxlength="100" /></li>
- <li><label for="id_publications">Publications:</label> <select multiple="multiple" name="publications" id="id_publications">
- <option value="%d" selected="selected">First Book</option>
- <option value="%d" selected="selected">Second Book</option>
- <option value="%d">Third Book</option>
- </select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>"""
- % (book1.pk, book2.pk, book3.pk))
- class CFFForm(forms.ModelForm):
- class Meta:
- model = CustomFF
- fields = '__all__'
- class CustomFieldSaveTests(TestCase):
- def test_save(self):
- "Regression for #11149: save_form_data should be called only once"
- # 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()
- class ModelChoiceIteratorTests(TestCase):
- def test_len(self):
- class Form(forms.ModelForm):
- class Meta:
- model = Article
- fields = ["publications"]
- Publication.objects.create(title="Pravda",
- date_published=date(1991, 8, 22))
- f = Form()
- self.assertEqual(len(f.fields["publications"].choices), 1)
- class RealPersonForm(forms.ModelForm):
- class Meta:
- model = RealPerson
- fields = '__all__'
- class CustomModelFormSaveMethod(TestCase):
- def test_string_message(self):
- data = {'name': 'anonymous'}
- form = RealPersonForm(data)
- self.assertEqual(form.is_valid(), False)
- self.assertEqual(form.errors['__all__'], ['Please specify a real name.'])
- class ModelClassTests(TestCase):
- def test_no_model_class(self):
- class NoModelModelForm(forms.ModelForm):
- pass
- self.assertRaises(ValueError, NoModelModelForm)
- class OneToOneFieldTests(TestCase):
- 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=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.assertEqual(form.cleaned_data['publication'], None)
- author = form.save()
- # author object returned from form still retains original publication object
- # that's why we need to retreive it from database again
- new_author = Author.objects.get(pk=author.pk)
- self.assertEqual(new_author.publication, None)
- 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=date(1991, 8, 22))
- author = Author1.objects.create(publication=publication, full_name='John Doe')
- form = AuthorForm({'publication':'', 'full_name':'John Doe'}, instance=author)
- self.assertTrue(not form.is_valid())
- class ModelChoiceForm(forms.Form):
- person = ModelChoiceField(Person.objects.all())
- class TestTicket11183(TestCase):
- def test_11183(self):
- form1 = ModelChoiceForm()
- field1 = form1.fields['person']
- # To allow the widget to change the queryset of field1.widget.choices correctly,
- # without affecting other forms, the following must hold:
- self.assertTrue(field1 is not ModelChoiceForm.base_fields['person'])
- self.assertTrue(field1.widget.choices.field is field1)
- class HomepageForm(forms.ModelForm):
- class Meta:
- model = Homepage
- fields = '__all__'
- class URLFieldTests(TestCase):
- def test_url_on_modelform(self):
- "Check basic URL field validation on model forms"
- 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_http_prefixing(self):
- "If the http:// prefix is omitted on form input, the field adds it again. (Refs #13613)"
- form = HomepageForm({'url': 'example.com'})
- form.is_valid()
- # self.assertTrue(form.is_valid())
- # self.assertEqual(form.cleaned_data['url'], 'http://example.com/')
- form = HomepageForm({'url': 'example.com/test'})
- form.is_valid()
- # self.assertTrue(form.is_valid())
- # self.assertEqual(form.cleaned_data['url'], 'http://example.com/test')
- class FormFieldCallbackTests(TestCase):
- 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.assertTrue(Form.base_fields['name'].widget is widget)
- 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_custom_callback(self):
- """Test that 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
- self.assertRaises(TypeError, modelform_factory, Person, fields="__all__",
- formfield_callback='not a function or callable')
- class InvalidFieldAndFactory(TestCase):
- """ Tests for #11905 """
- def test_extra_field_model_form(self):
- try:
- class ExtraPersonForm(forms.ModelForm):
- """ ModelForm with an extra field """
- age = forms.IntegerField()
- class Meta:
- model = Person
- fields = ('name', 'no-field')
- except FieldError as e:
- # Make sure the exception contains some reference to the
- # field responsible for the problem.
- self.assertTrue('no-field' in e.args[0])
- else:
- self.fail('Invalid "no-field" field not caught')
- def test_extra_declared_field_model_form(self):
- try:
- class ExtraPersonForm(forms.ModelForm):
- """ ModelForm with an extra field """
- age = forms.IntegerField()
- class Meta:
- model = Person
- fields = ('name', 'age')
- except FieldError:
- self.fail('Declarative field raised FieldError incorrectly')
- def test_extra_field_modelform_factory(self):
- self.assertRaises(FieldError, modelform_factory,
- Person, fields=['no-field', 'name'])
- class DocumentForm(forms.ModelForm):
- class Meta:
- model = Document
- fields = '__all__'
- class FileFieldTests(unittest.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.assertEqual(f.clean(False), False)
- self.assertEqual(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')
- 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.
- """
- form = DocumentForm()
- self.assertTrue('name="myfile"' in six.text_type(form))
- self.assertTrue('myfile-clear' not in six.text_type(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.assertTrue('myfile-clear' in six.text_type(form))
- form = DocumentForm(instance=doc, data={'myfile-clear': 'true'})
- doc = form.save(commit=False)
- self.assertEqual(bool(doc.myfile), False)
- 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.
- """
- 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 = six.text_type(form)
- self.assertTrue('something.txt' in rendered)
- self.assertTrue('myfile-clear' in rendered)
- class EditionForm(forms.ModelForm):
- author = forms.ModelChoiceField(queryset=Person.objects.all())
- publication = forms.ModelChoiceField(queryset=Publication.objects.all())
- edition = forms.IntegerField()
- isbn = forms.CharField(max_length=13)
- class Meta:
- model = Edition
- fields = '__all__'
- class UniqueErrorsTests(TestCase):
- def setUp(self):
- self.author1 = Person.objects.create(name='Author #1')
- self.author2 = Person.objects.create(name='Author #2')
- self.pub1 = Publication.objects.create(title='Pub #1', date_published=date(2000, 10, 31))
- self.pub2 = Publication.objects.create(title='Pub #2', date_published=date(2004, 1, 5))
- form = EditionForm(data={'author': self.author1.pk, 'publication': self.pub1.pk, 'edition': 1, 'isbn': '9783161484100'})
- form.save()
- def test_unique_error_message(self):
- form = EditionForm(data={'author': self.author1.pk, 'publication': self.pub2.pk, 'edition': 1, 'isbn': '9783161484100'})
- self.assertEqual(form.errors, {'isbn': ['Edition with this Isbn already exists.']})
- def test_unique_together_error_message(self):
- form = EditionForm(data={'author': self.author1.pk, 'publication': self.pub1.pk, 'edition': 2, 'isbn': '9783161489999'})
- self.assertEqual(form.errors, {'__all__': ['Edition with this Author and Publication already exists.']})
- form = EditionForm(data={'author': self.author2.pk, 'publication': self.pub1.pk, 'edition': 1, 'isbn': '9783161487777'})
- self.assertEqual(form.errors, {'__all__': ['Edition with this Publication and Edition already exists.']})
- class EmptyFieldsTestCase(TestCase):
- "Tests for fields=() cases as reported in #14119"
- class EmptyPersonForm(forms.ModelForm):
- class Meta:
- model = Person
- fields = ()
- 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"
- form = self.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, '')
- class CustomMetaclass(ModelFormMetaclass):
- def __new__(cls, name, bases, attrs):
- new = super(CustomMetaclass, cls).__new__(cls, name, bases, attrs)
- new.base_fields = {}
- return new
- class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)):
- pass
- class CustomMetaclassTestCase(TestCase):
- def test_modelform_factory_metaclass(self):
- new_cls = modelform_factory(Person, fields="__all__", form=CustomMetaclassForm)
- self.assertEqual(new_cls.base_fields, {})
- class TestTicket19733(TestCase):
- def test_modelform_factory_without_fields(self):
- with warnings.catch_warnings(record=True) as w:
- warnings.simplefilter("always", PendingDeprecationWarning)
- # This should become an error once deprecation cycle is complete.
- form = modelform_factory(Person)
- self.assertEqual(w[0].category, PendingDeprecationWarning)
- def test_modelform_factory_with_all_fields(self):
- form = modelform_factory(Person, fields="__all__")
- self.assertEqual(list(form.base_fields), ["name"])
|