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(), """
  • Hold down "Control", or "Command" on a Mac, to select more than one.
  • """ % (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"])