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

1import warnings 

2 

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) 

15 

16from phonenumber_field.phonenumber import PhoneNumber, to_python 

17 

18try: 

19 import babel 

20except ModuleNotFoundError: 

21 babel = None 

22 

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} 

29 

30 

31def localized_choices(language): 

32 if babel is None: 

33 raise ImproperlyConfigured( 

34 "The PhonePrefixSelect widget requires the babel package be installed." 

35 ) 

36 

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 

45 

46 

47class PhonePrefixSelect(Select): 

48 initial = None 

49 

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) 

60 

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) 

65 

66 

67class PhoneNumberPrefixWidget(MultiWidget): 

68 """ 

69 A Widget that splits a phone number into fields: 

70 

71 - a :class:`~django.forms.Select` for the country (phone prefix) 

72 - a :class:`~django.forms.TextInput` for local phone number 

73 """ 

74 

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) 

101 

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] 

112 

113 def value_from_datadict(self, data, files, name): 

114 region_code, national_number = super().value_from_datadict(data, files, name) 

115 

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 

123 

124 

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 """ 

131 

132 input_type = "tel" 

133 

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) 

144 

145 def value_from_datadict(self, data, files, name): 

146 phone_number_str = super().value_from_datadict(data, files, name) 

147 

148 if phone_number_str is None: 

149 phone_number_str = "" 

150 return to_python(phone_number_str, region=self.region) 

151 

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) 

159 

160 

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 ) 

170 

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)