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 PartiallyRequiredField(MultiValueField): def compress(self, data_list): return ",".join(data_list) if data_list else None class PartiallyRequiredForm(Form): f = PartiallyRequiredField( fields=(CharField(required=True), CharField(required=False)), required=True, require_all_fields=False, widget=MultiWidget(widgets=[TextInput(), TextInput()]), ) 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(), """ """, ) 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(), """ """, ) 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" ) def test_render_required_attributes(self): form = PartiallyRequiredForm({"f_0": "Hello", "f_1": ""}) self.assertTrue(form.is_valid()) self.assertInHTML( '', form.as_p(), ) self.assertInHTML('', form.as_p()) form = PartiallyRequiredForm({"f_0": "", "f_1": ""}) self.assertFalse(form.is_valid())