from datetime import datetime from django.core.exceptions import ValidationError from django.forms import ( CharField, Form, MultipleChoiceField, MultiValueField, MultiWidget, SelectMultiple, SplitDateTimeField, SplitDateTimeWidget, TextInput, ) from django.test import SimpleTestCase beatles = (('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')) class ComplexMultiWidget(MultiWidget): def __init__(self, attrs=None): widgets = ( TextInput(), SelectMultiple(choices=beatles), SplitDateTimeWidget(), ) super().__init__(widgets, attrs) def decompress(self, value): if value: data = value.split(',') return [ data[0], list(data[1]), datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S"), ] return [None, None, None] class ComplexField(MultiValueField): def __init__(self, **kwargs): fields = ( CharField(), MultipleChoiceField(choices=beatles), SplitDateTimeField(), ) super().__init__(fields, **kwargs) def compress(self, data_list): if data_list: return '%s,%s,%s' % (data_list[0], ''.join(data_list[1]), data_list[2]) return None class ComplexFieldForm(Form): field1 = ComplexField(widget=ComplexMultiWidget()) class MultiValueFieldTest(SimpleTestCase): @classmethod def setUpClass(cls): cls.field = ComplexField(widget=ComplexMultiWidget()) super().setUpClass() def test_clean(self): self.assertEqual( self.field.clean(['some text', ['J', 'P'], ['2007-04-25', '6:24:00']]), 'some text,JP,2007-04-25 06:24:00', ) def test_clean_disabled_multivalue(self): class ComplexFieldForm(Form): f = ComplexField(disabled=True, widget=ComplexMultiWidget) inputs = ( 'some text,JP,2007-04-25 06:24:00', ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']], ) for data in inputs: with self.subTest(data=data): form = ComplexFieldForm({}, initial={'f': data}) form.full_clean() self.assertEqual(form.errors, {}) self.assertEqual(form.cleaned_data, {'f': inputs[0]}) def test_bad_choice(self): msg = "'Select a valid choice. X is not one of the available choices.'" with self.assertRaisesMessage(ValidationError, msg): self.field.clean(['some text', ['X'], ['2007-04-25', '6:24:00']]) def test_no_value(self): """ If insufficient data is provided, None is substituted. """ msg = "'This field is required.'" with self.assertRaisesMessage(ValidationError, msg): self.field.clean(['some text', ['JP']]) def test_has_changed_no_initial(self): self.assertTrue(self.field.has_changed(None, ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']])) def test_has_changed_same(self): self.assertFalse(self.field.has_changed( 'some text,JP,2007-04-25 06:24:00', ['some text', ['J', 'P'], ['2007-04-25', '6:24:00']], )) def test_has_changed_first_widget(self): """ Test when the first widget's data has changed. """ self.assertTrue(self.field.has_changed( 'some text,JP,2007-04-25 06:24:00', ['other text', ['J', 'P'], ['2007-04-25', '6:24:00']], )) def test_has_changed_last_widget(self): """ Test when the last widget's data has changed. This ensures that it is not short circuiting while testing the widgets. """ self.assertTrue(self.field.has_changed( 'some text,JP,2007-04-25 06:24:00', ['some text', ['J', 'P'], ['2009-04-25', '11:44:00']], )) def test_disabled_has_changed(self): f = MultiValueField(fields=(CharField(), CharField()), disabled=True) self.assertIs(f.has_changed(['x', 'x'], ['y', 'y']), False) def test_form_as_table(self): form = ComplexFieldForm() self.assertHTMLEqual( form.as_table(), """ <tr><th><label for="id_field1_0">Field1:</label></th> <td><input type="text" name="field1_0" id="id_field1_0" required> <select multiple name="field1_1" id="id_field1_1" required> <option value="J">John</option> <option value="P">Paul</option> <option value="G">George</option> <option value="R">Ringo</option> </select> <input type="text" name="field1_2_0" id="id_field1_2_0" required> <input type="text" name="field1_2_1" id="id_field1_2_1" required></td></tr> """, ) def test_form_as_table_data(self): form = ComplexFieldForm({ 'field1_0': 'some text', 'field1_1': ['J', 'P'], 'field1_2_0': '2007-04-25', 'field1_2_1': '06:24:00', }) self.assertHTMLEqual( form.as_table(), """ <tr><th><label for="id_field1_0">Field1:</label></th> <td><input type="text" name="field1_0" value="some text" id="id_field1_0" required> <select multiple name="field1_1" id="id_field1_1" required> <option value="J" selected>John</option> <option value="P" selected>Paul</option> <option value="G">George</option> <option value="R">Ringo</option> </select> <input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" required> <input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" required></td></tr> """, ) def test_form_cleaned_data(self): form = ComplexFieldForm({ 'field1_0': 'some text', 'field1_1': ['J', 'P'], 'field1_2_0': '2007-04-25', 'field1_2_1': '06:24:00', }) form.is_valid() self.assertEqual(form.cleaned_data['field1'], 'some text,JP,2007-04-25 06:24:00')