Add support for PhoneNumber composites
This commit is contained in:
@@ -4,6 +4,12 @@ Changelog
|
||||
Here you can see the full list of changes between each SQLAlchemy-Utils release.
|
||||
|
||||
|
||||
0.32.3 (2016-04-20)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Added support for PhoneNumber objects as composites
|
||||
|
||||
|
||||
0.32.2 (2016-04-20)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@@ -108,6 +108,8 @@ PhoneNumberType
|
||||
|
||||
.. module:: sqlalchemy_utils.types.phone_number
|
||||
|
||||
.. autoclass:: PhoneNumber
|
||||
|
||||
.. autoclass:: PhoneNumberType
|
||||
|
||||
|
||||
|
@@ -93,4 +93,4 @@ from .types import ( # noqa
|
||||
WeekDaysType
|
||||
)
|
||||
|
||||
__version__ = '0.32.2'
|
||||
__version__ = '0.32.3'
|
||||
|
@@ -25,18 +25,46 @@ class PhoneNumber(BasePhoneNumber):
|
||||
.. _Python phonenumbers library:
|
||||
https://github.com/daviddrysdale/python-phonenumbers
|
||||
|
||||
|
||||
::
|
||||
|
||||
from sqlalchemy_utils import PhoneNumber
|
||||
|
||||
|
||||
class User(self.Base):
|
||||
__tablename__ = 'user'
|
||||
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
|
||||
name = sa.Column(sa.Unicode(255))
|
||||
_phone_number = sa.Column(sa.Unicode(20))
|
||||
country_code = sa.Column(sa.Unicode(8))
|
||||
|
||||
phonenumber = sa.orm.composite(
|
||||
PhoneNumber,
|
||||
_phone_number,
|
||||
country_code
|
||||
)
|
||||
|
||||
|
||||
user = User(phone_number=PhoneNumber('0401234567', 'FI'))
|
||||
|
||||
user.phone_number.e164 # u'+358401234567'
|
||||
user.phone_number.international # u'+358 40 1234567'
|
||||
user.phone_number.national # u'040 1234567'
|
||||
user.country_code # 'FI'
|
||||
|
||||
|
||||
:param raw_number:
|
||||
String representation of the phone number.
|
||||
:param country_code:
|
||||
Country code of the phone number.
|
||||
:param region:
|
||||
Region of the phone number.
|
||||
'''
|
||||
def __init__(self, raw_number, country_code=None):
|
||||
def __init__(self, raw_number, region=None):
|
||||
# Bail if phonenumbers is not found.
|
||||
if phonenumbers is None:
|
||||
raise ImproperlyConfigured(
|
||||
"'phonenumbers' is required to use 'PhoneNumber'")
|
||||
|
||||
self._phone_number = phonenumbers.parse(raw_number, country_code)
|
||||
self._phone_number = phonenumbers.parse(raw_number, region)
|
||||
super(PhoneNumber, self).__init__(
|
||||
country_code=self._phone_number.country_code,
|
||||
national_number=self._phone_number.national_number,
|
||||
@@ -48,6 +76,7 @@ class PhoneNumber(BasePhoneNumber):
|
||||
self._phone_number.preferred_domestic_carrier_code
|
||||
)
|
||||
)
|
||||
self.region = region
|
||||
self.national = phonenumbers.format_number(
|
||||
self._phone_number,
|
||||
phonenumbers.PhoneNumberFormat.NATIONAL
|
||||
@@ -61,6 +90,9 @@ class PhoneNumber(BasePhoneNumber):
|
||||
phonenumbers.PhoneNumberFormat.E164
|
||||
)
|
||||
|
||||
def __composite_values__(self):
|
||||
return self.national, self.region
|
||||
|
||||
def is_valid_number(self):
|
||||
return phonenumbers.is_valid_number(self._phone_number)
|
||||
|
||||
@@ -96,20 +128,20 @@ class PhoneNumberType(types.TypeDecorator, ScalarCoercible):
|
||||
def python_type(self, text):
|
||||
return self._coerce(text)
|
||||
|
||||
def __init__(self, country_code='US', max_length=20, *args, **kwargs):
|
||||
def __init__(self, region='US', max_length=20, *args, **kwargs):
|
||||
# Bail if phonenumbers is not found.
|
||||
if phonenumbers is None:
|
||||
raise ImproperlyConfigured(
|
||||
"'phonenumbers' is required to use 'PhoneNumberType'")
|
||||
|
||||
super(PhoneNumberType, self).__init__(*args, **kwargs)
|
||||
self.country_code = country_code
|
||||
self.region = region
|
||||
self.impl = types.Unicode(max_length)
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value:
|
||||
if not isinstance(value, PhoneNumber):
|
||||
value = PhoneNumber(value, country_code=self.country_code)
|
||||
value = PhoneNumber(value, region=self.region)
|
||||
|
||||
if self.STORE_FORMAT == 'e164' and value.extension:
|
||||
return '%s;ext=%s' % (value.e164, value.extension)
|
||||
@@ -120,11 +152,11 @@ class PhoneNumberType(types.TypeDecorator, ScalarCoercible):
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
if value:
|
||||
return PhoneNumber(value, self.country_code)
|
||||
return PhoneNumber(value, self.region)
|
||||
return value
|
||||
|
||||
def _coerce(self, value):
|
||||
if value and not isinstance(value, PhoneNumber):
|
||||
value = PhoneNumber(value, country_code=self.country_code)
|
||||
value = PhoneNumber(value, region=self.region)
|
||||
|
||||
return value or None
|
||||
|
@@ -148,3 +148,40 @@ class TestPhoneNumberType(object):
|
||||
user = User(phone_number='050111222')
|
||||
|
||||
assert isinstance(user.phone_number, PhoneNumber)
|
||||
|
||||
|
||||
@pytest.mark.skipif('types.phone_number.phonenumbers is None')
|
||||
class TestPhoneNumberComposite(object):
|
||||
@pytest.fixture
|
||||
def User(self, Base):
|
||||
class User(Base):
|
||||
__tablename__ = 'user'
|
||||
id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
|
||||
name = sa.Column(sa.String(255))
|
||||
_phone_number = sa.Column(sa.String(255))
|
||||
country = sa.Column(sa.String(255))
|
||||
phone_number = sa.orm.composite(
|
||||
PhoneNumber,
|
||||
_phone_number,
|
||||
country
|
||||
)
|
||||
return User
|
||||
|
||||
@pytest.fixture
|
||||
def user(self, session, User):
|
||||
user = User()
|
||||
user.name = u'Someone'
|
||||
user.phone_number = PhoneNumber('+35840111222', 'FI')
|
||||
session.add(user)
|
||||
session.commit()
|
||||
return user
|
||||
|
||||
def test_query_returns_phone_number_object(
|
||||
self,
|
||||
session,
|
||||
User,
|
||||
user
|
||||
):
|
||||
queried_user = session.query(User).first()
|
||||
assert queried_user.phone_number.national == '040 111222'
|
||||
assert queried_user.phone_number.region == 'FI'
|
||||
|
Reference in New Issue
Block a user