diff --git a/phonenumber_field/formfields.py b/phonenumber_field/formfields.py index 67ce537..f5fde44 100644 --- a/phonenumber_field/formfields.py +++ b/phonenumber_field/formfields.py @@ -104,7 +104,16 @@ class SplitPhoneNumberField(MultiValueField): default_validators = [validate_international_phonenumber] widget = widgets.PhoneNumberPrefixWidget - def __init__(self, *, initial=None, region=None, widget=None, **kwargs): + def __init__( + self, + *, + initial=None, + region=None, + widget=None, + empty_value="", + max_length=None, + **kwargs, + ): """ :keyword list initial: A two-elements iterable: @@ -122,6 +131,9 @@ def __init__(self, *, initial=None, region=None, widget=None, **kwargs): When not supplied, defaults to :setting:`PHONENUMBER_DEFAULT_REGION` :keyword ~django.forms.MultiWidget widget: defaults to :class:`~phonenumber_field.widgets.PhoneNumberPrefixWidget` + :keyword empty_value: value to use when the field is empty + :keyword int max_length: maximum length of the phone number, when + represented as :setting:`PHONENUMBER_DB_FORMAT`. """ validate_region(region) region = region or getattr(settings, "PHONENUMBER_DEFAULT_REGION", None) @@ -132,6 +144,8 @@ def __init__(self, *, initial=None, region=None, widget=None, **kwargs): fields = (prefix_field, number_field) if widget is None: widget = self.widget((prefix_field.widget, number_field.widget)) + self.empty_value = empty_value + self.max_length = max_length super().__init__(fields, initial=initial, widget=widget, **kwargs) def prefix_field(self): @@ -168,7 +182,7 @@ def invalid_error_message(self): def compress(self, data_list): if not data_list: - return data_list + return self.empty_value region, national_number = data_list return to_python(national_number, region=region) @@ -188,4 +202,19 @@ def clean(self, value): error_message, example_number=example_number, ) - return super().clean(value) + clean_value = super().clean(value) + if self.max_length is not None: + phonenumber_str = clean_value.format_as( + getattr(settings, "PHONENUMBER_DB_FORMAT", "E164") + ) + if len(phonenumber_str) > self.max_length: + raise ValidationError( + format_lazy( + "Ensure this value has no more than {max_length} characters, " + "“{value}” is {length} characters long.", + max_length=self.max_length, + value=phonenumber_str, + length=len(phonenumber_str), + ) + ) + return clean_value diff --git a/tests/models.py b/tests/models.py index 9e52a4e..653ba99 100644 --- a/tests/models.py +++ b/tests/models.py @@ -104,3 +104,7 @@ class FrenchPhoneOwner(models.Model): class TestModelRegionAR(models.Model): phone = PhoneNumberField(region="AR", blank=True, null=True) + + +class PhoneNumberWithMaxLength(models.Model): + phone = PhoneNumberField(max_length=3) diff --git a/tests/test_formfields.py b/tests/test_formfields.py index f9b3a45..9309839 100644 --- a/tests/test_formfields.py +++ b/tests/test_formfields.py @@ -12,6 +12,7 @@ from phonenumber_field.formfields import PhoneNumberField, SplitPhoneNumberField from phonenumber_field.phonenumber import PhoneNumber from phonenumber_field.validators import validate_phonenumber +from tests.models import NullablePhoneNumber, PhoneNumberWithMaxLength ALGERIAN_PHONE_NUMBER = "+213799136332" @@ -317,6 +318,7 @@ class TestForm(forms.Form): form = TestForm(data={"phone_0": "", "phone_1": ""}) self.assertIs(form.is_valid(), True) + self.assertEqual(form.cleaned_data["phone"], "") def test_no_region(self): class TestForm(forms.Form): @@ -389,6 +391,17 @@ class TestForm(forms.Form): form = TestForm(data={}) self.assertIs(form.is_valid(), True) + self.assertEqual(form.cleaned_data["phone"], "") + + def test_not_required_empty_value(self): + class TestForm(forms.Form): + phone = SplitPhoneNumberField(required=False) + phone_none = SplitPhoneNumberField(required=False, empty_value=None) + + form = TestForm(data={}) + self.assertIs(form.is_valid(), True) + self.assertEqual(form.cleaned_data["phone"], "") + self.assertIsNone(form.cleaned_data["phone_none"]) def test_keeps_region_with_invalid_national_number(self): class TestForm(forms.Form): @@ -696,3 +709,39 @@ class TestForm(forms.Form): form = TestForm({"phone_0": "FR", "phone_1": "1010"}) self.assertIs(form.is_valid(), True) + + def test_formfield_with_maxlength_null(self): + class TestForm(forms.Form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields = forms.fields_for_model( + NullablePhoneNumber, + ["phone_number"], + field_classes={"phone_number": SplitPhoneNumberField}, + ) + + form = TestForm(data={"phone_number_0": "FR", "phone_number_1": "612345678"}) + self.assertIs(form.is_valid(), True) + self.assertEqual(form.cleaned_data["phone_number"], "+33612345678") + + def test_formfield_with_max_length(self): + class TestForm(forms.Form): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields = forms.fields_for_model( + PhoneNumberWithMaxLength, + ["phone"], + field_classes={"phone": SplitPhoneNumberField}, + ) + + form = TestForm(data={"phone_0": "FR", "phone_1": "33612345678"}) + self.assertIs(form.is_valid(), False) + self.assertEqual( + form.errors, + { + "phone": [ + "Ensure this value has no more than 3 characters, " + "“6 12 34 56 78” is 13 characters long." + ], + }, + )