Format phone numbers
It format for human the value of contact mechanisms and add a compact value format for search. issue4329 review26831002
This commit is contained in:
1 parent
7d4ef33559
commit
8971db3194
5 files changed
+130
-10
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add format to phone numbers
|
||||
* Rename VAT Code into Tax Identifier
|
||||
* Add sequence order on Identifier
|
||||
* Add Replace Party wizard
|
||||
|
||||
@@ -8,6 +8,7 @@ Prerequisites
|
||||
* trytond (http://www.tryton.org/)
|
||||
* trytond_country (http://www.tryton.org/)
|
||||
* python-stdnum (http://arthurdejong.org/python-stdnum/)
|
||||
* Optional: phonenumbers (http://pypi.python.org/pypi/phonenumbers)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
+89
-9
@@ -1,5 +1,11 @@
|
||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
try:
|
||||
import phonenumbers
|
||||
from phonenumbers import PhoneNumberFormat, NumberParseException
|
||||
except ImportError:
|
||||
phonenumbers = None
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, fields, sequence_ordered
|
||||
from trytond.pyson import Eval
|
||||
from trytond import backend
|
||||
@@ -34,7 +40,8 @@ class ContactMechanism(sequence_ordered(), ModelSQL, ModelView):
|
||||
sort=False, depends=DEPENDS)
|
||||
value = fields.Char('Value', select=True, states=STATES, depends=DEPENDS
|
||||
# Add all function fields to ensure to always fill them via on_change
|
||||
+ ['email', 'website', 'skype', 'sip', 'other_value'])
|
||||
+ ['email', 'website', 'skype', 'sip', 'other_value', 'value_compact'])
|
||||
value_compact = fields.Char('Value Compact', readonly=True)
|
||||
comment = fields.Text('Comment', states=STATES, depends=DEPENDS)
|
||||
party = fields.Many2One('party.party', 'Party', required=True,
|
||||
ondelete='CASCADE', states=STATES, select=True, depends=DEPENDS)
|
||||
@@ -83,6 +90,8 @@ class ContactMechanism(sequence_ordered(), ModelSQL, ModelView):
|
||||
cls._error_messages.update({
|
||||
'write_party': ('You can not modify the party of contact '
|
||||
'mechanism "%s".'),
|
||||
'invalid_phonenumber': ('The phone number "%(phone)s" of '
|
||||
'party "%(party)s" is not valid .'),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@@ -125,13 +134,39 @@ class ContactMechanism(sequence_ordered(), ModelSQL, ModelView):
|
||||
return 'fax:%s' % value
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def format_value(cls, value=None, type_=None):
|
||||
if phonenumbers and type_ == 'phone':
|
||||
try:
|
||||
phonenumber = phonenumbers.parse(value)
|
||||
except NumberParseException:
|
||||
pass
|
||||
else:
|
||||
value = phonenumbers.format_number(
|
||||
phonenumber, PhoneNumberFormat.INTERNATIONAL)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def format_value_compact(cls, value=None, type_=None):
|
||||
if phonenumbers and type_ == 'phone':
|
||||
try:
|
||||
phonenumber = phonenumbers.parse(value)
|
||||
except NumberParseException:
|
||||
pass
|
||||
else:
|
||||
value = phonenumbers.format_number(
|
||||
phonenumber, PhoneNumberFormat.E164)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def set_value(cls, mechanisms, name, value):
|
||||
# Setting value is done by on_changes
|
||||
pass
|
||||
|
||||
def _change_value(self, value):
|
||||
self.value = value
|
||||
def _change_value(self, value, type_):
|
||||
self.value = self.format_value(value=value, type_=type_)
|
||||
self.value_compact = self.format_value_compact(
|
||||
value=value, type_=type_)
|
||||
self.website = value
|
||||
self.email = value
|
||||
self.skype = value
|
||||
@@ -145,27 +180,53 @@ class ContactMechanism(sequence_ordered(), ModelSQL, ModelView):
|
||||
|
||||
@fields.depends('value', 'type')
|
||||
def on_change_value(self):
|
||||
return self._change_value(self.value)
|
||||
return self._change_value(self.value, self.type)
|
||||
|
||||
@fields.depends('website', 'type')
|
||||
def on_change_website(self):
|
||||
return self._change_value(self.website)
|
||||
return self._change_value(self.website, self.type)
|
||||
|
||||
@fields.depends('email', 'type')
|
||||
def on_change_email(self):
|
||||
return self._change_value(self.email)
|
||||
return self._change_value(self.email, self.type)
|
||||
|
||||
@fields.depends('skype', 'type')
|
||||
def on_change_skype(self):
|
||||
return self._change_value(self.skype)
|
||||
return self._change_value(self.skype, self.type)
|
||||
|
||||
@fields.depends('sip', 'type')
|
||||
def on_change_sip(self):
|
||||
return self._change_value(self.sip)
|
||||
return self._change_value(self.sip, self.type)
|
||||
|
||||
@fields.depends('other_value', 'type')
|
||||
def on_change_other_value(self):
|
||||
return self._change_value(self.other_value)
|
||||
return self._change_value(self.other_value, self.type)
|
||||
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
return ['OR',
|
||||
('value',) + tuple(clause[1:]),
|
||||
('value_compact',) + tuple(clause[1:]),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _format_values(cls, mechanisms):
|
||||
for mechanism in mechanisms:
|
||||
value = mechanism.format_value(
|
||||
value=mechanism.value, type_=mechanism.type)
|
||||
if value != mechanism.value:
|
||||
mechanism.value = value
|
||||
value_compact = mechanism.format_value_compact(
|
||||
value=mechanism.value, type_=mechanism.type)
|
||||
if value_compact != mechanism.value_compact:
|
||||
mechanism.value_compact = value_compact
|
||||
cls.save(mechanisms)
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
mechanisms = super(ContactMechanism, cls).create(vlist)
|
||||
cls._format_values(mechanisms)
|
||||
return mechanisms
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
@@ -177,3 +238,22 @@ class ContactMechanism(sequence_ordered(), ModelSQL, ModelView):
|
||||
cls.raise_user_error(
|
||||
'write_party', (mechanism.rec_name,))
|
||||
super(ContactMechanism, cls).write(*args)
|
||||
cls._format_values(mechanisms)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, mechanisms):
|
||||
super(ContactMechanism, cls).validate(mechanisms)
|
||||
for mechanism in mechanisms:
|
||||
mechanism.check_valid_phonenumber()
|
||||
|
||||
def check_valid_phonenumber(self):
|
||||
if not phonenumbers:
|
||||
return
|
||||
try:
|
||||
phonenumbers.parse(self.value)
|
||||
except NumberParseException:
|
||||
self.raise_user_error(
|
||||
'invalid_phonenumber', {
|
||||
'phone': self.value,
|
||||
'party': self.party.rec_name
|
||||
})
|
||||
@@ -53,7 +53,7 @@ for dep in info.get('depends', []):
|
||||
requires.append(get_require_version('trytond_%s' % dep))
|
||||
requires.append(get_require_version('trytond'))
|
||||
|
||||
tests_require = [get_require_version('proteus')]
|
||||
tests_require = [get_require_version('proteus'), 'phonenumbers']
|
||||
dependency_links = []
|
||||
if minor_version % 2:
|
||||
# Add development index for testing with proteus
|
||||
@@ -114,6 +114,7 @@ setup(name=name,
|
||||
install_requires=requires,
|
||||
extras_require={
|
||||
'VAT': ['python-stdnum'],
|
||||
'phonenumbers': ['phonenumbers'],
|
||||
},
|
||||
dependency_links=dependency_links,
|
||||
zip_safe=False,
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
import unittest
|
||||
import doctest
|
||||
try:
|
||||
import phonenumbers
|
||||
except ImportError:
|
||||
phonenumbers = None
|
||||
|
||||
import trytond.tests.test_tryton
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
@@ -121,6 +125,39 @@ class PartyTestCase(ModuleTestCase):
|
||||
code = party2.code
|
||||
self.assertEqual(party2.rec_name, '[' + code + ']')
|
||||
|
||||
@unittest.skipIf(phonenumbers is None, 'requires phonenumbers')
|
||||
@with_transaction()
|
||||
def test_phone_number_format(self):
|
||||
'Test phone number format'
|
||||
pool = Pool()
|
||||
Party = pool.get('party.party')
|
||||
ContactMechanism = pool.get('party.contact_mechanism')
|
||||
|
||||
party1, = Party.create([{
|
||||
'name': 'Party 1',
|
||||
}])
|
||||
mechanism, = ContactMechanism.create([{
|
||||
'party': party1.id,
|
||||
'type': 'phone',
|
||||
'value': '+442083661177',
|
||||
}])
|
||||
|
||||
# Test format on create
|
||||
self.assertEqual(mechanism.value, '+44 20 8366 1177')
|
||||
self.assertEqual(mechanism.value_compact, '+442083661177')
|
||||
|
||||
# Test format on write
|
||||
mechanism.value = '+442083661178'
|
||||
mechanism.save()
|
||||
self.assertEqual(mechanism.value, '+44 20 8366 1178')
|
||||
self.assertEqual(mechanism.value_compact, '+442083661178')
|
||||
|
||||
ContactMechanism.write([mechanism], {
|
||||
'value': '+442083661179',
|
||||
})
|
||||
self.assertEqual(mechanism.value, '+44 20 8366 1179')
|
||||
self.assertEqual(mechanism.value_compact, '+442083661179')
|
||||
|
||||
|
||||
def suite():
|
||||
suite = trytond.tests.test_tryton.suite()
|
||||
|
||||
Reference in new issue
Block a user