tests.py 22 KB

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