tests.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. from __future__ import unicode_literals
  2. from datetime import date
  3. import unittest
  4. import warnings
  5. from django import forms
  6. from django.core.exceptions import FieldError, ValidationError
  7. from django.core.files.uploadedfile import SimpleUploadedFile
  8. from django.forms.models import (modelform_factory, ModelChoiceField,
  9. fields_for_model, construct_instance, ModelFormMetaclass)
  10. from django.utils import six
  11. from django.test import TestCase
  12. from .models import (Person, RealPerson, Triple, FilePathModel, Article,
  13. Publication, CustomFF, Author, Author1, Homepage, Document, Edition)
  14. class ModelMultipleChoiceFieldTests(TestCase):
  15. def test_model_multiple_choice_number_of_queries(self):
  16. """
  17. Test that ModelMultipleChoiceField does O(1) queries instead of
  18. O(n) (#10156).
  19. """
  20. persons = [Person.objects.create(name="Person %s" % i) for i in range(30)]
  21. f = forms.ModelMultipleChoiceField(queryset=Person.objects.all())
  22. self.assertNumQueries(1, f.clean, [p.pk for p in persons[1:11:2]])
  23. def test_model_multiple_choice_run_validators(self):
  24. """
  25. Test that ModelMultipleChoiceField run given validators (#14144).
  26. """
  27. for i in range(30):
  28. Person.objects.create(name="Person %s" % i)
  29. self._validator_run = False
  30. def my_validator(value):
  31. self._validator_run = True
  32. f = forms.ModelMultipleChoiceField(queryset=Person.objects.all(),
  33. validators=[my_validator])
  34. f.clean([p.pk for p in Person.objects.all()[8:9]])
  35. self.assertTrue(self._validator_run)
  36. class TripleForm(forms.ModelForm):
  37. class Meta:
  38. model = Triple
  39. fields = '__all__'
  40. class UniqueTogetherTests(TestCase):
  41. def test_multiple_field_unique_together(self):
  42. """
  43. When the same field is involved in multiple unique_together
  44. constraints, we need to make sure we don't remove the data for it
  45. before doing all the validation checking (not just failing after
  46. the first one).
  47. """
  48. Triple.objects.create(left=1, middle=2, right=3)
  49. form = TripleForm({'left': '1', 'middle': '2', 'right': '3'})
  50. self.assertFalse(form.is_valid())
  51. form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
  52. self.assertTrue(form.is_valid())
  53. class TripleFormWithCleanOverride(forms.ModelForm):
  54. class Meta:
  55. model = Triple
  56. fields = '__all__'
  57. def clean(self):
  58. if not self.cleaned_data['left'] == self.cleaned_data['right']:
  59. raise forms.ValidationError('Left and right should be equal')
  60. return self.cleaned_data
  61. class OverrideCleanTests(TestCase):
  62. def test_override_clean(self):
  63. """
  64. Regression for #12596: Calling super from ModelForm.clean() should be
  65. optional.
  66. """
  67. form = TripleFormWithCleanOverride({'left': 1, 'middle': 2, 'right': 1})
  68. self.assertTrue(form.is_valid())
  69. # form.instance.left will be None if the instance was not constructed
  70. # by form.full_clean().
  71. self.assertEqual(form.instance.left, 1)
  72. class PartiallyLocalizedTripleForm(forms.ModelForm):
  73. class Meta:
  74. model = Triple
  75. localized_fields = ('left', 'right',)
  76. fields = '__all__'
  77. class FullyLocalizedTripleForm(forms.ModelForm):
  78. class Meta:
  79. model = Triple
  80. localized_fields = '__all__'
  81. fields = '__all__'
  82. class LocalizedModelFormTest(TestCase):
  83. def test_model_form_applies_localize_to_some_fields(self):
  84. f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
  85. self.assertTrue(f.is_valid())
  86. self.assertTrue(f.fields['left'].localize)
  87. self.assertFalse(f.fields['middle'].localize)
  88. self.assertTrue(f.fields['right'].localize)
  89. def test_model_form_applies_localize_to_all_fields(self):
  90. f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
  91. self.assertTrue(f.is_valid())
  92. self.assertTrue(f.fields['left'].localize)
  93. self.assertTrue(f.fields['middle'].localize)
  94. self.assertTrue(f.fields['right'].localize)
  95. def test_model_form_refuses_arbitrary_string(self):
  96. with self.assertRaises(TypeError):
  97. class BrokenLocalizedTripleForm(forms.ModelForm):
  98. class Meta:
  99. model = Triple
  100. localized_fields = "foo"
  101. # Regression test for #12960.
  102. # Make sure the cleaned_data returned from ModelForm.clean() is applied to the
  103. # model instance.
  104. class PublicationForm(forms.ModelForm):
  105. def clean(self):
  106. self.cleaned_data['title'] = self.cleaned_data['title'].upper()
  107. return self.cleaned_data
  108. class Meta:
  109. model = Publication
  110. fields = '__all__'
  111. class ModelFormCleanTest(TestCase):
  112. def test_model_form_clean_applies_to_model(self):
  113. data = {'title': 'test', 'date_published': '2010-2-25'}
  114. form = PublicationForm(data)
  115. publication = form.save()
  116. self.assertEqual(publication.title, 'TEST')
  117. class FPForm(forms.ModelForm):
  118. class Meta:
  119. model = FilePathModel
  120. fields = '__all__'
  121. class FilePathFieldTests(TestCase):
  122. def test_file_path_field_blank(self):
  123. """
  124. Regression test for #8842: FilePathField(blank=True)
  125. """
  126. form = FPForm()
  127. names = [p[1] for p in form['path'].field.choices]
  128. names.sort()
  129. self.assertEqual(names, ['---------', '__init__.py', 'models.py', 'tests.py'])
  130. class ManyToManyCallableInitialTests(TestCase):
  131. def test_callable(self):
  132. "Regression for #10349: A callable can be provided as the initial value for an m2m field"
  133. # Set up a callable initial value
  134. def formfield_for_dbfield(db_field, **kwargs):
  135. if db_field.name == 'publications':
  136. kwargs['initial'] = lambda: Publication.objects.all().order_by('date_published')[:2]
  137. return db_field.formfield(**kwargs)
  138. # Set up some Publications to use as data
  139. book1 = Publication.objects.create(title="First Book", date_published=date(2007,1,1))
  140. book2 = Publication.objects.create(title="Second Book", date_published=date(2008,1,1))
  141. book3 = Publication.objects.create(title="Third Book", date_published=date(2009,1,1))
  142. # Create a ModelForm, instantiate it, and check that the output is as expected
  143. ModelForm = modelform_factory(Article, fields="__all__",
  144. formfield_callback=formfield_for_dbfield)
  145. form = ModelForm()
  146. self.assertHTMLEqual(form.as_ul(), """<li><label for="id_headline">Headline:</label> <input id="id_headline" type="text" name="headline" maxlength="100" /></li>
  147. <li><label for="id_publications">Publications:</label> <select multiple="multiple" name="publications" id="id_publications">
  148. <option value="%d" selected="selected">First Book</option>
  149. <option value="%d" selected="selected">Second Book</option>
  150. <option value="%d">Third Book</option>
  151. </select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>"""
  152. % (book1.pk, book2.pk, book3.pk))
  153. class CFFForm(forms.ModelForm):
  154. class Meta:
  155. model = CustomFF
  156. fields = '__all__'
  157. class CustomFieldSaveTests(TestCase):
  158. def test_save(self):
  159. "Regression for #11149: save_form_data should be called only once"
  160. # It's enough that the form saves without error -- the custom save routine will
  161. # generate an AssertionError if it is called more than once during save.
  162. form = CFFForm(data = {'f': None})
  163. form.save()
  164. class ModelChoiceIteratorTests(TestCase):
  165. def test_len(self):
  166. class Form(forms.ModelForm):
  167. class Meta:
  168. model = Article
  169. fields = ["publications"]
  170. Publication.objects.create(title="Pravda",
  171. date_published=date(1991, 8, 22))
  172. f = Form()
  173. self.assertEqual(len(f.fields["publications"].choices), 1)
  174. class RealPersonForm(forms.ModelForm):
  175. class Meta:
  176. model = RealPerson
  177. fields = '__all__'
  178. class CustomModelFormSaveMethod(TestCase):
  179. def test_string_message(self):
  180. data = {'name': 'anonymous'}
  181. form = RealPersonForm(data)
  182. self.assertEqual(form.is_valid(), False)
  183. self.assertEqual(form.errors['__all__'], ['Please specify a real name.'])
  184. class ModelClassTests(TestCase):
  185. def test_no_model_class(self):
  186. class NoModelModelForm(forms.ModelForm):
  187. pass
  188. self.assertRaises(ValueError, NoModelModelForm)
  189. class OneToOneFieldTests(TestCase):
  190. def test_assignment_of_none(self):
  191. class AuthorForm(forms.ModelForm):
  192. class Meta:
  193. model = Author
  194. fields = ['publication', 'full_name']
  195. publication = Publication.objects.create(title="Pravda",
  196. date_published=date(1991, 8, 22))
  197. author = Author.objects.create(publication=publication, full_name='John Doe')
  198. form = AuthorForm({'publication':'', 'full_name':'John Doe'}, instance=author)
  199. self.assertTrue(form.is_valid())
  200. self.assertEqual(form.cleaned_data['publication'], None)
  201. author = form.save()
  202. # author object returned from form still retains original publication object
  203. # that's why we need to retreive it from database again
  204. new_author = Author.objects.get(pk=author.pk)
  205. self.assertEqual(new_author.publication, None)
  206. def test_assignment_of_none_null_false(self):
  207. class AuthorForm(forms.ModelForm):
  208. class Meta:
  209. model = Author1
  210. fields = ['publication', 'full_name']
  211. publication = Publication.objects.create(title="Pravda",
  212. date_published=date(1991, 8, 22))
  213. author = Author1.objects.create(publication=publication, full_name='John Doe')
  214. form = AuthorForm({'publication':'', 'full_name':'John Doe'}, instance=author)
  215. self.assertTrue(not form.is_valid())
  216. class ModelChoiceForm(forms.Form):
  217. person = ModelChoiceField(Person.objects.all())
  218. class TestTicket11183(TestCase):
  219. def test_11183(self):
  220. form1 = ModelChoiceForm()
  221. field1 = form1.fields['person']
  222. # To allow the widget to change the queryset of field1.widget.choices correctly,
  223. # without affecting other forms, the following must hold:
  224. self.assertTrue(field1 is not ModelChoiceForm.base_fields['person'])
  225. self.assertTrue(field1.widget.choices.field is field1)
  226. class HomepageForm(forms.ModelForm):
  227. class Meta:
  228. model = Homepage
  229. fields = '__all__'
  230. class URLFieldTests(TestCase):
  231. def test_url_on_modelform(self):
  232. "Check basic URL field validation on model forms"
  233. self.assertFalse(HomepageForm({'url': 'foo'}).is_valid())
  234. self.assertFalse(HomepageForm({'url': 'http://'}).is_valid())
  235. self.assertFalse(HomepageForm({'url': 'http://example'}).is_valid())
  236. self.assertFalse(HomepageForm({'url': 'http://example.'}).is_valid())
  237. self.assertFalse(HomepageForm({'url': 'http://com.'}).is_valid())
  238. self.assertTrue(HomepageForm({'url': 'http://localhost'}).is_valid())
  239. self.assertTrue(HomepageForm({'url': 'http://example.com'}).is_valid())
  240. self.assertTrue(HomepageForm({'url': 'http://www.example.com'}).is_valid())
  241. self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000'}).is_valid())
  242. self.assertTrue(HomepageForm({'url': 'http://www.example.com/test'}).is_valid())
  243. self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000/test'}).is_valid())
  244. self.assertTrue(HomepageForm({'url': 'http://example.com/foo/bar'}).is_valid())
  245. def test_http_prefixing(self):
  246. "If the http:// prefix is omitted on form input, the field adds it again. (Refs #13613)"
  247. form = HomepageForm({'url': 'example.com'})
  248. form.is_valid()
  249. # self.assertTrue(form.is_valid())
  250. # self.assertEqual(form.cleaned_data['url'], 'http://example.com/')
  251. form = HomepageForm({'url': 'example.com/test'})
  252. form.is_valid()
  253. # self.assertTrue(form.is_valid())
  254. # self.assertEqual(form.cleaned_data['url'], 'http://example.com/test')
  255. class FormFieldCallbackTests(TestCase):
  256. def test_baseform_with_widgets_in_meta(self):
  257. """Regression for #13095: Using base forms with widgets defined in Meta should not raise errors."""
  258. widget = forms.Textarea()
  259. class BaseForm(forms.ModelForm):
  260. class Meta:
  261. model = Person
  262. widgets = {'name': widget}
  263. fields = "__all__"
  264. Form = modelform_factory(Person, form=BaseForm)
  265. self.assertTrue(Form.base_fields['name'].widget is widget)
  266. def test_factory_with_widget_argument(self):
  267. """ Regression for #15315: modelform_factory should accept widgets
  268. argument
  269. """
  270. widget = forms.Textarea()
  271. # Without a widget should not set the widget to textarea
  272. Form = modelform_factory(Person, fields="__all__")
  273. self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
  274. # With a widget should not set the widget to textarea
  275. Form = modelform_factory(Person, fields="__all__", widgets={'name':widget})
  276. self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
  277. def test_custom_callback(self):
  278. """Test that a custom formfield_callback is used if provided"""
  279. callback_args = []
  280. def callback(db_field, **kwargs):
  281. callback_args.append((db_field, kwargs))
  282. return db_field.formfield(**kwargs)
  283. widget = forms.Textarea()
  284. class BaseForm(forms.ModelForm):
  285. class Meta:
  286. model = Person
  287. widgets = {'name': widget}
  288. fields = "__all__"
  289. _ = modelform_factory(Person, form=BaseForm,
  290. formfield_callback=callback)
  291. id_field, name_field = Person._meta.fields
  292. self.assertEqual(callback_args,
  293. [(id_field, {}), (name_field, {'widget': widget})])
  294. def test_bad_callback(self):
  295. # A bad callback provided by user still gives an error
  296. self.assertRaises(TypeError, modelform_factory, Person, fields="__all__",
  297. formfield_callback='not a function or callable')
  298. class InvalidFieldAndFactory(TestCase):
  299. """ Tests for #11905 """
  300. def test_extra_field_model_form(self):
  301. try:
  302. class ExtraPersonForm(forms.ModelForm):
  303. """ ModelForm with an extra field """
  304. age = forms.IntegerField()
  305. class Meta:
  306. model = Person
  307. fields = ('name', 'no-field')
  308. except FieldError as e:
  309. # Make sure the exception contains some reference to the
  310. # field responsible for the problem.
  311. self.assertTrue('no-field' in e.args[0])
  312. else:
  313. self.fail('Invalid "no-field" field not caught')
  314. def test_extra_declared_field_model_form(self):
  315. try:
  316. class ExtraPersonForm(forms.ModelForm):
  317. """ ModelForm with an extra field """
  318. age = forms.IntegerField()
  319. class Meta:
  320. model = Person
  321. fields = ('name', 'age')
  322. except FieldError:
  323. self.fail('Declarative field raised FieldError incorrectly')
  324. def test_extra_field_modelform_factory(self):
  325. self.assertRaises(FieldError, modelform_factory,
  326. Person, fields=['no-field', 'name'])
  327. class DocumentForm(forms.ModelForm):
  328. class Meta:
  329. model = Document
  330. fields = '__all__'
  331. class FileFieldTests(unittest.TestCase):
  332. def test_clean_false(self):
  333. """
  334. If the ``clean`` method on a non-required FileField receives False as
  335. the data (meaning clear the field value), it returns False, regardless
  336. of the value of ``initial``.
  337. """
  338. f = forms.FileField(required=False)
  339. self.assertEqual(f.clean(False), False)
  340. self.assertEqual(f.clean(False, 'initial'), False)
  341. def test_clean_false_required(self):
  342. """
  343. If the ``clean`` method on a required FileField receives False as the
  344. data, it has the same effect as None: initial is returned if non-empty,
  345. otherwise the validation catches the lack of a required value.
  346. """
  347. f = forms.FileField(required=True)
  348. self.assertEqual(f.clean(False, 'initial'), 'initial')
  349. self.assertRaises(ValidationError, f.clean, False)
  350. def test_full_clear(self):
  351. """
  352. Integration happy-path test that a model FileField can actually be set
  353. and cleared via a ModelForm.
  354. """
  355. form = DocumentForm()
  356. self.assertTrue('name="myfile"' in six.text_type(form))
  357. self.assertTrue('myfile-clear' not in six.text_type(form))
  358. form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', b'content')})
  359. self.assertTrue(form.is_valid())
  360. doc = form.save(commit=False)
  361. self.assertEqual(doc.myfile.name, 'something.txt')
  362. form = DocumentForm(instance=doc)
  363. self.assertTrue('myfile-clear' in six.text_type(form))
  364. form = DocumentForm(instance=doc, data={'myfile-clear': 'true'})
  365. doc = form.save(commit=False)
  366. self.assertEqual(bool(doc.myfile), False)
  367. def test_clear_and_file_contradiction(self):
  368. """
  369. If the user submits a new file upload AND checks the clear checkbox,
  370. they get a validation error, and the bound redisplay of the form still
  371. includes the current file and the clear checkbox.
  372. """
  373. form = DocumentForm(files={'myfile': SimpleUploadedFile('something.txt', b'content')})
  374. self.assertTrue(form.is_valid())
  375. doc = form.save(commit=False)
  376. form = DocumentForm(instance=doc,
  377. files={'myfile': SimpleUploadedFile('something.txt', b'content')},
  378. data={'myfile-clear': 'true'})
  379. self.assertTrue(not form.is_valid())
  380. self.assertEqual(form.errors['myfile'],
  381. ['Please either submit a file or check the clear checkbox, not both.'])
  382. rendered = six.text_type(form)
  383. self.assertTrue('something.txt' in rendered)
  384. self.assertTrue('myfile-clear' in rendered)
  385. class EditionForm(forms.ModelForm):
  386. author = forms.ModelChoiceField(queryset=Person.objects.all())
  387. publication = forms.ModelChoiceField(queryset=Publication.objects.all())
  388. edition = forms.IntegerField()
  389. isbn = forms.CharField(max_length=13)
  390. class Meta:
  391. model = Edition
  392. fields = '__all__'
  393. class UniqueErrorsTests(TestCase):
  394. def setUp(self):
  395. self.author1 = Person.objects.create(name='Author #1')
  396. self.author2 = Person.objects.create(name='Author #2')
  397. self.pub1 = Publication.objects.create(title='Pub #1', date_published=date(2000, 10, 31))
  398. self.pub2 = Publication.objects.create(title='Pub #2', date_published=date(2004, 1, 5))
  399. form = EditionForm(data={'author': self.author1.pk, 'publication': self.pub1.pk, 'edition': 1, 'isbn': '9783161484100'})
  400. form.save()
  401. def test_unique_error_message(self):
  402. form = EditionForm(data={'author': self.author1.pk, 'publication': self.pub2.pk, 'edition': 1, 'isbn': '9783161484100'})
  403. self.assertEqual(form.errors, {'isbn': ['Edition with this Isbn already exists.']})
  404. def test_unique_together_error_message(self):
  405. form = EditionForm(data={'author': self.author1.pk, 'publication': self.pub1.pk, 'edition': 2, 'isbn': '9783161489999'})
  406. self.assertEqual(form.errors, {'__all__': ['Edition with this Author and Publication already exists.']})
  407. form = EditionForm(data={'author': self.author2.pk, 'publication': self.pub1.pk, 'edition': 1, 'isbn': '9783161487777'})
  408. self.assertEqual(form.errors, {'__all__': ['Edition with this Publication and Edition already exists.']})
  409. class EmptyFieldsTestCase(TestCase):
  410. "Tests for fields=() cases as reported in #14119"
  411. class EmptyPersonForm(forms.ModelForm):
  412. class Meta:
  413. model = Person
  414. fields = ()
  415. def test_empty_fields_to_fields_for_model(self):
  416. "An argument of fields=() to fields_for_model should return an empty dictionary"
  417. field_dict = fields_for_model(Person, fields=())
  418. self.assertEqual(len(field_dict), 0)
  419. def test_empty_fields_on_modelform(self):
  420. "No fields on a ModelForm should actually result in no fields"
  421. form = self.EmptyPersonForm()
  422. self.assertEqual(len(form.fields), 0)
  423. def test_empty_fields_to_construct_instance(self):
  424. "No fields should be set on a model instance if construct_instance receives fields=()"
  425. form = modelform_factory(Person, fields="__all__")({'name': 'John Doe'})
  426. self.assertTrue(form.is_valid())
  427. instance = construct_instance(form, Person(), fields=())
  428. self.assertEqual(instance.name, '')
  429. class CustomMetaclass(ModelFormMetaclass):
  430. def __new__(cls, name, bases, attrs):
  431. new = super(CustomMetaclass, cls).__new__(cls, name, bases, attrs)
  432. new.base_fields = {}
  433. return new
  434. class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)):
  435. pass
  436. class CustomMetaclassTestCase(TestCase):
  437. def test_modelform_factory_metaclass(self):
  438. new_cls = modelform_factory(Person, fields="__all__", form=CustomMetaclassForm)
  439. self.assertEqual(new_cls.base_fields, {})
  440. class TestTicket19733(TestCase):
  441. def test_modelform_factory_without_fields(self):
  442. with warnings.catch_warnings(record=True) as w:
  443. warnings.simplefilter("always", DeprecationWarning)
  444. # This should become an error once deprecation cycle is complete.
  445. form = modelform_factory(Person)
  446. self.assertEqual(w[0].category, DeprecationWarning)
  447. def test_modelform_factory_with_all_fields(self):
  448. form = modelform_factory(Person, fields="__all__")
  449. self.assertEqual(list(form.base_fields), ["name"])