1# -*- coding: utf-8 -*- 2# Part of Odoo. See LICENSE file for full copyright and licensing details. 3 4import re 5 6from odoo import api, fields, models, _ 7from odoo.exceptions import UserError 8 9 10class Partner(models.Model): 11 _inherit = ['res.partner'] 12 13 street_name = fields.Char( 14 'Street Name', compute='_compute_street_data', inverse='_inverse_street_data', store=True) 15 street_number = fields.Char( 16 'House', compute='_compute_street_data', inverse='_inverse_street_data', store=True) 17 street_number2 = fields.Char( 18 'Door', compute='_compute_street_data', inverse='_inverse_street_data', store=True) 19 20 def _inverse_street_data(self): 21 """Updates the street field. 22 Writes the `street` field on the partners when one of the sub-fields in STREET_FIELDS 23 has been touched""" 24 street_fields = self._get_street_fields() 25 for partner in self: 26 street_format = (partner.country_id.street_format or 27 '%(street_number)s/%(street_number2)s %(street_name)s') 28 previous_field = None 29 previous_pos = 0 30 street_value = "" 31 separator = "" 32 # iter on fields in street_format, detected as '%(<field_name>)s' 33 for re_match in re.finditer(r'%\(\w+\)s', street_format): 34 # [2:-2] is used to remove the extra chars '%(' and ')s' 35 field_name = re_match.group()[2:-2] 36 field_pos = re_match.start() 37 if field_name not in street_fields: 38 raise UserError(_("Unrecognized field %s in street format.", field_name)) 39 if not previous_field: 40 # first iteration: add heading chars in street_format 41 if partner[field_name]: 42 street_value += street_format[0:field_pos] + partner[field_name] 43 else: 44 # get the substring between 2 fields, to be used as separator 45 separator = street_format[previous_pos:field_pos] 46 if street_value and partner[field_name]: 47 street_value += separator 48 if partner[field_name]: 49 street_value += partner[field_name] 50 previous_field = field_name 51 previous_pos = re_match.end() 52 53 # add trailing chars in street_format 54 street_value += street_format[previous_pos:] 55 partner.street = street_value 56 57 @api.depends('street') 58 def _compute_street_data(self): 59 """Splits street value into sub-fields. 60 Recomputes the fields of STREET_FIELDS when `street` of a partner is updated""" 61 street_fields = self._get_street_fields() 62 for partner in self: 63 if not partner.street: 64 for field in street_fields: 65 partner[field] = None 66 continue 67 68 street_format = (partner.country_id.street_format or 69 '%(street_number)s/%(street_number2)s %(street_name)s') 70 street_raw = partner.street 71 vals = self._split_street_with_params(street_raw, street_format) 72 # assign the values to the fields 73 for k, v in vals.items(): 74 partner[k] = v 75 for k in set(street_fields) - set(vals): 76 partner[k] = None 77 78 def _split_street_with_params(self, street_raw, street_format): 79 street_fields = self._get_street_fields() 80 vals = {} 81 previous_pos = 0 82 field_name = None 83 # iter on fields in street_format, detected as '%(<field_name>)s' 84 for re_match in re.finditer(r'%\(\w+\)s', street_format): 85 field_pos = re_match.start() 86 if not field_name: 87 #first iteration: remove the heading chars 88 street_raw = street_raw[field_pos:] 89 90 # get the substring between 2 fields, to be used as separator 91 separator = street_format[previous_pos:field_pos] 92 field_value = None 93 if separator and field_name: 94 #maxsplit set to 1 to unpack only the first element and let the rest untouched 95 tmp = street_raw.split(separator, 1) 96 if previous_greedy in vals: 97 # attach part before space to preceding greedy field 98 append_previous, sep, tmp[0] = tmp[0].rpartition(' ') 99 street_raw = separator.join(tmp) 100 vals[previous_greedy] += sep + append_previous 101 if len(tmp) == 2: 102 field_value, street_raw = tmp 103 vals[field_name] = field_value 104 if field_value or not field_name: 105 previous_greedy = None 106 if field_name == 'street_name' and separator == ' ': 107 previous_greedy = field_name 108 # select next field to find (first pass OR field found) 109 # [2:-2] is used to remove the extra chars '%(' and ')s' 110 field_name = re_match.group()[2:-2] 111 else: 112 # value not found: keep looking for the same field 113 pass 114 if field_name not in street_fields: 115 raise UserError(_("Unrecognized field %s in street format.", field_name)) 116 previous_pos = re_match.end() 117 118 # last field value is what remains in street_raw minus trailing chars in street_format 119 trailing_chars = street_format[previous_pos:] 120 if trailing_chars and street_raw.endswith(trailing_chars): 121 vals[field_name] = street_raw[:-len(trailing_chars)] 122 else: 123 vals[field_name] = street_raw 124 return vals 125 126 def write(self, vals): 127 res = super(Partner, self).write(vals) 128 if 'country_id' in vals and 'street' not in vals: 129 self._inverse_street_data() 130 return res 131 132 def _formatting_address_fields(self): 133 """Returns the list of address fields usable to format addresses.""" 134 return super(Partner, self)._formatting_address_fields() + self._get_street_fields() 135 136 def _get_street_fields(self): 137 """Returns the fields that can be used in a street format. 138 Overwrite this function if you want to add your own fields.""" 139 return ['street_name', 'street_number', 'street_number2'] 140