[ADD] base_address_extended: add module to take care of multiple fields inside the 'street' fields defined in base
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import models
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': 'Extended Addresses',
|
||||
'summary': 'Add extra fields on addresses',
|
||||
'sequence': '19',
|
||||
'category': 'Base',
|
||||
'complexity': 'easy',
|
||||
'description': """
|
||||
Extended Addresses Management
|
||||
=============================
|
||||
|
||||
This module holds all extra fields one may need to manage accurately addresses.
|
||||
|
||||
For example, in legal reports, some countries need to split the street into several fields,
|
||||
with the street name, the house number, and room number.
|
||||
""",
|
||||
'data': [
|
||||
'views/base_address_extended.xml'
|
||||
],
|
||||
'depends': ['base'],
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base_address_extended
|
||||
@@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
STREET_FIELDS = ('street_name', 'street_number', 'street_number2')
|
||||
|
||||
|
||||
class ResCountry(models.Model):
|
||||
_inherit = 'res.country'
|
||||
|
||||
street_format = fields.Text(
|
||||
help="Format to use for streets belonging to this country.\n\n"
|
||||
"You can use the python-style string pattern with all the fields of the street "
|
||||
"(for example, use '%(street_name)s, %(street_number)s' if you want to display "
|
||||
"the street name, followed by a comma and the house number)"
|
||||
"\n%(street_name)s: the name of the street"
|
||||
"\n%(street_number)s: the house number"
|
||||
"\n%(street_number2)s: the door number",
|
||||
default='%(street_number)s/%(street_number2)s %(street_name)s', required=True)
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = ['res.partner']
|
||||
_name = 'res.partner'
|
||||
|
||||
street_name = fields.Char('Street Name', compute='_split_street',
|
||||
inverse='_set_street', store=True)
|
||||
street_number = fields.Char('House Number', compute='_split_street',
|
||||
inverse='_set_street', store=True)
|
||||
street_number2 = fields.Char('Door Number', compute='_split_street',
|
||||
inverse='_set_street', store=True)
|
||||
|
||||
def get_street_fields(self):
|
||||
"""Returns the fields that can be used in a street format.
|
||||
Overwrite this function if you want to add your own fields."""
|
||||
return STREET_FIELDS
|
||||
|
||||
@api.multi
|
||||
def _set_street(self):
|
||||
"""Updates the street field.
|
||||
Writes the `street` field on the partners when one of the sub-fields in STREET_FIELDS
|
||||
has been touched"""
|
||||
street_fields = self.get_street_fields()
|
||||
for partner in self:
|
||||
street_format = (partner.country_id.street_format or
|
||||
'%(street_number)s/%(street_number2)s %(street_name)s')
|
||||
previous_field = None
|
||||
previous_pos = 0
|
||||
street_value = ""
|
||||
separator = ""
|
||||
# iter on fields in street_format, detected as '%(<field_name>)s'
|
||||
for re_match in re.finditer(r'%\(\w+\)s', street_format):
|
||||
# [2:-2] is used to remove the extra chars '%(' and ')s'
|
||||
field_name = re_match.group()[2:-2]
|
||||
field_pos = re_match.start()
|
||||
if field_name not in street_fields:
|
||||
raise UserError(_("Unrecognized field %s in street format.") % field_name)
|
||||
if not previous_field:
|
||||
# first iteration: add heading chars in street_format
|
||||
if partner[field_name]:
|
||||
street_value += street_format[0:field_pos] + partner[field_name]
|
||||
else:
|
||||
# get the substring between 2 fields, to be used as separator
|
||||
separator = street_format[previous_pos:field_pos]
|
||||
if street_value and partner[field_name]:
|
||||
street_value += separator
|
||||
if partner[field_name]:
|
||||
street_value += partner[field_name]
|
||||
previous_field = field_name
|
||||
previous_pos = re_match.end()
|
||||
|
||||
# add trailing chars in street_format
|
||||
street_value += street_format[previous_pos:]
|
||||
|
||||
# /!\ Note that we must use a sql query to bypass the orm as it would call _split_street()
|
||||
# that would try to set the fields we just modified.
|
||||
self._cr.execute('UPDATE res_partner SET street = %s WHERE ID = %s', (street_value, partner.id))
|
||||
#invalidate the cache for the field we manually set
|
||||
self.invalidate_cache(['street'], [partner.id])
|
||||
|
||||
@api.multi
|
||||
@api.depends('street')
|
||||
def _split_street(self):
|
||||
"""Splits street value into sub-fields.
|
||||
Recomputes the fields of STREET_FIELDS when `street` of a partner is updated"""
|
||||
street_fields = self.get_street_fields()
|
||||
for partner in self:
|
||||
if not partner.street:
|
||||
partner.street_name = ''
|
||||
partner.street_number = ''
|
||||
partner.street_number2 = ''
|
||||
continue
|
||||
|
||||
street_format = (partner.country_id.street_format or
|
||||
'%(street_number)s/%(street_number2)s %(street_name)s')
|
||||
vals = {}
|
||||
previous_pos = 0
|
||||
street_raw = partner.street
|
||||
field_name = None
|
||||
# iter on fields in street_format, detected as '%(<field_name>)s'
|
||||
for re_match in re.finditer(r'%\(\w+\)s', street_format):
|
||||
field_pos = re_match.start()
|
||||
if not field_name:
|
||||
#first iteration: remove the heading chars
|
||||
street_raw = street_raw[field_pos:]
|
||||
|
||||
# get the substring between 2 fields, to be used as separator
|
||||
separator = street_format[previous_pos:field_pos]
|
||||
field_value = None
|
||||
if separator and field_name:
|
||||
#maxsplit set to 1 to unpack only the first element and let the rest untouched
|
||||
tmp = street_raw.split(separator, 1)
|
||||
if len(tmp) == 2:
|
||||
field_value, street_raw = tmp
|
||||
vals[field_name] = field_value
|
||||
if field_value or not field_name:
|
||||
# select next field to find (first pass OR field found)
|
||||
# [2:-2] is used to remove the extra chars '%(' and ')s'
|
||||
field_name = re_match.group()[2:-2]
|
||||
else:
|
||||
# value not found: keep looking for the same field
|
||||
pass
|
||||
if field_name not in street_fields:
|
||||
raise UserError(_("Unrecognized field %s in street format.") % field_name)
|
||||
previous_pos = re_match.end()
|
||||
|
||||
# last field value is what remains in street_raw minus trailing chars in street_format
|
||||
trailing_chars = street_format[previous_pos:]
|
||||
if trailing_chars and street_raw.endswith(trailing_chars):
|
||||
vals[field_name] = street_raw[:-len(trailing_chars)]
|
||||
else:
|
||||
vals[field_name] = street_raw
|
||||
# assign the values to the fields
|
||||
# /!\ Note that a write(vals) would cause a recursion since it would bypass the cache
|
||||
for k, v in vals.items():
|
||||
partner[k] = v
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import test_street_fields
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestStreetFields(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestStreetFields, self).setUp()
|
||||
self.Partner = self.env['res.partner']
|
||||
self.env.ref('base.be').write({'street_format': '%(street_name)s, %(street_number)s/%(street_number2)s'})
|
||||
self.env.ref('base.us').write({'street_format': '%(street_number)s/%(street_number2)s %(street_name)s'})
|
||||
self.env.ref('base.ch').write({'street_format': 'header %(street_name)s, %(street_number)s - %(street_number2)s trailer'})
|
||||
|
||||
def create_and_assert(self, partner_name, country_id, street, street_name, street_number, street_number2):
|
||||
partner = self.Partner.create({'name': partner_name + '-1', 'street': street, 'country_id': country_id})
|
||||
self.assertEqual(partner.street_name or '', street_name, 'wrong street name for %s: %s' % (partner_name, partner.street_name))
|
||||
self.assertEqual(partner.street_number or '', street_number, 'wrong house number for %s: %s' % (partner_name, partner.street_number))
|
||||
self.assertEqual(partner.street_number2 or '', street_number2, 'wrong door number for %s: %s' % (partner_name, partner.street_number2))
|
||||
partner = self.Partner.create({
|
||||
'name': partner_name + '-2',
|
||||
'street_name': street_name,
|
||||
'street_number': street_number,
|
||||
'street_number2': street_number2,
|
||||
'country_id': country_id,
|
||||
})
|
||||
self.assertEqual(partner.street or '', street, 'wrong street for %s: %s' % (partner_name, partner.street))
|
||||
return partner
|
||||
|
||||
def write_and_assert(self, partner, vals, street, street_name, street_number, street_number2):
|
||||
partner.write(vals)
|
||||
self.assertEqual(partner.street_name or '', street_name, 'wrong street name: %s' % partner.street_name)
|
||||
self.assertEqual(partner.street_number or '', street_number, 'wrong house number: %s' % partner.street_number)
|
||||
self.assertEqual(partner.street_number2 or '', street_number2, 'wrong door number: %s' % partner.street_number2)
|
||||
self.assertEqual(partner.street or '', street, 'wrong street: %s' % partner.street)
|
||||
|
||||
def test_00_res_partner_name_create(self):
|
||||
self.create_and_assert('Test00', self.env.ref('base.us').id, '40/2b Chaussee de Namur', 'Chaussee de Namur', '40', '2b')
|
||||
self.create_and_assert('Test01', self.env.ref('base.us').id, '40 Chaussee de Namur', 'Chaussee de Namur', '40', '')
|
||||
self.create_and_assert('Test02', self.env.ref('base.us').id, 'Chaussee de Namur', 'de Namur', 'Chaussee', '')
|
||||
|
||||
def test_01_header_trailer(self):
|
||||
self.create_and_assert('Test10', self.env.ref('base.ch').id, 'header Chaussee de Namur, 40 - 2b trailer', 'Chaussee de Namur', '40', '2b')
|
||||
self.create_and_assert('Test11', self.env.ref('base.ch').id, 'header Chaussee de Namur, 40 trailer', 'Chaussee de Namur', '40', '')
|
||||
self.create_and_assert('Test12', self.env.ref('base.ch').id, 'header Chaussee de Namur trailer', 'Chaussee de Namur', '', '')
|
||||
|
||||
def test_02_res_partner_write(self):
|
||||
p1 = self.create_and_assert('Test20', self.env.ref('base.be').id, 'Chaussee de Namur, 40/2b', 'Chaussee de Namur', '40', '2b')
|
||||
self.write_and_assert(p1, {'street': 'Chaussee de Namur, 43'}, 'Chaussee de Namur, 43', 'Chaussee de Namur', '43', '')
|
||||
self.write_and_assert(p1, {'street': 'Chaussee de Namur'}, 'Chaussee de Namur', 'Chaussee de Namur', '', '')
|
||||
self.write_and_assert(p1, {'street_name': 'Chee de Namur', 'street_number': '40'}, 'Chee de Namur, 40', 'Chee de Namur', '40', '')
|
||||
self.write_and_assert(p1, {'street_number2': '4'}, 'Chee de Namur, 40/4', 'Chee de Namur', '40', '4')
|
||||
#we don't recompute the street fields when we change the country
|
||||
self.write_and_assert(p1, {'country_id': self.env.ref('base.us').id}, 'Chee de Namur, 40/4', 'Chee de Namur', '40', '4')
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_res_country_extended_form" model="ir.ui.view">
|
||||
<field name="name">view_res_country_extended_form</field>
|
||||
<field name="model">res.country</field>
|
||||
<field name="inherit_id" ref="base.view_country_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='address_format']" position="after">
|
||||
<field name="street_format" groups="base.group_no_one" placeholder="Street format..."/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_partner_structured_form" model="ir.ui.view">
|
||||
<field name="name">view_partner_structured_form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='street']" position="replace">
|
||||
<field name="street" invisible="1"/>
|
||||
<field name="street_name" placeholder="Street Name..."/>
|
||||
<div class="o_row">
|
||||
<field name="city" invisible="1"/>
|
||||
<label for="street_number" class="oe_edit_only"/>
|
||||
<span> </span>
|
||||
<field name="street_number"/>
|
||||
<label for="street_number2" class="oe_edit_only"/>
|
||||
<field name="street_number2"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_partner_address_structured_form" model="ir.ui.view">
|
||||
<field name="name">view_partner_address_structured_form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_address_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='street']" position="replace">
|
||||
<field name="street" invisible="1"/>
|
||||
<field name="street_name" placeholder="Street Name..."/>
|
||||
<div class="o_row">
|
||||
<label for="street_number" class="oe_edit_only"/>
|
||||
<span> </span>
|
||||
<field name="street_number"/>
|
||||
<label for="street_number2" class="oe_edit_only"/>
|
||||
<field name="street_number2"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user