| 944 | |
| 945 | |
| 946 | class MultipleChoiceField(ChoiceField): |
| 947 | hidden_widget = MultipleHiddenInput |
| 948 | widget = SelectMultiple |
| 949 | default_error_messages = { |
| 950 | "invalid_choice": _( |
| 951 | "Select a valid choice. %(value)s is not one of the available choices." |
| 952 | ), |
| 953 | "invalid_list": _("Enter a list of values."), |
| 954 | } |
| 955 | |
| 956 | def to_python(self, value): |
| 957 | if not value: |
| 958 | return [] |
| 959 | elif not isinstance(value, (list, tuple)): |
| 960 | raise ValidationError( |
| 961 | self.error_messages["invalid_list"], code="invalid_list" |
| 962 | ) |
| 963 | return [str(val) for val in value] |
| 964 | |
| 965 | def validate(self, value): |
| 966 | """Validate that the input is a list or tuple.""" |
| 967 | if self.required and not value: |
| 968 | raise ValidationError(self.error_messages["required"], code="required") |
| 969 | # Validate that each value in the value list is in self.choices. |
| 970 | # Avoid redundant validation, and keep elements ordered. |
| 971 | for val in OrderedSet(value): |
| 972 | if not self.valid_value(val): |
| 973 | raise ValidationError( |
| 974 | self.error_messages["invalid_choice"], |
| 975 | code="invalid_choice", |
| 976 | params={"value": val}, |
| 977 | ) |
| 978 | |
| 979 | def has_changed(self, initial, data): |
| 980 | if self.disabled: |
| 981 | return False |
| 982 | if initial is None: |
| 983 | initial = [] |
| 984 | if data is None: |
| 985 | data = [] |
| 986 | if len(initial) != len(data): |
| 987 | return True |
| 988 | initial_set = {str(value) for value in initial} |
| 989 | data_set = {str(value) for value in data} |
| 990 | return data_set != initial_set |
| 991 | |
| 992 | |
| 993 | class TypedMultipleChoiceField(MultipleChoiceField): |
no outgoing calls