educommon 3.10.0__py3-none-any.whl → 3.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- educommon/contingent/actions.py +10 -3
- educommon/contingent/base.py +1 -1
- educommon/contingent/json_data/oksm.json +1230 -724
- educommon/django/db/fields.py +5 -1
- educommon/utils/phone_number/__init__.py +0 -0
- educommon/utils/phone_number/enums.py +47 -0
- educommon/utils/phone_number/modelfields.py +91 -0
- educommon/utils/phone_number/phone_number.py +273 -0
- educommon/utils/phone_number/validators.py +92 -0
- educommon/version.conf +5 -5
- {educommon-3.10.0.dist-info → educommon-3.11.0.dist-info}/METADATA +1 -1
- {educommon-3.10.0.dist-info → educommon-3.11.0.dist-info}/RECORD +15 -10
- {educommon-3.10.0.dist-info → educommon-3.11.0.dist-info}/WHEEL +1 -1
- {educommon-3.10.0.dist-info → educommon-3.11.0.dist-info}/dependency_links.txt +0 -0
- {educommon-3.10.0.dist-info → educommon-3.11.0.dist-info}/top_level.txt +0 -0
educommon/django/db/fields.py
CHANGED
@@ -24,6 +24,9 @@ from educommon.django.db.validators import (
|
|
24
24
|
from educommon.utils.misc import (
|
25
25
|
cached_property,
|
26
26
|
)
|
27
|
+
from educommon.utils.phone_number.modelfields import (
|
28
|
+
PhoneField,
|
29
|
+
)
|
27
30
|
|
28
31
|
|
29
32
|
__all__ = [
|
@@ -41,7 +44,8 @@ __all__ = [
|
|
41
44
|
'PassportNumberField',
|
42
45
|
'INNField',
|
43
46
|
'KPPField',
|
44
|
-
'OGRNField'
|
47
|
+
'OGRNField',
|
48
|
+
'PhoneField',
|
45
49
|
]
|
46
50
|
|
47
51
|
|
File without changes
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from enum import (
|
2
|
+
Enum,
|
3
|
+
)
|
4
|
+
|
5
|
+
|
6
|
+
class PhoneFieldType(Enum):
|
7
|
+
"""Тип номера телефона для поля модели."""
|
8
|
+
|
9
|
+
COMMON = 'COMMON'
|
10
|
+
E164 = 'E164'
|
11
|
+
RU = 'RU'
|
12
|
+
RU_E164 = 'RU_E164'
|
13
|
+
RU_LOCAL = 'RU_LOCAL'
|
14
|
+
RU_MOBILE = 'RU_MOBILE'
|
15
|
+
|
16
|
+
values = {
|
17
|
+
COMMON: 'Общий формат телефона',
|
18
|
+
E164: 'Международный формат телефона',
|
19
|
+
RU: 'Общий формат российского номера',
|
20
|
+
RU_E164: 'Российский номер в международном формате',
|
21
|
+
RU_LOCAL: 'Российский городской номер',
|
22
|
+
RU_MOBILE: 'Российский мобильный номер',
|
23
|
+
}
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def get_choices(cls):
|
27
|
+
return list(cls.values.items())
|
28
|
+
|
29
|
+
|
30
|
+
class PhoneNumberType(Enum):
|
31
|
+
"""Тип формата номера телефона."""
|
32
|
+
|
33
|
+
E164 = 'E164'
|
34
|
+
RU_E164 = 'RU_E164'
|
35
|
+
RU_LOCAL = 'RU_LOCAL'
|
36
|
+
UNKNOWN = 'UNKNOWN'
|
37
|
+
|
38
|
+
values = {
|
39
|
+
E164: 'Международный формат телефона',
|
40
|
+
RU_E164: 'Российский номер в международном формате',
|
41
|
+
RU_LOCAL: 'Российский городской номер',
|
42
|
+
UNKNOWN: 'Неизвестный формат',
|
43
|
+
}
|
44
|
+
|
45
|
+
@classmethod
|
46
|
+
def get_choices(cls):
|
47
|
+
return list(cls.values.items())
|
@@ -0,0 +1,91 @@
|
|
1
|
+
from django.db.models import (
|
2
|
+
CharField,
|
3
|
+
)
|
4
|
+
|
5
|
+
from educommon.utils.phone_number.enums import (
|
6
|
+
PhoneFieldType,
|
7
|
+
)
|
8
|
+
from educommon.utils.phone_number.phone_number import (
|
9
|
+
PhoneNumber,
|
10
|
+
to_python,
|
11
|
+
)
|
12
|
+
from educommon.utils.phone_number.validators import (
|
13
|
+
validate_common_phone_number,
|
14
|
+
validate_e164_phone_number,
|
15
|
+
validate_ru_e164_phone_number,
|
16
|
+
validate_ru_mobile_phone_number,
|
17
|
+
validate_ru_phone_number,
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
PHONE_FIELD_TYPE_TO_VALIDATOR = {
|
22
|
+
PhoneFieldType.COMMON: validate_common_phone_number,
|
23
|
+
PhoneFieldType.E164: validate_e164_phone_number,
|
24
|
+
PhoneFieldType.RU: validate_ru_phone_number,
|
25
|
+
PhoneFieldType.RU_E164: validate_ru_e164_phone_number,
|
26
|
+
PhoneFieldType.RU_MOBILE: validate_ru_mobile_phone_number,
|
27
|
+
}
|
28
|
+
|
29
|
+
|
30
|
+
class PhoneNumberDescriptor:
|
31
|
+
"""Дескриптор для поля с номером телефона инстанса модели.
|
32
|
+
|
33
|
+
Возвращает PhoneNumber при доступе к полю телефона.
|
34
|
+
|
35
|
+
Позволяет для поля задавать номер телефона как:
|
36
|
+
instance.phone = PhoneNumber(...)
|
37
|
+
либо
|
38
|
+
instance.phone = '+7 (900) 555-55-55'
|
39
|
+
"""
|
40
|
+
|
41
|
+
def __init__(self, field):
|
42
|
+
self.field = field
|
43
|
+
|
44
|
+
def __get__(self, instance, owner):
|
45
|
+
if instance is None:
|
46
|
+
return self
|
47
|
+
|
48
|
+
if self.field.name in instance.__dict__:
|
49
|
+
value = instance.__dict__[self.field.name]
|
50
|
+
else:
|
51
|
+
instance.refresh_from_db(fields=[self.field.name])
|
52
|
+
value = getattr(instance, self.field.name)
|
53
|
+
|
54
|
+
return value
|
55
|
+
|
56
|
+
def __set__(self, instance, value):
|
57
|
+
instance.__dict__[self.field.name] = to_python(value)
|
58
|
+
|
59
|
+
|
60
|
+
class PhoneField(CharField):
|
61
|
+
"""Поле с номером телефона."""
|
62
|
+
|
63
|
+
attr_class = PhoneNumber
|
64
|
+
descriptor_class = PhoneNumberDescriptor
|
65
|
+
|
66
|
+
empty_values = [*CharField.empty_values, PhoneNumber('')]
|
67
|
+
|
68
|
+
def __init__(self, *args, phone_type: PhoneFieldType = PhoneFieldType.COMMON, **kwargs):
|
69
|
+
kwargs.setdefault('max_length', 31)
|
70
|
+
super().__init__(*args, **kwargs)
|
71
|
+
|
72
|
+
self.validators.append(
|
73
|
+
PHONE_FIELD_TYPE_TO_VALIDATOR.get(phone_type, validate_common_phone_number)
|
74
|
+
)
|
75
|
+
|
76
|
+
def to_python(self, value):
|
77
|
+
"""Преобразование входящего значения в ожидаемый тип Python."""
|
78
|
+
return to_python(value)
|
79
|
+
|
80
|
+
def from_db_value(self, value, expression, connection):
|
81
|
+
"""Преобразование значения полученного из БД."""
|
82
|
+
return self.to_python(value)
|
83
|
+
|
84
|
+
def get_prep_value(self, value):
|
85
|
+
"""Подготовка значения перед сохранением в БД."""
|
86
|
+
if isinstance(value, PhoneNumber):
|
87
|
+
value = value.cleaned
|
88
|
+
elif value:
|
89
|
+
value = to_python(value).cleaned
|
90
|
+
|
91
|
+
return value or super().get_prep_value(value)
|
@@ -0,0 +1,273 @@
|
|
1
|
+
import re
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Optional,
|
5
|
+
Union,
|
6
|
+
)
|
7
|
+
|
8
|
+
from educommon.utils.phone_number.enums import (
|
9
|
+
PhoneNumberType,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
PHONE_REGEX = re.compile(
|
14
|
+
r'^(?:' # группа с кодом страны и кодом региона
|
15
|
+
r'(\+[1-6,90]{1,3}|\+?7|\+?8)?-?\s*' # опциональный код страны (+XXX, для России может быть +7, 7, 8)
|
16
|
+
r'\(?(\d{3}(?:[-\s]?\d{0,2}(?=[)-.\s]))?)\)?[-.\s]*' # оцпиональный код региона/города в необязательных скобках
|
17
|
+
r')?' # группа с кодом страны и кодом региона может отсутствовать
|
18
|
+
r'(\d{1,2}[-.\s]*\d?)[-.\s]*(\d[-.\s]*\d)[-.\s]*(\d[-.\s]*\d)$' # абонентский номер в формате XXX-XX-XX
|
19
|
+
# первая группа может состоять от 1 до 3 цифр
|
20
|
+
)
|
21
|
+
|
22
|
+
# российский мобильный номер вида +7 (9XX) XXX-XX-XX
|
23
|
+
RU_MOBILE_PHONE_REGEX = re.compile(r'^(\+?7|8)?-?\s*\(?(9\d{2})\)?[-.\s]*(\d{3})[-.\s]*(\d{2})[-.\s]*(\d{2})$')
|
24
|
+
|
25
|
+
# местный номер в формате: XXX-XX-XX, XX-XX-XX, X-XX-XX
|
26
|
+
LOCAL_PHONE_REGEX = re.compile(r'^(\d{1,3})[-.\s]*(\d{2})[-.\s]*(\d{2})$')
|
27
|
+
|
28
|
+
RU_PHONE_CODE = '+7'
|
29
|
+
|
30
|
+
|
31
|
+
class PhoneNumber:
|
32
|
+
"""Телефонный номер.
|
33
|
+
|
34
|
+
В качестве параметра принимает строку с номером телефона в различных форматах,
|
35
|
+
производит разбор и валидацию номера, приводит его к единому формату.
|
36
|
+
|
37
|
+
Поддерживаемые форматы:
|
38
|
+
+XXX (XXX) XXX-XX-XX - в общем виде
|
39
|
+
+7 (XXX) XXX-XX-XX - российский формат
|
40
|
+
8 (XXX) XXX-XX-XX, (XXX-XX) X-XX-XX - российский формат городских номеров
|
41
|
+
XXX-XX-XX, XX-XX-XX, X-XX-X - местные российские номера
|
42
|
+
|
43
|
+
Свойство cleaned возвращает номер в очищенном виде для возможности сохранения в БД.
|
44
|
+
Примеры входной строки и её преобразования в cleaned:
|
45
|
+
+12 (555) 888-77-66 -> +12(555)8887766
|
46
|
+
+7 (900) 555-44-33 -> +79005554433
|
47
|
+
8 800 555-44-33 -> +78005554433
|
48
|
+
(818) 222-11-00 -> +78182221100
|
49
|
+
(818-53) 2-11-00 -> +7(81853)21100
|
50
|
+
222 44 55 -> 2224455
|
51
|
+
2-44-55 -> 24455
|
52
|
+
|
53
|
+
Возвращаемое текстовое представление номера в формате:
|
54
|
+
+XXX (XXX) XXX-XX-XX
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(
|
58
|
+
self,
|
59
|
+
phone: Optional[str],
|
60
|
+
*,
|
61
|
+
need_sanitize: bool = False,
|
62
|
+
) -> None:
|
63
|
+
"""Инициализация.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
need_sanitize: Признак необходимости дополнительной очистки номера перед выполнением парсинга
|
67
|
+
"""
|
68
|
+
self.raw_phone = phone or ''
|
69
|
+
self._need_sanitize = need_sanitize
|
70
|
+
|
71
|
+
self._cleaned = ''
|
72
|
+
|
73
|
+
self.country_code = ''
|
74
|
+
self.region_code = ''
|
75
|
+
self.subscriber_part_1 = ''
|
76
|
+
self.subscriber_part_2 = ''
|
77
|
+
self.subscriber_part_3 = ''
|
78
|
+
|
79
|
+
self._is_parsed = False
|
80
|
+
self._is_e164 = False
|
81
|
+
|
82
|
+
self.parse()
|
83
|
+
|
84
|
+
@staticmethod
|
85
|
+
def _sanitize(value: str) -> str:
|
86
|
+
"""Очистка значения от незначащих символов."""
|
87
|
+
return value.replace(' ', '').replace('-', '')
|
88
|
+
|
89
|
+
@staticmethod
|
90
|
+
def _only_digits(value: str) -> str:
|
91
|
+
"""Возвращает из строки только цифры."""
|
92
|
+
return ''.join(ch for ch in value if ch.isdigit())
|
93
|
+
|
94
|
+
@property
|
95
|
+
def _sanitized(self) -> str:
|
96
|
+
"""Возвращает введенный номер в очищенном виде."""
|
97
|
+
return self._sanitize(self.raw_phone)
|
98
|
+
|
99
|
+
def parse(self):
|
100
|
+
"""Разбор номера."""
|
101
|
+
if self._is_parsed or not self.raw_phone:
|
102
|
+
return
|
103
|
+
|
104
|
+
raw_phone = self._sanitized if self._need_sanitize else self.raw_phone.strip()
|
105
|
+
regex_match = PHONE_REGEX.search(raw_phone)
|
106
|
+
|
107
|
+
self._is_parsed = True
|
108
|
+
|
109
|
+
if regex_match:
|
110
|
+
self.subscriber_part_1 = self._only_digits(regex_match.group(3))
|
111
|
+
self.subscriber_part_2 = self._only_digits(regex_match.group(4))
|
112
|
+
self.subscriber_part_3 = self._only_digits(regex_match.group(5))
|
113
|
+
|
114
|
+
self.region_code = regex_match.group(2)
|
115
|
+
if self.region_code:
|
116
|
+
self.region_code = self._sanitize(self.region_code)
|
117
|
+
|
118
|
+
country_code = regex_match.group(1)
|
119
|
+
|
120
|
+
if country_code in ('8', '7') or (not country_code and self.region_code):
|
121
|
+
self.country_code = RU_PHONE_CODE
|
122
|
+
else:
|
123
|
+
self.country_code = country_code
|
124
|
+
|
125
|
+
if self.country_code == RU_PHONE_CODE and (len(self.region_code) + len(self.subscriber_part_1)) != 6:
|
126
|
+
# у российских номеров код региона и первый блок абонентского номера в сумме должны быть 6 цифр
|
127
|
+
return
|
128
|
+
|
129
|
+
if self.region_code:
|
130
|
+
self._is_e164 = True
|
131
|
+
|
132
|
+
if len(self.region_code) != 3 or len(self.country_code) > 2:
|
133
|
+
cleaned_template = '{0}({1}){2}{3}{4}'
|
134
|
+
else:
|
135
|
+
cleaned_template = '{0}{1}{2}{3}{4}'
|
136
|
+
|
137
|
+
self._cleaned = cleaned_template.format(
|
138
|
+
self.country_code,
|
139
|
+
self.region_code,
|
140
|
+
self.subscriber_part_1,
|
141
|
+
self.subscriber_part_2,
|
142
|
+
self.subscriber_part_3,
|
143
|
+
)
|
144
|
+
|
145
|
+
else:
|
146
|
+
self._cleaned = '{0}{1}{2}'.format(
|
147
|
+
self.subscriber_part_1,
|
148
|
+
self.subscriber_part_2,
|
149
|
+
self.subscriber_part_3,
|
150
|
+
)
|
151
|
+
|
152
|
+
@property
|
153
|
+
def type(self) -> PhoneNumberType:
|
154
|
+
"""Тип номера."""
|
155
|
+
if self.is_valid:
|
156
|
+
if self.country_code == RU_PHONE_CODE:
|
157
|
+
return PhoneNumberType.RU_E164
|
158
|
+
|
159
|
+
elif self.country_code:
|
160
|
+
return PhoneNumberType.E164
|
161
|
+
|
162
|
+
elif not self.country_code:
|
163
|
+
return PhoneNumberType.RU_LOCAL
|
164
|
+
|
165
|
+
return PhoneNumberType.UNKNOWN
|
166
|
+
|
167
|
+
@property
|
168
|
+
def is_valid(self) -> bool:
|
169
|
+
"""Признак корректности введенного номера."""
|
170
|
+
return True if self._is_parsed and self._cleaned else False
|
171
|
+
|
172
|
+
@property
|
173
|
+
def is_e164(self):
|
174
|
+
"""Признак, что номер в международном формате."""
|
175
|
+
return self._is_e164
|
176
|
+
|
177
|
+
@property
|
178
|
+
def is_russia(self) -> bool:
|
179
|
+
"""Признак, что номер российский."""
|
180
|
+
return (
|
181
|
+
self._cleaned.startswith(RU_PHONE_CODE)
|
182
|
+
or (self.is_valid and not self.region_code)
|
183
|
+
)
|
184
|
+
|
185
|
+
@property
|
186
|
+
def cleaned(self) -> str:
|
187
|
+
"""Очищенный номер.
|
188
|
+
|
189
|
+
Используется для сохранения в БД.
|
190
|
+
"""
|
191
|
+
return self._cleaned
|
192
|
+
|
193
|
+
@property
|
194
|
+
def formatted(self) -> str:
|
195
|
+
"""Отформатированный номер для отображения пользователю."""
|
196
|
+
if not self.is_valid:
|
197
|
+
return ''
|
198
|
+
|
199
|
+
formatted_number = '{0}-{1}-{2}'.format(
|
200
|
+
self.subscriber_part_1,
|
201
|
+
self.subscriber_part_2,
|
202
|
+
self.subscriber_part_3,
|
203
|
+
)
|
204
|
+
|
205
|
+
if self._is_e164:
|
206
|
+
formatted_number = '{0} ({1}) {2}'.format(
|
207
|
+
self.country_code,
|
208
|
+
self.region_code,
|
209
|
+
formatted_number,
|
210
|
+
)
|
211
|
+
|
212
|
+
return formatted_number
|
213
|
+
|
214
|
+
def __str__(self) -> str:
|
215
|
+
"""Текстовое представление."""
|
216
|
+
if self.is_valid:
|
217
|
+
return self.formatted
|
218
|
+
else:
|
219
|
+
return self.raw_phone
|
220
|
+
|
221
|
+
def __repr__(self) -> str:
|
222
|
+
"""Представление объекта."""
|
223
|
+
if self.is_valid or not self.raw_phone:
|
224
|
+
return '<{0}: {1}>'.format(type(self).__name__, str(self) or "''")
|
225
|
+
|
226
|
+
else:
|
227
|
+
return '<Invalid {0}: raw_phone="{1}">'.format(type(self).__name__, self.raw_phone)
|
228
|
+
|
229
|
+
def __len__(self):
|
230
|
+
return len(self.cleaned)
|
231
|
+
|
232
|
+
def __bool__(self):
|
233
|
+
return bool(self.cleaned)
|
234
|
+
|
235
|
+
def __hash__(self):
|
236
|
+
return hash(self.cleaned)
|
237
|
+
|
238
|
+
def __eq__(self, other_phone) -> bool:
|
239
|
+
if isinstance(other_phone, str):
|
240
|
+
other_phone = PhoneNumber(other_phone)
|
241
|
+
|
242
|
+
elif not isinstance(other_phone, PhoneNumber):
|
243
|
+
return False
|
244
|
+
|
245
|
+
self_str = self.formatted if self.is_valid else self.raw_phone
|
246
|
+
other_str = other_phone.formatted if other_phone.is_valid else other_phone.raw_phone
|
247
|
+
|
248
|
+
return self_str == other_str
|
249
|
+
|
250
|
+
def __add__(self, other: str) -> str:
|
251
|
+
"""Конкатенация со строкой."""
|
252
|
+
return str(self) + other
|
253
|
+
|
254
|
+
def __radd__(self, other: str) -> str:
|
255
|
+
"""Конкатенация со строкой."""
|
256
|
+
return other + str(self)
|
257
|
+
|
258
|
+
|
259
|
+
def to_python(value: Any) -> Union[PhoneNumber, str, None]:
|
260
|
+
"""Преобразование значения к объекту номера телефона."""
|
261
|
+
if value in (None, ''):
|
262
|
+
phone_number = value
|
263
|
+
|
264
|
+
elif isinstance(value, str):
|
265
|
+
phone_number = PhoneNumber(value)
|
266
|
+
|
267
|
+
elif isinstance(value, PhoneNumber):
|
268
|
+
phone_number = value
|
269
|
+
|
270
|
+
else:
|
271
|
+
raise TypeError(f'Невозможно преобразовать {type(value).__name__} к PhoneNumber.')
|
272
|
+
|
273
|
+
return phone_number
|
@@ -0,0 +1,92 @@
|
|
1
|
+
from typing import (
|
2
|
+
Union,
|
3
|
+
)
|
4
|
+
|
5
|
+
from django.core.exceptions import (
|
6
|
+
ValidationError,
|
7
|
+
)
|
8
|
+
|
9
|
+
from educommon.utils.phone_number.phone_number import (
|
10
|
+
PhoneNumber,
|
11
|
+
to_python,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
def validate_common_phone_number(phone_number: Union[str, PhoneNumber]):
|
16
|
+
"""Валидация номера телефона в общем формате."""
|
17
|
+
phone_number = to_python(phone_number)
|
18
|
+
|
19
|
+
if isinstance(phone_number, PhoneNumber) and not phone_number.is_valid:
|
20
|
+
raise ValidationError(
|
21
|
+
'Неверный формат! Примеры допустимых форматов: '
|
22
|
+
'+XXX (XXX) XXX XX XX, 8 (XXXXX) X-XX-XX, XXX-XX-XX',
|
23
|
+
code='invalid',
|
24
|
+
)
|
25
|
+
|
26
|
+
|
27
|
+
def validate_e164_phone_number(phone_number: Union[str, PhoneNumber]):
|
28
|
+
"""Валидация номера телефона в международном формате."""
|
29
|
+
phone_number = to_python(phone_number)
|
30
|
+
|
31
|
+
if (
|
32
|
+
isinstance(phone_number, PhoneNumber)
|
33
|
+
and not phone_number.is_valid
|
34
|
+
or not phone_number.is_e164
|
35
|
+
):
|
36
|
+
raise ValidationError(
|
37
|
+
'Неверный формат! Примеры допустимых форматов: '
|
38
|
+
'+XXX (XXX) XXX XX XX, +7 (XXX) XXX-XX-XX, 8 (XXXXX) X-XX-XX',
|
39
|
+
code='invalid',
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
def validate_ru_phone_number(phone_number: Union[str, PhoneNumber]):
|
44
|
+
"""Валидация российского номера телефона в общем формате."""
|
45
|
+
phone_number = to_python(phone_number)
|
46
|
+
|
47
|
+
if (
|
48
|
+
isinstance(phone_number, PhoneNumber)
|
49
|
+
and not phone_number.is_valid
|
50
|
+
or not phone_number.is_russia
|
51
|
+
):
|
52
|
+
raise ValidationError(
|
53
|
+
'Неверный формат! Примеры допустимых форматов: '
|
54
|
+
'+7 (XXX) XXX-XX-XX, 8 (XXXXX) X-XX-XX, XXX-XX-XX',
|
55
|
+
code='invalid',
|
56
|
+
)
|
57
|
+
|
58
|
+
|
59
|
+
def validate_ru_e164_phone_number(phone_number: Union[str, PhoneNumber]):
|
60
|
+
"""Валидация российского номера телефона в международном формате."""
|
61
|
+
phone_number = to_python(phone_number)
|
62
|
+
|
63
|
+
if (
|
64
|
+
isinstance(phone_number, PhoneNumber)
|
65
|
+
and not phone_number.is_valid
|
66
|
+
or not (phone_number.is_russia and phone_number.is_e164)
|
67
|
+
):
|
68
|
+
raise ValidationError(
|
69
|
+
'Неверный формат! Примеры допустимых форматов: '
|
70
|
+
'+7 (XXX) XXX-XX-XX, 8 (XXXXX) X-XX-XX, (XXX) XXX-XX-XX',
|
71
|
+
code='invalid',
|
72
|
+
)
|
73
|
+
|
74
|
+
|
75
|
+
def validate_ru_mobile_phone_number(phone_number: Union[str, PhoneNumber]):
|
76
|
+
"""Валидация российского мобильного номера телефона."""
|
77
|
+
phone_number = to_python(phone_number)
|
78
|
+
|
79
|
+
if (
|
80
|
+
isinstance(phone_number, PhoneNumber)
|
81
|
+
and not phone_number.is_valid
|
82
|
+
or not (
|
83
|
+
phone_number.region_code
|
84
|
+
and len(phone_number.region_code) == 3
|
85
|
+
and phone_number.region_code.startswith('9')
|
86
|
+
)
|
87
|
+
):
|
88
|
+
raise ValidationError(
|
89
|
+
'Неверный формат! Примеры допустимых форматов: '
|
90
|
+
'+7 (9XX) XXX-XX-XX, 7 (9XX) XXX XX XX, 89XXXXXXXXX',
|
91
|
+
code='invalid',
|
92
|
+
)
|
educommon/version.conf
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# нормальной установки обновлений.
|
5
5
|
|
6
6
|
[version]
|
7
|
-
BRANCH = tags/3.
|
8
|
-
VERSION = 3.
|
9
|
-
REVISION =
|
10
|
-
VERSION_DATE =
|
11
|
-
REVISION_DATE =
|
7
|
+
BRANCH = tags/3.11.0
|
8
|
+
VERSION = 3.11.0
|
9
|
+
REVISION = f55e33a2f910df4cd73e441a843b681a481919bc
|
10
|
+
VERSION_DATE = 25.09.2024
|
11
|
+
REVISION_DATE = 25.09.2024
|
@@ -1,6 +1,6 @@
|
|
1
1
|
educommon/__init__.py,sha256=fvsBDL7g8HgOTd-JHOh7TSvMcnUauvGVgPuyA2Z9hUI,419
|
2
2
|
educommon/thread_data.py,sha256=n0XtdesP9H92O3rJ8K6fVnJLiHqyJEfh2xpuT36wzxs,61
|
3
|
-
educommon/version.conf,sha256=
|
3
|
+
educommon/version.conf,sha256=wgh9j5tHQoztDcRaVnISzOv7b8mQS40XZNuU8O4qFR8,450
|
4
4
|
educommon/about/README.rst,sha256=U48UW5jv-8qHyaV56atzzkNMvzHKXVcWSb_NR06PnMo,2685
|
5
5
|
educommon/about/__init__.py,sha256=H1W0IgW-qX9LCZ49GOJzHdmQGHhh-MA6U1xmNx7WnfM,132
|
6
6
|
educommon/about/apps.py,sha256=GrpJAOE2sF0ukWsqugP_WJS88DO4aL-T3kTLprrJrcA,259
|
@@ -116,9 +116,9 @@ educommon/auth/simple_auth/templates/simple_auth/login_page.html,sha256=BN0rx5mS
|
|
116
116
|
educommon/auth/simple_auth/templates/simple_auth/reset_password_page.html,sha256=MRg0QfF6LMfpRxZUphhI5BO1ENrCKjMmDW3tkvCcFIQ,1340
|
117
117
|
educommon/auth/simple_auth/templates/simple_auth/email/reset_password.html,sha256=GuqE56sItB1Ar5vA5-jFkDnUlZbRK0cWDk2eWtDWcbg,635
|
118
118
|
educommon/contingent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
119
|
-
educommon/contingent/actions.py,sha256=
|
119
|
+
educommon/contingent/actions.py,sha256=zcKKUvpWJrm32q0q96oILqVIWMaqaO8T3_sbu0Z6KO0,3233
|
120
120
|
educommon/contingent/app_meta.py,sha256=4VE-PqRFLs8vLkaowIHbDyVb_9dvCScml3WvZ24KDws,273
|
121
|
-
educommon/contingent/base.py,sha256=
|
121
|
+
educommon/contingent/base.py,sha256=pGpYqTuJ9XyKpK7PKZZAngURlb7-YQx-wCZKAhYhYQA,6854
|
122
122
|
educommon/contingent/catalogs.py,sha256=OafO1jDi7q7wm_7cnu8k-xiUc6mwKhXE36HklJX2Sb4,61449
|
123
123
|
educommon/contingent/contingent_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
124
124
|
educommon/contingent/contingent_plugin/actions.py,sha256=SXjFCDGyFHrOZNcroEouRLZB7NqFSs7M_1Gw89bfe48,717
|
@@ -134,10 +134,10 @@ educommon/contingent/contingent_plugin/migrations/0002_add_contingent_model_dele
|
|
134
134
|
educommon/contingent/contingent_plugin/migrations/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
135
135
|
educommon/contingent/json_data/icao.json,sha256=4XEN5fCjczAvs4sGZFeZJmGMdxk4wcfWZBxjAi2ECwk,20078
|
136
136
|
educommon/contingent/json_data/okogu.json,sha256=1yK8VWxivX5VeFQzkNSUWqHabu6RBy4VJW0VnOIlEsw,53540
|
137
|
-
educommon/contingent/json_data/oksm.json,sha256=
|
137
|
+
educommon/contingent/json_data/oksm.json,sha256=kZIQPcdWhwtHKJGgfO79gCYtk6dEe2iAz3Lj5L_AZrU,41859
|
138
138
|
educommon/django/__init__.py,sha256=0Fe7dZFIVdw18JIpvl9P7q0GoqKN7ySr5D1rA9YaAzk,69
|
139
139
|
educommon/django/db/__init__.py,sha256=bkN8tB5vTllcTXYt8ERHn1wgUQCFXNUmZi068u1jBfU,73
|
140
|
-
educommon/django/db/fields.py,sha256=
|
140
|
+
educommon/django/db/fields.py,sha256=a_CCuDthg9Gv_T3Ngm93Iz6kTFNo1LJMnk2-PFKohsM,7141
|
141
141
|
educommon/django/db/models.py,sha256=mNtrj3MZYCXJJ2alw5TaIiN_fFjsCUrm8OCeuZ9isWI,2107
|
142
142
|
educommon/django/db/observer.py,sha256=oAh0Uu0fwlGZ4pj1ytKZyAhosbWKs4THGBTVyR4NYmA,10990
|
143
143
|
educommon/django/db/routers.py,sha256=Zx8cIf7ZQpQLVhxutNj31_IQqYS6J3MnuBTOA5m43SQ,3268
|
@@ -305,6 +305,11 @@ educommon/utils/fonts/Tahoma.ttf,sha256=bGmcQotghnnHrebZwP92ZkobWSdMOUzwq6DuLDP4
|
|
305
305
|
educommon/utils/fonts/__init__.py,sha256=KfGNPOWk7up_p62UKt0lZIuMpMryXYo0qoqI5_Yp7Zc,4815
|
306
306
|
educommon/utils/licence/__init__.py,sha256=qr2XwwmrewMNM2y_bHaLq2Q20I8I66wfxtvrBABnpFU,5095
|
307
307
|
educommon/utils/licence/converters.py,sha256=BLXjqy4kgBj78qqfiXtuz0Nwu5rVb_us3omNJkObTtA,1174
|
308
|
+
educommon/utils/phone_number/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
309
|
+
educommon/utils/phone_number/enums.py,sha256=EqTCTeCxYioKco07fkG25hq6lae63o0WZIuo-6fdNRg,1420
|
310
|
+
educommon/utils/phone_number/modelfields.py,sha256=gIdDYSf7a30m85JTdmXVxOv0j-IYMUntweZngS7sV6I,2938
|
311
|
+
educommon/utils/phone_number/phone_number.py,sha256=80dzAMiJ1rDWFRx2GsC1PTtg9GgXxdvty2-7XLhZTik,9969
|
312
|
+
educommon/utils/phone_number/validators.py,sha256=QFMWGbatinNBjdMNYsMfNSO7VPARMdW4BFtIM_kOoxI,3290
|
308
313
|
educommon/utils/system_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
309
314
|
educommon/utils/system_app/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
310
315
|
educommon/utils/system_app/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -344,8 +349,8 @@ educommon/ws_log/smev/exceptions.py,sha256=lmy7o2T3dJkqgIhG07qyh5yPqO3qZAYABuT4J
|
|
344
349
|
educommon/ws_log/templates/report/smev_logs.xlsx,sha256=nnYgB0Z_ix8HoxsRICjsZfFRQBdra-5Gd8nWhCxTjYg,10439
|
345
350
|
educommon/ws_log/templates/ui-js/smev-logs-list-window.js,sha256=AGup3D8GTJSY9WdDPj0zBJeYQBFOmGgcbxPOJbKK-nY,513
|
346
351
|
educommon/ws_log/templates/ui-js/smev-logs-report-setting-window.js,sha256=nQ7QYK9frJcE7g7kIt6INg9TlEEJAPPayBJgRaoTePA,1103
|
347
|
-
educommon-3.
|
348
|
-
educommon-3.
|
349
|
-
educommon-3.
|
350
|
-
educommon-3.
|
351
|
-
educommon-3.
|
352
|
+
educommon-3.11.0.dist-info/METADATA,sha256=B8t2OQ9Hzeu07wf-yJbwyDJKcwUpLAVW-ktXa9tXOz8,1563
|
353
|
+
educommon-3.11.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
354
|
+
educommon-3.11.0.dist-info/dependency_links.txt,sha256=RNlr4t-BxZRm7e_IfVo1ikr5ln-7viimzLHvQMO1C_Q,43
|
355
|
+
educommon-3.11.0.dist-info/top_level.txt,sha256=z5fbW7bz_0V1foUm_FGcZ9_MTpW3N1dBN7-kEmMowl4,10
|
356
|
+
educommon-3.11.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|