Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/phonenumber_field/widgets.py: 29%
95 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import warnings
3from django.conf import settings
4from django.core import validators
5from django.core.exceptions import ImproperlyConfigured
6from django.forms import Select, TextInput
7from django.forms.widgets import MultiWidget
8from django.utils import translation
9from phonenumbers.phonenumberutil import (
10 COUNTRY_CODE_TO_REGION_CODE,
11 national_significant_number,
12 region_code_for_number,
13 region_codes_for_country_code,
14)
16from phonenumber_field.phonenumber import PhoneNumber, to_python
18try:
19 import babel
20except ModuleNotFoundError:
21 babel = None
23# ISO 3166-1 alpha-2 to national prefix
24REGION_CODE_TO_COUNTRY_CODE = {
25 region_code: country_code
26 for country_code, region_codes in COUNTRY_CODE_TO_REGION_CODE.items()
27 for region_code in region_codes
28}
31def localized_choices(language):
32 if babel is None:
33 raise ImproperlyConfigured(
34 "The PhonePrefixSelect widget requires the babel package be installed."
35 )
37 choices = [("", "---------")]
38 locale_name = translation.to_locale(language)
39 locale = babel.Locale(locale_name)
40 for region_code, country_code in REGION_CODE_TO_COUNTRY_CODE.items():
41 region_name = locale.territories.get(region_code)
42 if region_name:
43 choices.append((region_code, f"{region_name} +{country_code}"))
44 return choices
47class PhonePrefixSelect(Select):
48 initial = None
50 def __init__(self, initial=None, attrs=None, choices=None):
51 language = translation.get_language() or settings.LANGUAGE_CODE
52 if choices is None:
53 choices = localized_choices(language)
54 choices.sort(key=lambda item: item[1])
55 if initial is None:
56 initial = getattr(settings, "PHONENUMBER_DEFAULT_REGION", None)
57 if initial in REGION_CODE_TO_COUNTRY_CODE:
58 self.initial = initial
59 super().__init__(attrs=attrs, choices=choices)
61 def get_context(self, name, value, attrs):
62 attrs = (attrs or {}).copy()
63 attrs.pop("maxlength", None)
64 return super().get_context(name, value or self.initial, attrs)
67class PhoneNumberPrefixWidget(MultiWidget):
68 """
69 A Widget that splits a phone number into fields:
71 - a :class:`~django.forms.Select` for the country (phone prefix)
72 - a :class:`~django.forms.TextInput` for local phone number
73 """
75 def __init__(
76 self,
77 attrs=None,
78 initial=None,
79 country_attrs=None,
80 country_choices=None,
81 number_attrs=None,
82 ):
83 """
84 :keyword dict attrs: See :attr:`~django.forms.Widget.attrs`
85 :keyword dict initial: See :attr:`~django.forms.Field.initial`
86 :keyword dict country_attrs: The :attr:`~django.forms.Widget.attrs` for
87 the country :class:`~django.forms.Select`.
88 :keyword country_choices: Limit the country choices.
89 :type country_choices: list of 2-tuple, optional
90 The first element is the value, which must match a country code
91 recognized by the phonenumbers project.
92 The second element is the label.
93 :keyword dict number_attrs: The :attr:`~django.forms.Widget.attrs` for
94 the local phone number :class:`~django.forms.TextInput`.
95 """
96 widgets = (
97 PhonePrefixSelect(initial, attrs=country_attrs, choices=country_choices),
98 TextInput(attrs=number_attrs),
99 )
100 super().__init__(widgets, attrs)
102 def decompress(self, value):
103 if isinstance(value, PhoneNumber):
104 if not value.is_valid():
105 region = getattr(settings, "PHONENUMBER_DEFAULT_REGION", None)
106 region_code = getattr(value, "_region", region)
107 return [region_code, value.raw_input]
108 region_code = region_code_for_number(value)
109 national_number = national_significant_number(value)
110 return [region_code, national_number]
111 return [None, None]
113 def value_from_datadict(self, data, files, name):
114 region_code, national_number = super().value_from_datadict(data, files, name)
116 if national_number is None:
117 national_number = ""
118 number = to_python(national_number, region=region_code)
119 if number in validators.EMPTY_VALUES:
120 return number
121 number._region = region_code
122 return number
125class RegionalPhoneNumberWidget(TextInput):
126 """
127 A :class:`~django.forms.Widget` that prefers displaying numbers in the
128 national format, and falls back to :setting:`PHONENUMBER_DEFAULT_FORMAT`
129 for international numbers.
130 """
132 input_type = "tel"
134 def __init__(self, region=None, attrs=None):
135 """
136 :keyword str region: 2-letter country code as defined in ISO 3166-1.
137 When not supplied, defaults to :setting:`PHONENUMBER_DEFAULT_REGION`
138 :keyword dict attrs: See :attr:`django.forms.Widget.attrs`
139 """
140 if region is None:
141 region = getattr(settings, "PHONENUMBER_DEFAULT_REGION", None)
142 self.region = region
143 super().__init__(attrs)
145 def value_from_datadict(self, data, files, name):
146 phone_number_str = super().value_from_datadict(data, files, name)
148 if phone_number_str is None:
149 phone_number_str = ""
150 return to_python(phone_number_str, region=self.region)
152 def format_value(self, value):
153 if isinstance(value, PhoneNumber):
154 if value.is_valid():
155 region_codes = region_codes_for_country_code(value.country_code)
156 if self.region in region_codes:
157 return value.as_national
158 return super().format_value(value)
161class PhoneNumberInternationalFallbackWidget(RegionalPhoneNumberWidget):
162 def __init__(self, *args, **kwargs):
163 super().__init__(*args, **kwargs)
164 warnings.warn(
165 f"{self.__class__.__name__} will be removed in the next major version. "
166 "Use phonenumber_field.widgets.RegionalPhoneNumberWidget instead.",
167 DeprecationWarning,
168 stacklevel=2,
169 )
171 def format_value(self, value):
172 if isinstance(value, PhoneNumber):
173 if value.is_valid():
174 region_codes = region_codes_for_country_code(value.country_code)
175 if self.region in region_codes:
176 return value.as_national
177 return value.as_international
178 return super().format_value(value)