From b83d7055a9244e92960ac852ea5291329e475e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 23 Apr 2026 09:18:59 +0200 Subject: [PATCH] Guess the type of contact mechanism Closes #14768 --- modules/party/CHANGELOG | 1 + modules/party/contact_mechanism.py | 36 ++++++++++++++++++- ..._party_contact_mechanism_notifications.rst | 2 +- modules/party/view/contact_mechanism_form.xml | 4 +-- modules/party/view/contact_mechanism_tree.xml | 2 +- .../view/contact_mechanism_tree_sequence.xml | 2 +- 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/modules/party/CHANGELOG b/modules/party/CHANGELOG index 3fa8a73b42..ce925ce916 100644 --- a/modules/party/CHANGELOG +++ b/modules/party/CHANGELOG @@ -1,3 +1,4 @@ +* Guess the type of contact mechanism Version 8.0.0 - 2026-04-20 -------------------------- diff --git a/modules/party/contact_mechanism.py b/modules/party/contact_mechanism.py index d0329048dd..6b5f41fd2c 100644 --- a/modules/party/contact_mechanism.py +++ b/modules/party/contact_mechanism.py @@ -1,5 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. + +import re from itertools import chain try: @@ -39,6 +41,8 @@ _PHONE_TYPES = { 'fax', } +_URL_REGEX = re.compile(r'^(http|ftp)s?://', re.IGNORECASE) + class _ContactMechanismMixin: __slots__ = () @@ -140,7 +144,7 @@ class ContactMechanism( @staticmethod def default_type(): - return 'phone' + return 'other' @classmethod def default_party(cls): @@ -219,9 +223,39 @@ class ContactMechanism( # Setting value is done by on_changes pass + def _guess_types(self, value, type_): + types = [] + if type_ in {'skype', 'sip', 'irc', 'jabber'}: + return types + if phonenumbers: + if phonenumber := self._parse_phonenumber(value): + phonenumber_type = phonenumbers.number_type(phonenumber) + if phonenumber_type in { + phonenumbers.PhoneNumberType.FIXED_LINE}: + types.append('phone') + elif phonenumber_type in { + phonenumbers.PhoneNumberType.MOBILE}: + types.append('mobile') + else: + types.extend(_PHONE_TYPES) + try: + validate_email(value, check_deliverability=False) + except EmailNotValidError: + pass + else: + types.append('email') + if re.match(_URL_REGEX, value): + types.append('website') + return types + @fields.depends( methods=['on_change_with_url', 'format_value', 'format_value_compact']) def _change_value(self, value, type_): + if value: + guess_types = self._guess_types(value, type_) + if guess_types and type_ not in guess_types: + self.type = type_ = guess_types[0] + self.value = self.format_value(value=value, type_=type_) self.value_compact = self.format_value_compact( value=value, type_=type_) diff --git a/modules/party/tests/scenario_party_contact_mechanism_notifications.rst b/modules/party/tests/scenario_party_contact_mechanism_notifications.rst index 5c50bb6dea..15a8af45c1 100644 --- a/modules/party/tests/scenario_party_contact_mechanism_notifications.rst +++ b/modules/party/tests/scenario_party_contact_mechanism_notifications.rst @@ -42,7 +42,7 @@ Check notifications:: Change contact mechanism type:: - >>> contact_mechanism.type = 'other' + >>> contact_mechanism.type = 'jabber' >>> contact_mechanism.value = "test@example.com" Check notifications:: diff --git a/modules/party/view/contact_mechanism_form.xml b/modules/party/view/contact_mechanism_form.xml index 178fc024e9..461c7cb24a 100644 --- a/modules/party/view/contact_mechanism_form.xml +++ b/modules/party/view/contact_mechanism_form.xml @@ -12,8 +12,6 @@ this repository contains the full copyright notices and license terms. -->