You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

phonenumber.py 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. # This module is based on django-phone-number-field
  2. # https://github.com/stefanfoulis/django-phonenumber-field
  3. #
  4. # Here is the relevant copyright and permissions notice.
  5. #
  6. # Copyright (c) 2011 Stefan Foulis and contributors.
  7. #
  8. # Permission is hereby granted, free of charge, to any person
  9. # obtaining a copy of this software and associated documentation
  10. # files (the "Software"), to deal in the Software without
  11. # restriction, including without limitation the rights to use,
  12. # copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. # copies of the Software, and to permit persons to whom the
  14. # Software is furnished to do so, subject to the following
  15. # conditions:
  16. #
  17. # The above copyright notice and this permission notice shall be
  18. # included in all copies or substantial portions of the Software.
  19. #
  20. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  22. # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  24. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  25. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  26. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  27. # OTHER DEALINGS IN THE SOFTWARE.
  28. import six
  29. from django.core import validators
  30. from django.conf import settings
  31. from django.core.exceptions import ValidationError
  32. from django.utils.translation import ugettext_lazy as _
  33. from django.utils.encoding import python_2_unicode_compatible
  34. import phonenumbers
  35. @python_2_unicode_compatible
  36. class PhoneNumber(phonenumbers.phonenumber.PhoneNumber):
  37. """
  38. A extended version of phonenumbers.phonenumber.PhoneNumber that provides
  39. some neat and more pythonic, easy to access methods. This makes using a
  40. PhoneNumber instance much easier, especially in templates and such.
  41. """
  42. format_map = {
  43. 'E164': phonenumbers.PhoneNumberFormat.E164,
  44. 'INTERNATIONAL': phonenumbers.PhoneNumberFormat.INTERNATIONAL,
  45. 'NATIONAL': phonenumbers.PhoneNumberFormat.NATIONAL,
  46. 'RFC3966': phonenumbers.PhoneNumberFormat.RFC3966,
  47. }
  48. @classmethod
  49. def from_string(cls, phone_number, region=None):
  50. phone_number_obj = cls()
  51. if region is None:
  52. region = getattr(settings, 'PHONENUMBER_DEFAULT_REGION', None)
  53. phonenumbers.parse(number=phone_number, region=region,
  54. keep_raw_input=True, numobj=phone_number_obj)
  55. return phone_number_obj
  56. def __str__(self):
  57. format_string = getattr(
  58. settings, 'PHONENUMBER_DEFAULT_FORMAT', 'INTERNATIONAL')
  59. fmt = self.format_map[format_string]
  60. if self.is_valid():
  61. return self.format_as(fmt)
  62. return self.raw_input
  63. def is_valid(self):
  64. """
  65. checks whether the number supplied is actually valid
  66. """
  67. return phonenumbers.is_valid_number(self)
  68. def format_as(self, format):
  69. if self.is_valid():
  70. return phonenumbers.format_number(self, format)
  71. else:
  72. return self.raw_input
  73. @property
  74. def as_international(self):
  75. return self.format_as(phonenumbers.PhoneNumberFormat.INTERNATIONAL)
  76. @property
  77. def as_e164(self):
  78. return self.format_as(phonenumbers.PhoneNumberFormat.E164)
  79. @property
  80. def as_national(self):
  81. return self.format_as(phonenumbers.PhoneNumberFormat.NATIONAL)
  82. @property
  83. def as_rfc3966(self):
  84. return self.format_as(phonenumbers.PhoneNumberFormat.RFC3966)
  85. def __len__(self):
  86. return len(six.text_type(self))
  87. def __eq__(self, other):
  88. if type(other) == PhoneNumber:
  89. return self.as_e164 == other.as_e164
  90. else:
  91. return super(PhoneNumber, self).__eq__(other)
  92. def to_python(value):
  93. if value in validators.EMPTY_VALUES: # None or ''
  94. phone_number = None
  95. elif value and isinstance(value, six.string_types):
  96. try:
  97. phone_number = PhoneNumber.from_string(phone_number=value)
  98. except phonenumbers.phonenumberutil.NumberParseException:
  99. # the string provided is not a valid PhoneNumber.
  100. phone_number = PhoneNumber(raw_input=value)
  101. elif isinstance(value, PhoneNumber):
  102. phone_number = value
  103. return phone_number
  104. class PhoneNumberDescriptor(object):
  105. """
  106. The descriptor for the phone number attribute on the model instance.
  107. Returns a PhoneNumber when accessed so you can do stuff like::
  108. >>> instance.phone_number.as_international
  109. Assigns a phone number object on assignment so you can do::
  110. >>> instance.phone_number = PhoneNumber(...)
  111. or
  112. >>> instance.phone_number = '+414204242'
  113. """
  114. def __init__(self, field):
  115. self.field = field
  116. def __get__(self, instance=None, owner=None):
  117. if instance is None:
  118. raise AttributeError(
  119. "The '%s' attribute can only be accessed from %s instances."
  120. % (self.field.name, owner.__name__))
  121. return instance.__dict__[self.field.name]
  122. def __set__(self, instance, value):
  123. instance.__dict__[self.field.name] = to_python(value)
  124. def validate_international_phonenumber(value):
  125. phone_number = to_python(value)
  126. if phone_number and not phone_number.is_valid():
  127. raise ValidationError(_(u'The phone number entered is not valid.'))