[ADD] base_address_extended: add module to take care of multiple fields inside the 'street' fields defined in base

This commit is contained in:
qdp-odoo
2016-12-28 11:26:06 +01:00
parent 4dd27bba34
commit 0dc4e13f14
7 changed files with 283 additions and 0 deletions
+4
View File
@@ -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>