summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorqdp-odoo <qdp@odoo.com>2016-12-28 11:26:06 +0100
committerqdp-odoo <qdp@odoo.com>2016-12-28 11:26:06 +0100
commit0dc4e13f145a449395aa82cfe75dfbe09afb5455 (patch)
tree84438d6c8d9d98f8b54a7937baab43ed4222ab51 /addons
parent4dd27bba346e50f40769d1412fbca0b5f65bb5a6 (diff)
[ADD] base_address_extended: add module to take care of multiple fields inside the 'street' fields defined in base
Diffstat (limited to 'addons')
-rw-r--r--addons/base_address_extended/__init__.py4
-rw-r--r--addons/base_address_extended/__manifest__.py22
-rw-r--r--addons/base_address_extended/models/__init__.py4
-rw-r--r--addons/base_address_extended/models/base_address_extended.py140
-rw-r--r--addons/base_address_extended/tests/__init__.py4
-rw-r--r--addons/base_address_extended/tests/test_street_fields.py55
-rw-r--r--addons/base_address_extended/views/base_address_extended.xml54
7 files changed, 283 insertions, 0 deletions
diff --git a/addons/base_address_extended/__init__.py b/addons/base_address_extended/__init__.py
new file mode 100644
index 00000000000..81fc81d29c9
--- /dev/null
+++ b/addons/base_address_extended/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import models
diff --git a/addons/base_address_extended/__manifest__.py b/addons/base_address_extended/__manifest__.py
new file mode 100644
index 00000000000..c9c78846b26
--- /dev/null
+++ b/addons/base_address_extended/__manifest__.py
@@ -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'],
+}
diff --git a/addons/base_address_extended/models/__init__.py b/addons/base_address_extended/models/__init__.py
new file mode 100644
index 00000000000..0e2d5d905fa
--- /dev/null
+++ b/addons/base_address_extended/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import base_address_extended
diff --git a/addons/base_address_extended/models/base_address_extended.py b/addons/base_address_extended/models/base_address_extended.py
new file mode 100644
index 00000000000..8e599c08402
--- /dev/null
+++ b/addons/base_address_extended/models/base_address_extended.py
@@ -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
diff --git a/addons/base_address_extended/tests/__init__.py b/addons/base_address_extended/tests/__init__.py
new file mode 100644
index 00000000000..72bcdae08e5
--- /dev/null
+++ b/addons/base_address_extended/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import test_street_fields
diff --git a/addons/base_address_extended/tests/test_street_fields.py b/addons/base_address_extended/tests/test_street_fields.py
new file mode 100644
index 00000000000..d5817070ffd
--- /dev/null
+++ b/addons/base_address_extended/tests/test_street_fields.py
@@ -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')
diff --git a/addons/base_address_extended/views/base_address_extended.xml b/addons/base_address_extended/views/base_address_extended.xml
new file mode 100644
index 00000000000..9f6ab4be442
--- /dev/null
+++ b/addons/base_address_extended/views/base_address_extended.xml
@@ -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>