Finish the module: do the polishing, tests and docs.
This commit is contained in:
parent
e65608dc38
commit
599a9b5a83
|
@ -1,7 +1,10 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the full
|
# The COPYRIGHT file at the top level of this repository contains the full
|
||||||
# copyright notices and license terms.
|
# copyright notices and license terms.
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
|
from .party import *
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
Pool.register(
|
Pool.register(
|
||||||
|
Address,
|
||||||
|
CountryZip,
|
||||||
module='party_zip', type_='model')
|
module='party_zip', type_='model')
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
Party Zip Module
|
||||||
|
################
|
||||||
|
|
||||||
|
The Party Zip module adds the country zip field to addresses as well as menu
|
||||||
|
entries to Country Zip and Subdivision models.
|
||||||
|
|
||||||
|
If installed, the module will make zip and city fields in addresses readonly
|
||||||
|
so they can only be filled in with the new country zip field. This allows all
|
||||||
|
addresses to have a zip/city that is previously created in the database.
|
||||||
|
|
||||||
|
The module also takes care of updating all addresses if zip, city, subdivision
|
||||||
|
or country fields are changed in the zip record.
|
138
party.py
138
party.py
|
@ -1,33 +1,123 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the full
|
# The COPYRIGHT file at the top level of this repository contains the full
|
||||||
# copyright notices and license terms.
|
# copyright notices and license terms.
|
||||||
from trytond.model import ModelSQL, ModelView
|
from trytond.model import fields
|
||||||
from trytond.pool import PoolMeta
|
from trytond.pool import PoolMeta, Pool
|
||||||
|
from trytond.pyson import Eval, If, Bool
|
||||||
|
|
||||||
__all__ = ['Address']
|
__all__ = ['Address', 'CountryZip']
|
||||||
__metaclass__ = PoolMeta
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Create a view that adds 'country_zip' field and hides the following
|
|
||||||
# fields in the form view:
|
|
||||||
#
|
|
||||||
# - zip
|
|
||||||
# - city
|
|
||||||
# - country
|
|
||||||
# - subdivision
|
|
||||||
#
|
|
||||||
# (I think it is better to hide rather than replace so that other inheriting
|
|
||||||
# modules will still be compatible)
|
|
||||||
#
|
|
||||||
# For tree view I think it is better to keep existing fields
|
|
||||||
|
|
||||||
|
|
||||||
class Address:
|
class Address:
|
||||||
__name__ = 'party.address'
|
__name__ = 'party.address'
|
||||||
country_zip = fields.Many2One('country.zip', 'Location')
|
__metaclass__ = PoolMeta
|
||||||
|
country_zip = fields.Many2One('country.zip', 'Location',
|
||||||
|
ondelete='RESTRICT', domain=[
|
||||||
|
If(Bool(Eval('country')), ('country', '=', Eval('country', -1)),
|
||||||
|
()),
|
||||||
|
If(Bool(Eval('subdivision')),
|
||||||
|
('subdivision', '=', Eval('subdivision', -1)), ()),
|
||||||
|
], depends=['country', 'subdivision'])
|
||||||
|
|
||||||
# TODO: Given that I think there are some issues if we try to redefine those
|
@classmethod
|
||||||
# fields as Function ones, we could consider storing their value in the
|
def __setup__(cls):
|
||||||
# database on write. The problem we need to consider is what happens if a field
|
super(Address, cls).__setup__()
|
||||||
# from country.zip is changed. Should we 'simply' propagate the new values to
|
cls.zip.readonly = True
|
||||||
# existing addresses?
|
cls.city.readonly = True
|
||||||
|
cls.country.states['readonly'] |= Bool(Eval('country_zip'))
|
||||||
|
cls.subdivision.states['readonly'] |= Bool(Eval('country_zip'))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_zip_values(CountryZip, values):
|
||||||
|
values = values.copy()
|
||||||
|
if 'country_zip' in values:
|
||||||
|
if values['country_zip']:
|
||||||
|
country_zip, = CountryZip.search([
|
||||||
|
('id', '=', values['country_zip']),
|
||||||
|
], limit=1)
|
||||||
|
values['zip'] = country_zip.zip
|
||||||
|
values['city'] = country_zip.city
|
||||||
|
values['country'] = country_zip.country.id
|
||||||
|
values['subdivision'] = (country_zip.subdivision.id if
|
||||||
|
country_zip.subdivision else None)
|
||||||
|
else:
|
||||||
|
values['zip'] = None
|
||||||
|
values['city'] = None
|
||||||
|
return values
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, vlist):
|
||||||
|
CountryZip = Pool().get('country.zip')
|
||||||
|
new_vlist = []
|
||||||
|
for values in vlist:
|
||||||
|
new_vlist.append(cls.update_zip_values(CountryZip, values))
|
||||||
|
super(Address, cls).create(new_vlist)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def write(cls, *args):
|
||||||
|
CountryZip = Pool().get('country.zip')
|
||||||
|
actions = iter(args)
|
||||||
|
new_args = []
|
||||||
|
for addresses, values in zip(actions, actions):
|
||||||
|
new_args.append(addresses)
|
||||||
|
new_args.append(cls.update_zip_values(CountryZip, values))
|
||||||
|
super(Address, cls).write(*new_args)
|
||||||
|
|
||||||
|
@fields.depends('country_zip')
|
||||||
|
def on_change_country_zip(self):
|
||||||
|
if self.country_zip:
|
||||||
|
self.zip = self.country_zip.zip
|
||||||
|
self.city = self.country_zip.city
|
||||||
|
self.country = self.country_zip.country
|
||||||
|
self.subdivision = self.country_zip.subdivision
|
||||||
|
else:
|
||||||
|
self.zip = None
|
||||||
|
self.city = None
|
||||||
|
|
||||||
|
|
||||||
|
class CountryZip:
|
||||||
|
__name__ = 'country.zip'
|
||||||
|
__metaclass__ = PoolMeta
|
||||||
|
|
||||||
|
def get_rec_name(self, name):
|
||||||
|
res = []
|
||||||
|
if self.zip:
|
||||||
|
res.append(self.zip)
|
||||||
|
if self.city:
|
||||||
|
res.append(self.city)
|
||||||
|
res = [' '.join(res)]
|
||||||
|
if self.subdivision:
|
||||||
|
res.append(self.subdivision.rec_name)
|
||||||
|
res = [' - '.join(res)]
|
||||||
|
res.append('(%s)' % self.country.rec_name)
|
||||||
|
return ' '.join(res)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def search_rec_name(cls, name, clause):
|
||||||
|
return ['OR',
|
||||||
|
[('zip',) + tuple(clause[1:])],
|
||||||
|
[('city',) + tuple(clause[1:])],
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def write(cls, *args):
|
||||||
|
Address = Pool().get('party.address')
|
||||||
|
|
||||||
|
super(CountryZip, cls).write(*args)
|
||||||
|
|
||||||
|
actions = iter(args)
|
||||||
|
fields = set(['zip', 'city', 'country', 'subdivision'])
|
||||||
|
to_update = []
|
||||||
|
for zips, values in zip(actions, actions):
|
||||||
|
intersec = set(values.keys()) & fields
|
||||||
|
if not intersec:
|
||||||
|
continue
|
||||||
|
addresses = Address.search([
|
||||||
|
('country_zip', 'in', [x.id for x in zips]),
|
||||||
|
])
|
||||||
|
to_update.append(addresses)
|
||||||
|
address_values = {}
|
||||||
|
for field in intersec:
|
||||||
|
address_values[field] = values[field]
|
||||||
|
to_update.append(address_values)
|
||||||
|
|
||||||
|
Address.write(*to_update)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<tryton>
|
||||||
|
<data>
|
||||||
|
<record model="ir.ui.view" id="address_view_form">
|
||||||
|
<field name="model">party.address</field>
|
||||||
|
<field name="inherit" ref="party.address_view_form"/>
|
||||||
|
<field name="name">address_form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.action.act_window" id="act_subdivision_form">
|
||||||
|
<field name="name">Subdivisions</field>
|
||||||
|
<field name="res_model">country.subdivision</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.act_window.view" id="act_subdivision_form_view1">
|
||||||
|
<field name="sequence" eval="10"/>
|
||||||
|
<field name="view" ref="country.subdivision_view_tree"/>
|
||||||
|
<field name="act_window" ref="act_subdivision_form"/>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.act_window.view" id="act_subdivision_form_view2">
|
||||||
|
<field name="sequence" eval="20"/>
|
||||||
|
<field name="view" ref="country.subdivision_view_form"/>
|
||||||
|
<field name="act_window" ref="act_subdivision_form"/>
|
||||||
|
</record>
|
||||||
|
<menuitem parent="country.menu_country_form" action="act_subdivision_form"
|
||||||
|
id="menu_subdivision_form" string="Subdivisions"/>
|
||||||
|
|
||||||
|
<record model="ir.action.act_window" id="act_subdivision_form2">
|
||||||
|
<field name="name">Subdivisions</field>
|
||||||
|
<field name="res_model">country.subdivision</field>
|
||||||
|
<field name="domain"
|
||||||
|
eval="[('country', 'in', Eval('active_ids'))]" pyson="1"/>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.act_window.view" id="act_subdivision_form_view2_1">
|
||||||
|
<field name="sequence" eval="10"/>
|
||||||
|
<field name="view" ref="country.subdivision_view_tree"/>
|
||||||
|
<field name="act_window" ref="act_subdivision_form2"/>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.act_window.view" id="act_subdivision_form_view2_2">
|
||||||
|
<field name="sequence" eval="20"/>
|
||||||
|
<field name="view" ref="country.subdivision_view_form"/>
|
||||||
|
<field name="act_window" ref="act_subdivision_form2"/>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.keyword" id="act_subdivision_form_keyword1">
|
||||||
|
<field name="keyword">form_relate</field>
|
||||||
|
<field name="model">country.country,-1</field>
|
||||||
|
<field name="action" ref="act_subdivision_form2"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.action.act_window" id="act_zip_form">
|
||||||
|
<field name="name">Zips</field>
|
||||||
|
<field name="res_model">country.zip</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.act_window.view" id="act_zip_form_view1">
|
||||||
|
<field name="sequence" eval="10"/>
|
||||||
|
<field name="view" ref="country.zip_view_list"/>
|
||||||
|
<field name="act_window" ref="act_zip_form"/>
|
||||||
|
</record>
|
||||||
|
<record model="ir.action.act_window.view" id="act_zip_form_view2">
|
||||||
|
<field name="sequence" eval="20"/>
|
||||||
|
<field name="view" ref="country.zip_view_form"/>
|
||||||
|
<field name="act_window" ref="act_zip_form"/>
|
||||||
|
</record>
|
||||||
|
<menuitem parent="country.menu_country_form" action="act_zip_form"
|
||||||
|
id="menu_zip_form"/>
|
||||||
|
</data>
|
||||||
|
</tryton>
|
|
@ -1,33 +1,92 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the full
|
# The COPYRIGHT file at the top level of this repository contains the full
|
||||||
# copyright notices and license terms.
|
# copyright notices and license terms.
|
||||||
import unittest
|
import unittest
|
||||||
# import doctest
|
|
||||||
import trytond.tests.test_tryton
|
import trytond.tests.test_tryton
|
||||||
from trytond.tests.test_tryton import test_view, test_depends
|
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||||
# TODO: Remove if no sceneario needed.
|
from trytond.pool import Pool
|
||||||
# from trytond.tests.test_tryton import doctest_setup, doctest_teardown
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
class PartyZipTestCase(ModuleTestCase):
|
||||||
'Test module'
|
'Test PartyZip module'
|
||||||
|
module = 'party_zip'
|
||||||
|
|
||||||
def setUp(self):
|
@with_transaction()
|
||||||
trytond.tests.test_tryton.install_module('party_zip')
|
def test_address(self):
|
||||||
|
'Create address'
|
||||||
|
pool = Pool()
|
||||||
|
Party = pool.get('party.party')
|
||||||
|
Address = pool.get('party.address')
|
||||||
|
Country = pool.get('country.country')
|
||||||
|
Subdivision = pool.get('country.subdivision')
|
||||||
|
Zip = pool.get('country.zip')
|
||||||
|
|
||||||
def test0005views(self):
|
country1, country2 = Country.create([{
|
||||||
'Test views'
|
'name': 'Country 1',
|
||||||
test_view('party_zip')
|
}, {
|
||||||
|
'name': 'Country 2',
|
||||||
|
}])
|
||||||
|
subdivision1, subdivision2 = Subdivision.create([{
|
||||||
|
'code': '1',
|
||||||
|
'name': 'Subdivision 1',
|
||||||
|
'type': 'area',
|
||||||
|
'country': country1.id,
|
||||||
|
}, {
|
||||||
|
'code': '2',
|
||||||
|
'name': 'Subdivision 2',
|
||||||
|
'type': 'area',
|
||||||
|
'country': country2.id,
|
||||||
|
}])
|
||||||
|
zip1, zip2 = Zip.create([{
|
||||||
|
'zip': 'zip1',
|
||||||
|
'city': 'city1',
|
||||||
|
'country': country1.id,
|
||||||
|
'subdivision': subdivision1.id,
|
||||||
|
}, {
|
||||||
|
'zip': 'zip2',
|
||||||
|
'city': 'city2',
|
||||||
|
'country': country2.id,
|
||||||
|
'subdivision': subdivision2.id,
|
||||||
|
}])
|
||||||
|
party1, = Party.create([{
|
||||||
|
'name': 'Party 1',
|
||||||
|
}])
|
||||||
|
address, = Address.create([{
|
||||||
|
'party': party1.id,
|
||||||
|
'street': 'St sample, 15',
|
||||||
|
'city': 'City',
|
||||||
|
}])
|
||||||
|
self.assertEqual(address.zip, None)
|
||||||
|
self.assertEqual(address.city, None)
|
||||||
|
Address.write([address, {
|
||||||
|
'country_zip': zip1,
|
||||||
|
}])
|
||||||
|
self.assertEqual(address.zip, 'zip1')
|
||||||
|
self.assertEqual(address.city, 'city1')
|
||||||
|
self.assertEqual(address.country.id, country1.id)
|
||||||
|
self.assertEqual(address.subdivision.id, subdivision1.id)
|
||||||
|
|
||||||
def test0006depends(self):
|
Address.write([address, {
|
||||||
'Test depends'
|
'country_zip': zip2,
|
||||||
test_depends()
|
}])
|
||||||
|
self.assertEqual(address.zip, 'zip2')
|
||||||
|
self.assertEqual(address.city, 'city2')
|
||||||
|
self.assertEqual(address.country.id, country2.id)
|
||||||
|
self.assertEqual(address.subdivision.id, subdivision2.id)
|
||||||
|
|
||||||
|
Zip.write([zip2, {
|
||||||
|
'zip': 'ZIP 3',
|
||||||
|
'city': 'CITY 3',
|
||||||
|
'country': country1.id,
|
||||||
|
'subdivision': subdivision1.id,
|
||||||
|
}])
|
||||||
|
address, = Address.browse([address.id])
|
||||||
|
self.assertEqual(address.zip, 'ZIP3')
|
||||||
|
self.assertEqual(address.city, 'CITY 3')
|
||||||
|
self.assertEqual(address.country.id, country1.id)
|
||||||
|
self.assertEqual(address.subdivision.id, subdivision1.id)
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
suite = trytond.tests.test_tryton.suite()
|
suite = trytond.tests.test_tryton.suite()
|
||||||
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCase))
|
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(PartyZipTestCase))
|
||||||
# TODO: remove if no scenario needed.
|
|
||||||
#suite.addTests(doctest.DocFileSuite('scenario_party_zip.rst',
|
|
||||||
# setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
|
|
||||||
# optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
|
|
||||||
return suite
|
return suite
|
||||||
|
|
|
@ -4,3 +4,4 @@ depends:
|
||||||
country
|
country
|
||||||
party
|
party
|
||||||
xml:
|
xml:
|
||||||
|
party.xml
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||||
|
this repository contains the full copyright notices and license terms. -->
|
||||||
|
<data>
|
||||||
|
<xpath expr="/form/label[@name='zip']" position="before">
|
||||||
|
<label name="country_zip"/>
|
||||||
|
<field name="country_zip" colspan="3"/>
|
||||||
|
<newline/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
Loading…
Reference in New Issue