first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

Binary file not shown.

Binary file not shown.

500
modules/country/country.py Normal file
View File

@@ -0,0 +1,500 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
import operator
from sql import Literal
from sql.conditionals import Coalesce
from sql.functions import Position, Substring
from trytond.model import DeactivableMixin, ModelSQL, ModelView, fields, tree
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.tools import is_full_text, lstrip_wildcard
from trytond.transaction import Transaction
class Organization(ModelSQL, ModelView):
__name__ = 'country.organization'
name = fields.Char("Name", required=True, translate=True)
code = fields.Char("Code")
members = fields.One2Many(
'country.organization.member', 'organization', "Members",
filter=[
('active', 'in', [True, False]),
])
countries = fields.Many2Many(
'country.organization.member', 'organization', 'country', "Countries",
readonly=True)
class OrganizationMember(ModelSQL, ModelView):
__name__ = 'country.organization.member'
organization = fields.Many2One(
'country.organization', "Organization", required=True)
country = fields.Many2One(
'country.country', "Country", required=True)
from_date = fields.Date(
"From Date",
domain=[
If(Eval('from_date') & Eval('to_date'),
('from_date', '<=', Eval('to_date')),
()),
])
to_date = fields.Date(
"To Date",
domain=[
If(Eval('from_date') & Eval('to_date'),
('to_date', '>=', Eval('from_date')),
()),
])
active = fields.Function(fields.Boolean("Active"), 'on_change_with_active')
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('country', None))
cls._order.insert(1, ('from_date', 'ASC NULLS FIRST'))
cls.__access__.add('organization')
@classmethod
def default_active(cls):
return True
@fields.depends('from_date', 'to_date')
def on_change_with_active(self, name=None):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
date = context.get('date', Date.today())
from_date = self.from_date or dt.date.min
to_date = self.to_date or dt.date.max
return from_date <= date <= to_date
@classmethod
def domain_active(cls, domain, tables):
pool = Pool()
Date = pool.get('ir.date')
context = Transaction().context
table, _ = tables[None]
_, operator, operand = domain
date = context.get('date', Date.today())
from_date = Coalesce(table.from_date, dt.date.min)
to_date = Coalesce(table.to_date, dt.date.max)
expression = (from_date <= date) & (to_date >= date)
if operator in {'=', '!='}:
if (operator == '=') != operand:
expression = ~expression
elif operator in {'in', 'not in'}:
if True in operand and False not in operand:
pass
elif False in operand and True not in operand:
expression = ~expression
else:
expression = Literal(True)
else:
expression = Literal(True)
return expression
class Region(tree(), ModelSQL, ModelView):
__name__ = 'country.region'
name = fields.Char("Name", required=True, translate=True)
code_numeric = fields.Char(
"Numeric Code", size=3,
help="UN M49 region code.")
parent = fields.Many2One('country.region', "Parent")
subregions = fields.One2Many('country.region', 'parent', "Subregions")
countries = fields.One2Many(
'country.country', 'region', "Countries",
add_remove=[
('region', '=', None),
])
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('name', 'ASC'))
@classmethod
def search_rec_name(cls, name, clause):
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
code_value = clause[2]
if clause[1].endswith('like'):
code_value = lstrip_wildcard(clause[2])
return [bool_op,
('name',) + tuple(clause[1:]),
('code_numeric', clause[1], code_value) + tuple(clause[3:]),
]
class Country(DeactivableMixin, ModelSQL, ModelView):
__name__ = 'country.country'
name = fields.Char(
"Name", required=True, translate=True,
help="The main identifier of the country.")
code = fields.Char(
"Code", size=2,
help="The 2 chars ISO country code.")
code3 = fields.Char(
"3-letters Code", size=3,
help="The 3 chars ISO country code.")
code_numeric = fields.Char(
"Numeric Code",
help="The ISO numeric country code.")
flag = fields.Function(fields.Char("Flag"), 'on_change_with_flag')
region = fields.Many2One(
'country.region', "Region", ondelete='SET NULL')
subdivisions = fields.One2Many('country.subdivision',
'country', 'Subdivisions')
members = fields.One2Many(
'country.organization.member', 'country', "Members",
filter=[
('active', 'in', [True, False]),
])
organizations = fields.Many2Many(
'country.organization.member', 'country', 'organization',
"Organizations", readonly=True)
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('name', 'ASC'))
@fields.depends('code')
def on_change_with_flag(self, name=None):
if self.code:
return ''.join(map(chr, map(lambda c: 127397 + ord(c), self.code)))
def get_rec_name(self, name):
name = self.name
if self.flag:
name = ' '.join([self.flag, self.name])
return name
@classmethod
def search_rec_name(cls, name, clause):
_, operator, operand, *extra = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
code_value = operand
if operator.endswith('like') and is_full_text(operand):
code_value = lstrip_wildcard(operand)
return [bool_op,
('name', operator, operand, *extra),
('code', operator, code_value, *extra),
('code3', operator, code_value, *extra),
('code_numeric', operator, code_value, *extra),
]
def is_member(self, organization, date=None):
"""Return if the country is in the organization at the date
organization can be an XML id"""
pool = Pool()
Date = pool.get('ir.date')
ModelData = pool.get('ir.model.data')
Organization = pool.get('country.organization')
if date is None:
date = Date.today()
if isinstance(organization, str):
organization = ModelData.get_id(organization)
with Transaction().set_context(date=date):
organization = Organization(organization)
return self in organization.countries
@classmethod
def preprocess_values(cls, mode, values):
values = super().preprocess_values(mode, values)
for code in {'code', 'code3', 'code_numeric'}:
if values.get(code):
values[code] = values[code].upper()
return values
class Subdivision(DeactivableMixin, ModelSQL, ModelView):
__name__ = 'country.subdivision'
country = fields.Many2One(
'country.country', "Country", required=True,
help="The country where this subdivision is.")
name = fields.Char(
"Name", required=True, translate=True,
help="The main identifier of the subdivision.")
code = fields.Char(
"Code",
help="The ISO code of the subdivision.")
flag = fields.Function(fields.Char("Flag"), 'on_change_with_flag')
type = fields.Selection([
(None, ""),
('administration', 'Administration'),
('administrative area', 'Administrative area'),
('administrative atoll', 'Administrative atoll'),
('administrative precinct', 'Administrative precinct'),
('administrative region', 'Administrative Region'),
('administrative territory', 'Administrative Territory'),
('area', 'Area'),
('atoll', 'Atoll'),
('arctic region', 'Arctic Region'),
('autonomous city', 'Autonomous City'),
('autonomous city in north africa', 'Autonomous city in north africa'),
('autonomous commune', 'Autonomous Commune'),
('autonomous communities', 'Autonomous communities'),
('autonomous community', 'Autonomous community'),
('autonomous district', 'Autonomous District'),
('autonomous island', 'Autonomous island'),
('autonomous monastic state', 'Autonomous monastic state'),
('autonomous municipality', 'Autonomous municipality'),
('autonomous province', 'Autonomous Province'),
('autonomous region', 'Autonomous Region'),
('autonomous republic', 'Autonomous republic'),
('autonomous sector', 'Autonomous sector'),
('autonomous territory', 'Autonomous territory'),
('autonomous territorial unit', 'Autonomous territorial unit'),
('borough', 'Borough'),
('canton', 'Canton'),
('capital', 'Capital'),
('capital city', 'Capital city'),
('capital district', 'Capital District'),
('capital metropolitan city', 'Capital Metropolitan City'),
('capital territory', 'Capital Territory'),
('chain (of islands)', 'Chain (of islands)'),
('chains (of islands)', 'Chains (of islands)'),
('city', 'City'),
('city corporation', 'City corporation'),
('city municipality', 'City municipality'),
('city with county rights', 'City with county rights'),
('commune', 'Commune'),
('constitutional province', 'Constitutional province'),
('council area', 'Council area'),
('country', 'Country'),
('county', 'County'),
('decentralized regional entity', 'Decentralized regional entity'),
('department', 'Department'),
('dependency', 'Dependency'),
('development region', 'Development region'),
('district', 'District'),
('district council area', 'District council area'),
('district municipality', 'District municipality'),
('districts under republic administration',
'Districts under republic administration'),
('district with special status', 'District with special status'),
('division', 'Division'),
('economic prefecture', 'Economic Prefecture'),
('economic region', 'Economic region'),
('emirate', 'Emirate'),
('entity', 'Entity'),
('european collectivity', "European collectivity"),
('federal capital territory', 'Federal capital territory'),
('federal dependency', 'Federal Dependency'),
('federal district', 'Federal District'),
('federal entity', "Federal entity"),
('federal territories', 'Federal Territories'),
('federal territory', 'Federal Territory'),
('free municipal consortium', 'Free municipal consortium'),
('geographical entity', 'Geographical entity'),
('geographical region', 'Geographical region'),
('geographical unit', 'Geographical unit'),
('governorate', 'Governorate'),
('group of islands (20 inhabited islands)',
'Group of islands (20 inhabited islands)'),
('included for completeness', 'Included for completeness'),
('indigenous region', 'Indigenous region'),
('island', 'Island'),
('island council', 'Island council'),
('island group', 'Island group'),
('islands, groups of islands', 'Islands, groups of islands'),
('land', 'Land'),
('local council', 'Local council'),
('london borough', 'London borough'),
('metropolitan administration', 'Metropolitan administration'),
('metropolitan city', 'Metropolitan city'),
('metropolitan cities', 'Metropolitan cities'),
('metropolitan collectivity with special status',
'Metropolitan collectivity with special status'),
('metropolitan department', 'Metropolitan department'),
('metropolitan district', 'Metropolitan district'),
('metropolitan region', 'Metropolitan region'),
('municipalities', 'Municipalities'),
('municipality', 'Municipality'),
('nation', 'Nation'),
('oblast', 'Oblast'),
('outlying area', 'Outlying area'),
('overseas collectivity', 'Overseas collectivity'),
('overseas collectivity with special status',
'Overseas collectivity with special status'),
('overseas department', 'Overseas department'),
('overseas departmental collectivity',
"Overseas departmental collectivity"),
('overseas region', 'Overseas region'),
('overseas region/department', 'Overseas region/department'),
('overseas territory', 'Overseas territory'),
('overseas territorial collectivity',
'Overseas territorial collectivity'),
('overseas unique territorial collectivity',
"Overseas unique territorial collectivity"),
('pakistan administered area', 'Pakistan administered area'),
('parish', 'Parish'),
('popularate', 'Popularate'),
('popularates', 'Popularates'),
('prefecture', 'Prefecture'),
('principality', 'Principality'),
('province', 'Province'),
('quarter', 'Quarter'),
('rayon', 'Rayon'),
('region', 'Region'),
('regional council', 'Regional council'),
('regional state', 'Regional state'),
('republic', 'Republic'),
('republican city', 'Republican City'),
('rural municipality', 'Rural municipality'),
('self-governed part', 'Self-governed part'),
('special administrative city', 'Special administrative city'),
('special administrative region', 'Special administrative region'),
('special city', 'Special city'),
('special district', 'Special District'),
('special island authority', 'Special island authority'),
('special municipality', 'Special Municipality'),
('special region', 'Special Region'),
('special self-governing city', 'Special self-governing city'),
('special self-governing province', 'Special self-governing province'),
('special zone', 'Special zone'),
('state', 'State'),
('state city', "State city"),
('territorial unit', 'Territorial unit'),
('territory', 'Territory'),
('town', 'Town'),
('town council', 'Town council'),
('two-tier county', 'Two-tier county'),
('union territory', 'Union territory'),
('unitary authority', 'Unitary authority'),
('unitary authority (england)', 'Unitary authority (england)'),
('unitary authority (wales)', 'Unitary authority (wales)'),
('urban community', 'Urban community'),
('urban municipality', 'Urban municipality'),
('voivodship', 'Voivodship'),
('ward', 'Ward'),
('zone', 'zone'),
], "Type")
parent = fields.Many2One('country.subdivision', 'Parent',
domain=[
('country', '=', Eval('country', -1)),
],
help="Add subdivision below the parent.")
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('code', 'ASC'))
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
table_h = cls.__table_handler__(module_name)
# Migration from 6.2: remove type required
table_h.not_null_action('type', action='remove')
# Migration from 6.4: remove required on code
table_h.not_null_action('code', action='remove')
@fields.depends('code')
def on_change_with_flag(self, name=None):
if self.code:
return '🏴' + ''.join(map(chr, map(
lambda c: 917504 + ord(c),
self.code.replace('-', '').lower()))) + '\U000e007f'
@classmethod
def search_rec_name(cls, name, clause):
_, operator, operand, *extra = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
code_value = operand
if operator.endswith('like') and is_full_text(operand):
code_value = lstrip_wildcard(operand)
return [bool_op,
('name', operator, operand, *extra),
('code', operator, code_value, *extra),
]
@classmethod
def domain_code(cls, domain, tables):
table, _ = tables[None]
_, op, value = domain
Operator = fields.SQL_OPERATORS[op]
column = cls.code.sql_column(table)
if op.endswith('like'):
if op.endswith('ilike') and cls.code.search_unaccented:
database = Transaction().database
column = database.unaccent(column)
value = database.unaccent(value)
bool_op = operator.and_ if op.startswith('not ') else operator.or_
return bool_op(
Operator(column, value),
Operator(
Substring(column, Position('-', column) + Literal(1)),
value))
return Operator(column, value)
@classmethod
def preprocess_values(cls, mode, values):
values = super().preprocess_values(mode, values)
if values.get('code'):
values['code'] = values['code'].upper()
return values
class PostalCode(ModelSQL, ModelView):
__name__ = 'country.postal_code'
country = fields.Many2One(
'country.country', "Country", required=True, ondelete='CASCADE',
help="The country that contains the postal code.")
subdivision = fields.Many2One(
'country.subdivision', "Subdivision", ondelete='CASCADE',
domain=[('country', '=', Eval('country', -1))],
help="The subdivision where the postal code is.")
postal_code = fields.Char('Postal Code')
city = fields.Char(
"City", help="The city associated with the postal code.")
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('country', 'ASC'))
cls._order.insert(0, ('postal_code', 'ASC'))
def get_rec_name(self, name):
if self.city and self.postal_code:
return '%s (%s)' % (self.city, self.postal_code)
else:
return (self.postal_code or self.city or str(self.id))
@classmethod
def search_rec_name(cls, name, clause):
_, operator, operand, *extra = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
code_value = operand
if operator.endswith('like') and is_full_text(operand):
code_value = lstrip_wildcard(operand)
return [bool_op,
('postal_code', operator, code_value, *extra),
('city', operator, operand, *extra),
]

297
modules/country/country.xml Normal file
View File

@@ -0,0 +1,297 @@
<?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. -->
<tryton>
<data>
<record model="ir.ui.icon" id="country_icon">
<field name="name">tryton-country</field>
<field name="path">icons/tryton-country.svg</field>
</record>
<menuitem
name="Countries"
parent="ir.menu_administration"
sequence="30"
id="menu_country"
icon="tryton-country"/>
<record model="ir.ui.menu-res.group"
id="menu_country_group_admin">
<field name="menu" ref="menu_country"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.ui.view" id="organization_view_form">
<field name="model">country.organization</field>
<field name="type">form</field>
<field name="name">organization_form</field>
</record>
<record model="ir.ui.view" id="organization_view_list">
<field name="model">country.organization</field>
<field name="type">tree</field>
<field name="name">organization_list</field>
</record>
<record model="ir.action.act_window" id="act_organization_form">
<field name="name">Organizations</field>
<field name="res_model">country.organization</field>
</record>
<record model="ir.action.act_window.view" id="act_organization_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="organization_view_list"/>
<field name="act_window" ref="act_organization_form"/>
</record>
<record model="ir.action.act_window.view" id="act_organization_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="organization_view_form"/>
<field name="act_window" ref="act_organization_form"/>
</record>
<menuitem
parent="menu_country"
action="act_organization_form"
sequence="20"
id="menu_organization_form"/>
<record model="ir.model.access" id="access_organization">
<field name="model">country.organization</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_organization_admin">
<field name="model">country.organization</field>
<field name="group" ref="res.group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="organization_member_view_form">
<field name="model">country.organization.member</field>
<field name="type">form</field>
<field name="name">organization_member_form</field>
</record>
<record model="ir.ui.view" id="organization_member_view_list">
<field name="model">country.organization.member</field>
<field name="type">tree</field>
<field name="name">organization_member_list</field>
</record>
<record model="ir.ui.view" id="region_view_form">
<field name="model">country.region</field>
<field name="type">form</field>
<field name="name">region_form</field>
</record>
<record model="ir.ui.view" id="region_view_list">
<field name="model">country.region</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">region_list</field>
</record>
<record model="ir.ui.view" id="region_view_tree">
<field name="model">country.region</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="field_childs">subregions</field>
<field name="name">region_tree</field>
</record>
<record model="ir.action.act_window" id="act_region_tree">
<field name="name">Areas</field>
<field name="res_model">country.region</field>
<field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_region_tree_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="region_view_tree"/>
<field name="act_window" ref="act_region_tree"/>
</record>
<record model="ir.action.act_window.view" id="act_region_tree_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="region_view_form"/>
<field name="act_window" ref="act_region_tree"/>
</record>
<menuitem
parent="menu_country"
action="act_region_tree"
sequence="20"
id="menu_region_tree"/>
<record model="ir.action.act_window" id="act_region_form">
<field name="name">Regions</field>
<field name="res_model">country.region</field>
</record>
<record model="ir.action.act_window.view" id="act_region_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="region_view_list"/>
<field name="act_window" ref="act_region_form"/>
</record>
<record model="ir.action.act_window.view" id="act_region_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="region_view_form"/>
<field name="act_window" ref="act_region_form"/>
</record>
<menuitem
parent="menu_region_tree"
action="act_region_form"
sequence="10"
id="menu_region_form"/>
<record model="ir.model.access" id="access_region">
<field name="model">country.region</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_region_admin">
<field name="model">country.region</field>
<field name="group" ref="res.group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="country_view_form">
<field name="model">country.country</field>
<field name="type">form</field>
<field name="name">country_form</field>
</record>
<record model="ir.ui.view" id="country_view_tree">
<field name="model">country.country</field>
<field name="type">tree</field>
<field name="name">country_tree</field>
</record>
<record model="ir.action.act_window" id="act_country_form">
<field name="name">Countries</field>
<field name="res_model">country.country</field>
</record>
<record model="ir.action.act_window.view" id="act_country_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="country_view_tree"/>
<field name="act_window" ref="act_country_form"/>
</record>
<record model="ir.action.act_window.view" id="act_country_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="country_view_form"/>
<field name="act_window" ref="act_country_form"/>
</record>
<menuitem
parent="menu_country"
action="act_country_form"
sequence="10"
id="menu_country_form"/>
<record model="ir.action.act_window" id="act_country_by_region">
<field name="name">Countries by Region</field>
<field name="res_model">country.country</field>
<field
name="domain"
eval="[('region', 'child_of', Eval('active_ids', []), 'parent')]"
pyson="1"/>
</record>
<record model="ir.action.keyword" id="act_country_by_region_keyword1">
<field name="keyword">tree_open</field>
<field name="model">country.region,-1</field>
<field name="action" ref="act_country_by_region"/>
</record>
<record model="ir.model.access" id="access_country">
<field name="model">country.country</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_country_admin">
<field name="model">country.country</field>
<field name="group" ref="res.group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="subdivision_view_form">
<field name="model">country.subdivision</field>
<field name="type">form</field>
<field name="name">subdivision_form</field>
</record>
<record model="ir.ui.view" id="subdivision_view_tree">
<field name="model">country.subdivision</field>
<field name="type">tree</field>
<field name="name">subdivision_tree</field>
</record>
<record model="ir.model.access" id="access_subdivision">
<field name="model">country.subdivision</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_subdivision_admin">
<field name="model">country.subdivision</field>
<field name="group" ref="res.group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="postal_code_view_form">
<field name="model">country.postal_code</field>
<field name="type">form</field>
<field name="name">postal_code_form</field>
</record>
<record model="ir.ui.view" id="postal_code_view_list">
<field name="model">country.postal_code</field>
<field name="type">tree</field>
<field name="name">postal_code_list</field>
</record>
<record model="ir.action.act_window" id="act_postal_code_form">
<field name="name">Postal Codes</field>
<field name="res_model">country.postal_code</field>
<field name="domain"
eval="[('country', 'in', Eval('active_ids'))]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_postal_code_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="postal_code_view_list"/>
<field name="act_window" ref="act_postal_code_form"/>
</record>
<record model="ir.action.act_window.view" id="act_postal_code_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="postal_code_view_form"/>
<field name="act_window" ref="act_postal_code_form"/>
</record>
<record model="ir.action.keyword" id="act_postal_code_form_keyword1">
<field name="keyword">form_relate</field>
<field name="model">country.country,-1</field>
<field name="action" ref="act_postal_code_form"/>
</record>
<record model="ir.model.access" id="access_postal_code">
<field name="model">country.postal_code</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_postal_code_admin">
<field name="model">country.postal_code</field>
<field name="group" ref="res.group_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"/>
</svg>

After

Width:  |  Height:  |  Size: 186 B

1113
modules/country/locale/bg.po Normal file

File diff suppressed because it is too large Load Diff

1023
modules/country/locale/ca.po Normal file

File diff suppressed because it is too large Load Diff

1095
modules/country/locale/cs.po Normal file

File diff suppressed because it is too large Load Diff

1023
modules/country/locale/de.po Normal file

File diff suppressed because it is too large Load Diff

1023
modules/country/locale/es.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1111
modules/country/locale/et.po Normal file

File diff suppressed because it is too large Load Diff

1111
modules/country/locale/fa.po Normal file

File diff suppressed because it is too large Load Diff

1092
modules/country/locale/fi.po Normal file

File diff suppressed because it is too large Load Diff

1023
modules/country/locale/fr.po Normal file

File diff suppressed because it is too large Load Diff

1108
modules/country/locale/hu.po Normal file

File diff suppressed because it is too large Load Diff

1041
modules/country/locale/id.po Normal file

File diff suppressed because it is too large Load Diff

1100
modules/country/locale/it.po Normal file

File diff suppressed because it is too large Load Diff

1096
modules/country/locale/lo.po Normal file

File diff suppressed because it is too large Load Diff

1110
modules/country/locale/lt.po Normal file

File diff suppressed because it is too large Load Diff

1023
modules/country/locale/nl.po Normal file

File diff suppressed because it is too large Load Diff

1034
modules/country/locale/pl.po Normal file

File diff suppressed because it is too large Load Diff

1023
modules/country/locale/pt.po Normal file

File diff suppressed because it is too large Load Diff

1110
modules/country/locale/ro.po Normal file

File diff suppressed because it is too large Load Diff

1104
modules/country/locale/ru.po Normal file

File diff suppressed because it is too large Load Diff

1034
modules/country/locale/sl.po Normal file

File diff suppressed because it is too large Load Diff

1085
modules/country/locale/tr.po Normal file

File diff suppressed because it is too large Load Diff

1053
modules/country/locale/uk.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,101 @@
<?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. -->
<tryton>
<data grouped="1">
<record model="country.organization" id="organization_eu">
<field name="name">European Union</field>
<field name="code">EU</field>
</record>
<record model="country.organization" id="organization_benelux">
<field name="name">Benelux Union</field>
<field name="code">Benelux</field>
</record>
<record model="country.organization" id="organization_nafta">
<field name="name">North American Free Trade Agreement</field>
<field name="code">NAFTA</field>
</record>
<record model="country.organization" id="organization_mercosur">
<field name="name">Southern Common Market</field>
<field name="code">Mercosur</field>
</record>
<record model="country.organization" id="organization_can">
<field name="name">Andean Community</field>
<field name="code">CAN</field>
</record>
<record model="country.organization" id="organization_caricom">
<field name="name">Caribbean Community</field>
<field name="code">CARICOM</field>
</record>
<record model="country.organization" id="organization_apec">
<field name="name">Asia-Pacific Economic Cooperation</field>
<field name="code">APEC</field>
</record>
<record model="country.organization" id="organization_asean">
<field name="name">Association of Southeast Asian Nations</field>
<field name="code">ASEAN</field>
</record>
<record model="country.organization" id="organization_safta">
<field name="name">South Asian Free Trade Area</field>
<field name="code">SAFTA</field>
</record>
<record model="country.organization" id="organization_gcc">
<field name="name">Cooperation Council for the Arab States of the Gulf</field>
<field name="code">GCC</field>
</record>
<record model="country.organization" id="organization_cemac">
<field name="name">Economic and Monetary Community of Central Africa</field>
<field name="code">CEMAC</field>
</record>
<record model="country.organization" id="organization_eccas">
<field name="name">Economic Community of Central African States</field>
<field name="code">ECCAS</field>
</record>
<record model="country.organization" id="organization_ecowas">
<field name="name">Economic Community of West African States</field>
<field name="code">ECOWAS</field>
</record>
<record model="country.organization" id="organization_cen-sad">
<field name="name">Community of SahelSaharan States</field>
<field name="code">CEN-SAD</field>
</record>
<record model="country.organization" id="organization_comesa">
<field name="name">Common Market for Eastern and Southern Africa</field>
<field name="code">COMESA</field>
</record>
<record model="country.organization" id="organization_eac">
<field name="name">East African Community</field>
<field name="code">EAC</field>
</record>
<record model="country.organization" id="organization_igad">
<field name="name">Intergovernmental Authority on Development</field>
<field name="code">IGAD</field>
</record>
<record model="country.organization" id="organization_sadc">
<field name="name">Southern African Development Community</field>
<field name="code">SADC</field>
</record>
<record model="country.organization" id="organization_amu">
<field name="name">Arab Maghreb Union</field>
<field name="code">AMU</field>
</record>
</data>
</tryton>

206
modules/country/region.xml Normal file
View File

@@ -0,0 +1,206 @@
<?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. -->
<tryton>
<data grouped="1">
<record model="country.region" id="region_world">
<field name="name">World</field>
<field name="code_numeric">001</field>
</record>
</data>
<data grouped="1">
<record model="country.region" id="region_africa">
<field name="name">Africa</field>
<field name="code_numeric">002</field>
<field name="parent" ref="region_world"/>
</record>
<record model="country.region" id="region_antarctica">
<field name="name">Antarctica</field>
<field name="code_numeric">010</field>
<field name="parent" ref="region_world"/>
</record>
<record model="country.region" id="region_americas">
<field name="name">Americas</field>
<field name="code_numeric">019</field>
<field name="parent" ref="region_world"/>
</record>
<record model="country.region" id="region_asia">
<field name="name">Asia</field>
<field name="code_numeric">142</field>
<field name="parent" ref="region_world"/>
</record>
<record model="country.region" id="region_europe">
<field name="name">Europe</field>
<field name="code_numeric">150</field>
<field name="parent" ref="region_world"/>
</record>
<record model="country.region" id="region_oceania">
<field name="name">Oceania</field>
<field name="code_numeric">009</field>
<field name="parent" ref="region_world"/>
</record>
</data>
<data grouped="1">
<record model="country.region" id="region_northern_africa">
<field name="name">Northern Africa</field>
<field name="code_numeric">015</field>
<field name="parent" ref="region_africa"/>
</record>
<record model="country.region" id="region_sub-saharan_africa">
<field name="name">Sub-Saharan Africa</field>
<field name="code_numeric">202</field>
<field name="parent" ref="region_africa"/>
</record>
<record model="country.region" id="region_north_america">
<field name="name">North America</field>
<field name="code_numeric">003</field>
<field name="parent" ref="region_americas"/>
</record>
<record model="country.region" id="region_latin_america_caribbean">
<field name="name">Latin America and the Caribbean</field>
<field name="code_numeric">419</field>
<field name="parent" ref="region_americas"/>
</record>
<record model="country.region" id="region_eastern_asia">
<field name="name">Eastern Asia</field>
<field name="code_numeric">030</field>
<field name="parent" ref="region_asia"/>
</record>
<record model="country.region" id="region_southern_asia">
<field name="name">Southern Asia</field>
<field name="code_numeric">034</field>
<field name="parent" ref="region_asia"/>
</record>
<record model="country.region" id="region_south-eastern_asia">
<field name="name">South-eastern Asia</field>
<field name="code_numeric">035</field>
<field name="parent" ref="region_asia"/>
</record>
<record model="country.region" id="region_central_asia">
<field name="name">Central Asia</field>
<field name="code_numeric">143</field>
<field name="parent" ref="region_asia"/>
</record>
<record model="country.region" id="region_western_asia">
<field name="name">Western Asia</field>
<field name="code_numeric">145</field>
<field name="parent" ref="region_asia"/>
</record>
<record model="country.region" id="region_southern_europe">
<field name="name">Southern Europe</field>
<field name="code_numeric">039</field>
<field name="parent" ref="region_europe"/>
</record>
<record model="country.region" id="region_eastern_europe">
<field name="name">Eastern Europe</field>
<field name="code_numeric">151</field>
<field name="parent" ref="region_europe"/>
</record>
<record model="country.region" id="region_northern_europe">
<field name="name">Northern Europe</field>
<field name="code_numeric">154</field>
<field name="parent" ref="region_europe"/>
</record>
<record model="country.region" id="region_western_europe">
<field name="name">Western Europe</field>
<field name="code_numeric">155</field>
<field name="parent" ref="region_europe"/>
</record>
<record model="country.region" id="region_australia_new_zealand">
<field name="name">Australia and New Zealand</field>
<field name="code_numeric">053</field>
<field name="parent" ref="region_oceania"/>
</record>
<record model="country.region" id="region_melanesia">
<field name="name">Melanesia</field>
<field name="code_numeric">054</field>
<field name="parent" ref="region_oceania"/>
</record>
<record model="country.region" id="region_micronesia">
<field name="name">Micronesia</field>
<field name="code_numeric">057</field>
<field name="parent" ref="region_oceania"/>
</record>
<record model="country.region" id="region_polynesia">
<field name="name">Polynesia</field>
<field name="code_numeric">061</field>
<field name="parent" ref="region_oceania"/>
</record>
</data>
<data grouped="1">
<record model="country.region" id="region_western_africa">
<field name="name">Western Africa</field>
<field name="code_numeric">011</field>
<field name="parent" ref="region_sub-saharan_africa"/>
</record>
<record model="country.region" id="region_eastern_africa">
<field name="name">Eastern Africa</field>
<field name="code_numeric">014</field>
<field name="parent" ref="region_sub-saharan_africa"/>
</record>
<record model="country.region" id="region_middle_africa">
<field name="name">Middle Africa</field>
<field name="code_numeric">017</field>
<field name="parent" ref="region_sub-saharan_africa"/>
</record>
<record model="country.region" id="region_southern_africa">
<field name="name">Southern Africa</field>
<field name="code_numeric">018</field>
<field name="parent" ref="region_sub-saharan_africa"/>
</record>
<record model="country.region" id="region_northern_america">
<field name="name">Northern America</field>
<field name="code_numeric">021</field>
<field name="parent" ref="region_north_america"/>
</record>
<record model="country.region" id="region_south_america">
<field name="name">South America</field>
<field name="code_numeric">005</field>
<field name="parent" ref="region_latin_america_caribbean"/>
</record>
<record model="country.region" id="region_central_america">
<field name="name">Central America</field>
<field name="code_numeric">013</field>
<field name="parent" ref="region_latin_america_caribbean"/>
</record>
<record model="country.region" id="region_caribbean">
<field name="name">Caribbean</field>
<field name="code_numeric">029</field>
<field name="parent" ref="region_latin_america_caribbean"/>
</record>
<record model="country.region" id="region_channel_islands">
<field name="name">Channel Islands</field>
<field name="code_numeric">830</field>
<field name="parent" ref="region_northern_europe"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,565 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
import gettext
import os
import sys
import warnings
from argparse import ArgumentParser
import pycountry
try:
import argcomplete
except ImportError:
argcomplete = None
try:
from tqdm import tqdm
except ImportError:
tqdm = None
try:
from proteus import Model, config
except ImportError:
prog = os.path.basename(sys.argv[0])
sys.exit("proteus must be installed to use %s" % prog)
ORGANIZATIONS = {
# Founding members has no from date
'EU': {
'AT': [(dt.date(1995, 1, 1), None)],
'BE': [(None, None)],
'BG': [(dt.date(2007, 1, 1), None)],
'CY': [(dt.date(2004, 5, 1), None)],
'CZ': [(dt.date(2004, 5, 1), None)],
'DE': [(None, None)],
'DK': [(dt.date(1973, 1, 1), None)],
'EE': [(dt.date(2004, 5, 1), None)],
'ES': [(dt.date(1986, 1, 1), None)],
'FI': [(dt.date(1995, 1, 1), None)],
'FR': [(None, None)],
'GB': [(dt.date(1973, 1, 1), dt.date(2020, 1, 31))],
'GR': [(dt.date(1981, 1, 1), None)],
'HR': [(dt.date(2013, 7, 1), None)],
'HU': [(dt.date(2004, 5, 1), None)],
'IE': [(dt.date(1973, 1, 1), None)],
'IT': [(None, None)],
'LT': [(dt.date(2004, 5, 1), None)],
'LU': [(None, None)],
'LV': [(dt.date(2004, 5, 1), None)],
'MT': [(dt.date(2004, 5, 1), None)],
'NL': [(None, None)],
'PL': [(dt.date(2004, 5, 1), None)],
'PT': [(dt.date(1986, 1, 1), None)],
'RO': [(dt.date(2007, 1, 1), None)],
'SE': [(dt.date(1995, 1, 1), None)],
'SI': [(dt.date(2004, 5, 1), None)],
'SK': [(dt.date(2004, 5, 1), None)],
},
'Benelux': {
'BE': [(None, None)],
'LU': [(None, None)],
'NL': [(None, None)],
},
'NAFTA': {
'CA': [(None, None)],
'MX': [(None, None)],
'US': [(None, None)],
},
'Mercosur': {
'AR': [(None, None)],
'BR': [(None, None)],
'PY': [(None, None)],
'UY': [(None, None)],
'VE': [(dt.date(2012, 7, 31), dt.date(2016, 12, 2))],
},
'CAN': {
# days and months are default to covert the full year
'BO': [(None, None)],
'CL': [(dt.date(1969, 1, 1), dt.date(1976, 12, 31))],
'CO': [(None, None)],
'EC': [(None, None)],
'PE': [(None, None)],
'VE': [(dt.date(1973, 1, 1), dt.date(2006, 12, 31))],
},
'CARICOM': {
'AG': [(dt.date(1974, 7, 4), None)],
'BB': [(None, None)],
'BS': [(dt.date(1983, 7, 4), None)],
'BZ': [(dt.date(1974, 5, 1), None)],
'DM': [(dt.date(1974, 5, 1), None)],
'GD': [(dt.date(1974, 5, 1), None)],
'GY': [(None, None)],
'HT': [(dt.date(2002, 7, 2), None)],
'JM': [(None, None)],
'KN': [(dt.date(1974, 7, 26), None)],
'LC': [(dt.date(1974, 5, 1), None)],
'MS': [(dt.date(1974, 5, 1), None)],
'SR': [(dt.date(1995, 7, 4), None)],
'TT': [(None, None)],
'VC': [(dt.date(1974, 5, 1), None)],
},
'APEC': {
# days are default to covert the full month
'AU': [(None, None)],
'BN': [(None, None)],
'CA': [(None, None)],
'CL': [(dt.date(1994, 11, 1), None)],
'CN': [(dt.date(1991, 11, 1), None)],
'HK': [(dt.date(1991, 11, 1), None)],
'ID': [(None, None)],
'JP': [(None, None)],
'KR': [(None, None)],
'MX': [(dt.date(1993, 11, 1), None)],
'MY': [(None, None)],
'NZ': [(None, None)],
'PE': [(dt.date(1998, 11, 1), None)],
'PG': [(dt.date(1993, 11, 1), None)],
'PH': [(None, None)],
'RU': [(dt.date(1998, 11, 1), None)],
'SG': [(None, None)],
'TH': [(None, None)],
'TW': [(dt.date(1991, 11, 1), None)],
'US': [(None, None)],
'VN': [(dt.date(1998, 11, 1), None)],
},
'ASEAN': {
'BN': [(dt.date(1984, 1, 7), None)],
'ID': [(None, None)],
'KH': [(dt.date(1999, 4, 30), None)],
'LA': [(dt.date(1997, 7, 23), None)],
'MM': [(dt.date(1997, 7, 23), None)],
'MY': [(None, None)],
'PH': [(None, None)],
'SG': [(None, None)],
'TH': [(None, None)],
'VN': [(dt.date(1995, 7, 28), None)],
},
'SAFTA': {
'AF': [(None, None)],
'BD': [(None, None)],
'BT': [(None, None)],
'IN': [(None, None)],
'LK': [(None, None)],
'MV': [(None, None)],
'NP': [(None, None)],
'PK': [(None, None)],
},
'GCC': {
'AE': [(None, None)],
'BH': [(None, None)],
'KW': [(None, None)],
'OM': [(None, None)],
'QA': [(None, None)],
'SA': [(None, None)],
},
'CEMAC': {
'CF': [(None, None)],
'CG': [(None, None)],
'CM': [(None, None)],
'GA': [(None, None)],
'GQ': [(dt.date(1983, 12, 19), None)],
'TD': [(None, None)],
},
'ECCAS': {
'AO': [(None, None)],
'BI': [(None, None)],
'CM': [(None, None)],
'CF': [(None, None)],
'TD': [(None, None)],
'CD': [(None, None)],
'GQ': [(None, None)],
'GA': [(None, None)],
'CG': [(None, None)],
'RW': [(None, dt.date(2007, 12, 31)), (dt.date(2016, 8, 17), None)],
'ST': [(None, None)],
},
'ECOWAS': {
'BF': [(None, dt.date(2022, 1, 28))],
'BJ': [(None, None)],
'CI': [(None, None)],
'CV': [(dt.date(1977, 1, 1), None)],
'GH': [(None, None)],
'GM': [(None, None)],
'GN': [(None, dt.date(2021, 9, 8))],
'GW': [(None, None)],
'LR': [(None, None)],
'ML': [(None, dt.date(2021, 5, 30))],
'MR': [(None, dt.date(2000, 12, 1))],
'NE': [(None, None)],
'NG': [(None, None)],
'SL': [(None, None)],
'SN': [(None, None)],
'TG': [(None, None)],
},
'CEN-SAD': {
# days and months are default to covert the full year
'BF': [(None, None)],
'BJ': [(dt.date(2002, 1, 1), None)],
'CF': [(dt.date(1999, 1, 1), None)],
'CI': [(dt.date(2004, 1, 1), None)],
'CV': [(dt.date(2009, 1, 1), None)],
'DJ': [(dt.date(2000, 1, 1), None)],
'EG': [(dt.date(2001, 1, 1), None)],
'ER': [(dt.date(1999, 1, 1), None)],
'FN': [(dt.date(2007, 1, 1), None)],
'GH': [(dt.date(2005, 1, 1), None)],
'GM': [(dt.date(2000, 1, 1), None)],
'GW': [(dt.date(2004, 1, 1), None)],
'KE': [(dt.date(2007, 1, 1), None)],
'KM': [(dt.date(2007, 1, 1), None)],
'LR': [(dt.date(2004, 1, 1), None)],
'LY': [(None, None)],
'MA': [(dt.date(2001, 1, 1), None)],
'ML': [(None, None)],
'MR': [(dt.date(2007, 1, 1), None)],
'NE': [(None, None)],
'NG': [(dt.date(2001, 1, 1), None)],
'SD': [(None, None)],
'SL': [(dt.date(2005, 1, 1), None)],
'SN': [(dt.date(2000, 1, 1), None)],
'SO': [(dt.date(2001, 1, 1), None)],
'ST': [(dt.date(2007, 1, 1), None)],
'TD': [(None, None)],
'TG': [(dt.date(2002, 1, 1), None)],
'TN': [(dt.date(2001, 1, 1), None)],
},
'COMESA': {
# days and months are default to covert the full year
'AO': [(None, dt.date(2007, 1, 1))],
'BI': [(dt.date(1981, 12, 21), None)],
'CD': [(dt.date(1981, 12, 21), None)],
'DJ': [(dt.date(1981, 12, 21), None)],
'EG': [(dt.date(1999, 1, 6), None)],
'ER': [(dt.date(1994, 1, 1), None)],
'ET': [(dt.date(1981, 12, 21), None)],
'KE': [(None, None)],
'KM': [(dt.date(1981, 12, 21), None)],
'LS': [(None, dt.date(1997, 1, 1))],
'LY': [(dt.date(2005, 6, 3), None)],
'MG': [(None, None)],
'MU': [(None, None)],
'MW': [(None, None)],
'MZ': [(None, dt.date(1997, 1, 1))],
'NA': [(None, dt.date(2004, 5, 2))],
'RW': [(None, None)],
'SC': [(dt.date(2001, 1, 1), None)],
'SD': [(dt.date(1981, 12, 21), None)],
'SO': [(dt.date(2018, 7, 19), None)],
'SZ': [(dt.date(1981, 12, 21), None)],
'TN': [(dt.date(2018, 7, 18), None)],
'TZ': [(None, dt.date(2000, 9, 2))],
'UG': [(None, None)],
'ZM': [(None, None)],
'ZW': [(None, None)],
},
'EAC': {
# days and months are default to covert the full year
'BI': [(dt.date(2007, 1, 1), None)],
'CD': [(dt.date(2022, 1, 1), None)],
'KE': [(None, None)],
'RW': [(dt.date(2007, 1, 1), None)],
'SS': [(dt.date(2012, 1, 1), None)],
'TZ': [(None, None)],
'UG': [(None, None)],
},
'IGAD': {
# days and months are default to covert the full year
'DJ': [(None, None)],
'ER': [(dt.date(1993, 1, 1), dt.date(2007, 12, 31)),
(dt.date(2011, 1, 1), None)],
'ET': [(None, None)],
'KE': [(None, None)],
'SD': [(None, None)],
'SO': [(None, None)],
'SS': [(dt.date(2011, 1, 1), dt.date(2021, 12, 1))],
'UG': [(None, None)],
},
'SADC': {
'AO': [(None, None)],
'BW': [(None, None)],
'CD': [(dt.date(1997, 9, 8), None)],
'KM': [(dt.date(2017, 1, 1), None)],
'LS': [(None, None)],
'MG': [(dt.date(2005, 8, 18), dt.date(2009, 1, 26)),
(dt.date(2014, 1, 30), None)],
'MU': [(dt.date(1995, 8, 28), None)],
'MW': [(None, None)],
'MZ': [(None, None)],
'NA': [(dt.date(1990, 3, 21), None)],
'SC': [(dt.date(1997, 9, 8), dt.date(2004, 7, 1)),
(dt.date(2008, 1, 1), None)],
'SZ': [(None, None)],
'TZ': [(None, None)],
'ZA': [(dt.date(1994, 8, 30), None)],
'ZM': [(None, None)],
'ZW': [(None, None)],
},
'AMU': {
'DZ': [(None, None)],
'LY': [(None, None)],
'MA': [(None, None)],
'MR': [(None, None)],
'TN': [(None, None)],
},
}
SUBREGIONS = {
'001': ['002', '009', '010', '019', '142', '150'],
'002': ['015', '202'],
'015': ['012', '434', '504', '729', '732', '788', '818'],
'202': ['011', '014', '017', '018'],
'011': [
'132', '204', '270', '288', '324', '384', '430', '466', '478', '562',
'566', '624', '654', '686', '694', '768', '854'],
'014': [
'086', '108', '174', '175', '231', '232', '260', '262', '404', '450',
'454', '480', '508', '638', '646', '690', '706', '716', '728', '800',
'834', '894'],
'017': ['024', '120', '140', '148', '178', '180', '226', '266', '678'],
'018': ['072', '426', '516', '710', '748'],
'010': [],
'019': ['003', '419'],
'003': ['013', '021', '029'],
'021': ['060', '124', '304', '666', '840'],
'419': ['005', '013', '029'],
'005': [
'032', '068', '074', '076', '152', '170', '218', '238', '239', '254',
'328', '600', '604', '740', '858', '862'],
'013': ['084', '188', '222', '320', '340', '484', '558', '591'],
'029': [
'028', '044', '052', '092', '136', '192', '212', '214', '308', '312',
'332', '388', '474', '500', '531', '533', '534', '535', '630', '652',
'659', '660', '662', '663', '670', '780', '796', '850'],
'142': ['030', '034', '035', '143', '145'],
'030': ['156', '344', '392', '408', '410', '446', '496'] + ['158'],
'034': ['004', '050', '064', '144', '356', '364', '462', ' 524', '586'],
'035': [
'096', '104', '116', '360', '418', '458', '608', '626', '702', '704',
'764'],
'143': ['398', '417', '762', '795', '860'],
'145': [
'031', '051', '048', '196', '268', '275', '368', '376', '400', '414',
'422', '512', '634', '682', '760', '792', '784', '887'],
'150': ['039', '151', '154', '155'],
'039': [
'008', '020', '070', '191', '292', '300', '336', '380', '470', '499',
'620', '674', '688', '705', '724', '807'],
'151': [
'100', '112', '203', '348', '498', '616', '642', '643', '703', '804'],
'154': [
'208', '233', '234', '246', '248', '352', '372', '428', '440', '578',
'744', '752', '826', '833', '830'],
'830': ['831', '832', '680'],
'155': ['040', '056', '250', '276', '438', '442', '492', '528', '756'],
'009': ['053', '054', '057', '061'],
'053': ['036', '162', '166', '334', '554', '574'],
'054': ['090', '242', '540', '548', '598'],
'057': ['296', '316', '520', '580', '581', '583', '584', '585'],
'061': [
'016', '184', '258', '570', '612', '772', '776', '798', '876', '882'],
}
REGION2PARENT = {c: p for p, r in SUBREGIONS.items() for c in r}
def _progress(iterable, **kwargs):
if tqdm:
return tqdm(iterable, disable=None, **kwargs)
else:
return iterable
def _get_language_codes():
Language = Model.get('ir.lang')
languages = Language.find([('translatable', '=', True)])
for l in languages:
yield l.code
def _remove_forbidden_chars(name):
from trytond.tools import remove_forbidden_chars
return remove_forbidden_chars(name)
def get_countries():
Country = Model.get('country.country')
return {c.code: c for c in Country.find([])}
def get_organizations():
Organization = Model.get('country.organization')
return {o.code: o for o in Organization.find([])}
def update_countries(countries):
print("Update countries", file=sys.stderr)
Region = Model.get('country.region')
Country = Model.get('country.country')
Member = Model.get('country.organization.member')
organizations = get_organizations()
code2region = {a.code_numeric: a for a in Region.find([])}
records = []
for country in _progress(pycountry.countries):
code = country.alpha_2
if code in countries:
record = countries[code]
else:
record = Country(code=code, members=[])
record.name = _remove_forbidden_chars(country.name)
record.code3 = country.alpha_3
record.code_numeric = country.numeric
record.region = code2region.get(REGION2PARENT.get(country.numeric))
for organization_code, members in ORGANIZATIONS.items():
if organization_code in organizations and code in members:
organization = organizations[organization_code]
dates = members[code].copy()
for member in list(record.members):
if member.organization == organization:
if dates:
member.from_date, member.to_date = dates.pop()
else:
record.members.remove(member)
for from_date, to_date in dates:
record.members.append(Member(
organization=organization,
from_date=from_date,
to_date=to_date))
records.append(record)
Country.save(records)
return {c.code: c for c in records}
def translate_countries(countries):
Country = Model.get('country.country')
current_config = config.get_config()
for code in _get_language_codes():
try:
gnutranslation = gettext.translation(
'iso3166-1', pycountry.LOCALES_DIR, languages=[code])
except IOError:
continue
print("Update countries %s" % code, file=sys.stderr)
with current_config.set_context(language=code):
records = []
for country in _progress(pycountry.countries):
record = Country(countries[country.alpha_2].id)
record.name = _remove_forbidden_chars(
gnutranslation.gettext(country.name))
records.append(record)
Country.save(records)
def get_subdivisions():
Subdivision = Model.get('country.subdivision')
return {(s.country.code, s.code): s for s in Subdivision.find([])}
def update_subdivisions(countries, subdivisions):
print("Update subdivisions", file=sys.stderr)
Subdivision = Model.get('country.subdivision')
types = dict(Subdivision._fields['type']['selection'])
unknown_types = set()
records = []
for subdivision in _progress(pycountry.subdivisions):
code = subdivision.code
country_code = subdivision.country_code
if (country_code, code) in subdivisions:
record = subdivisions[(country_code, code)]
else:
record = Subdivision(code=code, country=countries[country_code])
record.name = _remove_forbidden_chars(subdivision.name)
type_ = subdivision.type.lower()
if type_ in types:
record.type = subdivision.type.lower()
else:
record.type = None
if type_ not in unknown_types:
warnings.warn(
f"Unknown subdivision type: {subdivision.type!r}")
unknown_types.add(type_)
records.append(record)
Subdivision.save(records)
return {(s.country.code, s.code): s for s in records}
def update_subdivisions_parent(subdivisions):
print("Update subdivisions parent", file=sys.stderr)
Subdivision = Model.get('country.subdivision')
records = []
for subdivision in _progress(pycountry.subdivisions):
code = subdivision.code
country_code = subdivision.country_code
record = subdivisions[(country_code, code)]
if subdivision.parent:
record.parent = subdivisions[
(country_code, subdivision.parent.code)]
else:
record.parent = None
records.append(record)
Subdivision.save(records)
def translate_subdivisions(subdivisions):
Subdivision = Model.get('country.subdivision')
current_config = config.get_config()
for code in _get_language_codes():
try:
gnutranslation = gettext.translation(
'iso3166-2', pycountry.LOCALES_DIR, languages=[code])
except IOError:
continue
print("Update subdivisions %s" % code, file=sys.stderr)
with current_config.set_context(language=code):
records = []
for subdivision in _progress(pycountry.subdivisions):
record = Subdivision(subdivisions[
(subdivision.country_code, subdivision.code)].id)
record.name = _remove_forbidden_chars(
gnutranslation.gettext(subdivision.name))
records.append(record)
Subdivision.save(records)
def main(database, config_file=None):
config.set_trytond(database, config_file=config_file)
with config.get_config().set_context(active_test=False):
do_import()
def do_import():
countries = get_countries()
countries = update_countries(countries)
translate_countries(countries)
subdivisions = get_subdivisions()
subdivisions = update_subdivisions(countries, subdivisions)
update_subdivisions_parent(subdivisions)
translate_subdivisions(subdivisions)
def run():
parser = ArgumentParser()
parser.add_argument('-d', '--database', dest='database', required=True)
parser.add_argument('-c', '--config', dest='config_file',
help='the trytond config file')
if argcomplete:
argcomplete.autocomplete(parser)
args = parser.parse_args()
main(args.database, args.config_file)
if __name__ == '__main__':
run()

View File

@@ -0,0 +1,142 @@
#!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from __future__ import print_function
import csv
import os
import sys
try:
from urllib.error import HTTPError
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen, HTTPError
import zipfile
from argparse import ArgumentParser
from io import BytesIO, TextIOWrapper
try:
import argcomplete
except ImportError:
argcomplete = None
try:
from tqdm import tqdm
except ImportError:
tqdm = None
try:
from proteus import Model, config
except ImportError:
prog = os.path.basename(sys.argv[0])
sys.exit("proteus must be installed to use %s" % prog)
def _progress(iterable, **kwargs):
if tqdm:
return tqdm(iterable, disable=None, **kwargs)
else:
return iterable
def clean(code):
sys.stderr.write('Cleaning')
PostalCode = Model.get('country.postal_code')
PostalCode._proxy.delete(
[c.id for c in PostalCode.find([('country.code', '=', code)])], {})
print('.', file=sys.stderr)
def fetch(code):
sys.stderr.write('Fetching')
url = 'https://downloads.tryton.org/geonames/%s.zip' % code
try:
responce = urlopen(url)
except HTTPError as e:
sys.exit("\nError downloading %s: %s" % (code, e.reason))
data = responce.read()
with zipfile.ZipFile(BytesIO(data)) as zf:
data = zf.read('%s.txt' % code)
print('.', file=sys.stderr)
return data
def import_(data):
PostalCode = Model.get('country.postal_code')
Country = Model.get('country.country')
Subdivision = Model.get('country.subdivision')
print('Importing', file=sys.stderr)
def get_country(code):
country = countries.get(code)
if not country:
try:
country, = Country.find([('code', '=', code)])
except ValueError:
sys.exit("Error missing country with code %s" % code)
countries[code] = country
return country
countries = {}
def get_subdivision(country, code):
code = '%s-%s' % (country, code)
subdivision = subdivisions.get(code)
if not subdivision:
try:
subdivision, = Subdivision.find([('code', '=', code)])
except ValueError:
return
subdivisions[code] = subdivision
return subdivision
subdivisions = {}
f = TextIOWrapper(BytesIO(data), encoding='utf-8')
codes = []
for row in _progress(csv.DictReader(
f, fieldnames=_fieldnames, delimiter='\t'),
total=data.count(b'\n')):
country = get_country(row['country'])
for code in ['code1', 'code2', 'code3']:
subdivision = get_subdivision(row['country'], row[code])
if code == 'code1' or subdivision:
codes.append(
PostalCode(country=country, subdivision=subdivision,
postal_code=row['postal'], city=row['place']))
PostalCode.save(codes)
_fieldnames = ['country', 'postal', 'place', 'name1', 'code1',
'name2', 'code2', 'name3', 'code3', 'latitude', 'longitude', 'accuracy']
def main(database, codes, config_file=None):
config.set_trytond(database, config_file=config_file)
do_import(codes)
def do_import(codes):
for code in codes:
print(code, file=sys.stderr)
code = code.upper()
clean(code)
import_(fetch(code))
def run():
parser = ArgumentParser()
parser.add_argument('-d', '--database', dest='database', required=True)
parser.add_argument('-c', '--config', dest='config_file',
help='the trytond config file')
parser.add_argument('codes', nargs='+')
if argcomplete:
argcomplete.autocomplete(parser)
args = parser.parse_args()
main(args.database, args.codes, args.config_file)
if __name__ == '__main__':
run()

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,26 @@
==============
Country Import
==============
Imports::
>>> from proteus import Model
>>> from trytond.modules.country.scripts import (
... import_countries, import_postal_codes)
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('country')
Import countries::
>>> Country = Model.get('country.country')
>>> belgium = Country(name="Belgium", code='BE')
>>> belgium.save()
>>> import_countries.do_import()
Import postal codes::
>>> import_postal_codes.do_import(['us'])

View File

@@ -0,0 +1,115 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
class CountryTestCase(ModuleTestCase):
'Test Country module'
module = 'country'
def create_membership(self, from_date=None, to_date=None):
pool = Pool()
Country = pool.get('country.country')
Organization = pool.get('country.organization')
OrganizationMember = pool.get('country.organization.member')
organization = Organization(name="Organization")
organization.save()
country = Country(name="Country")
country.save()
member = OrganizationMember(
organization=organization, country=country,
from_date=from_date, to_date=to_date)
member.save()
return organization, country
@with_transaction()
def test_is_member_no_date(self):
"Test is member without date"
organization, country = self.create_membership()
self.assertTrue(country.is_member(organization))
self.assertTrue(country.is_member(organization, dt.date.min))
self.assertTrue(country.is_member(organization, dt.date.max))
@with_transaction()
def test_is_member_with_from_date(self):
"Test is member with from date"
today = dt.date.today()
yesterday = today - dt.timedelta(days=1)
tomorrow = today + dt.timedelta(days=1)
organization, country = self.create_membership(from_date=today)
self.assertTrue(country.is_member(organization, today))
self.assertFalse(country.is_member(organization, yesterday))
self.assertTrue(country.is_member(organization, tomorrow))
self.assertFalse(country.is_member(organization, dt.date.min))
self.assertTrue(country.is_member(organization, dt.date.max))
@with_transaction()
def test_is_member_with_to_date(self):
"Test is member with to date"
today = dt.date.today()
yesterday = today - dt.timedelta(days=1)
tomorrow = today + dt.timedelta(days=1)
organization, country = self.create_membership(to_date=today)
self.assertTrue(country.is_member(organization, today))
self.assertTrue(country.is_member(organization, yesterday))
self.assertFalse(country.is_member(organization, tomorrow))
self.assertTrue(country.is_member(organization, dt.date.min))
self.assertFalse(country.is_member(organization, dt.date.max))
@with_transaction()
def test_is_member_with_dates(self):
"Test is member with dates"
today = dt.date.today()
yesterday = today - dt.timedelta(days=1)
tomorrow = today + dt.timedelta(days=1)
organization, country = self.create_membership(
from_date=yesterday, to_date=tomorrow)
self.assertTrue(country.is_member(organization, today))
self.assertTrue(country.is_member(organization, yesterday))
self.assertFalse(country.is_member(organization, dt.date.min))
self.assertFalse(country.is_member(organization, dt.date.max))
@with_transaction()
def test_subdivision_code_search(self):
"Test search on subdivision code"
pool = Pool()
Country = pool.get('country.country')
Subdivision = pool.get('country.subdivision')
fr, = Country.create([{'name': 'France'}])
paris, nord = Subdivision.create([{
'name': 'Paris',
'code': 'FR-75',
'country': fr,
}, {
'name': 'Nord',
'code': 'FR-59',
'country': fr,
}])
for domain, result in [
([('code', '=', 'FR-59')], [nord]),
([('code', '!=', 'FR-59')], [paris]),
([('code', '=', '59')], []),
([('code', '!=', '59')], [nord, paris]),
([('code', 'like', '59%')], [nord]),
([('code', 'not like', '59%')], [paris]),
([('code', 'ilike', '59%')], [nord]),
([('code', 'not like', '59%')], [paris]),
]:
with self.subTest(domain=domain):
self.assertEqual(Subdivision.search(
domain, order=[('name', 'ASC')]),
result)
del ModuleTestCase

View File

@@ -0,0 +1,10 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.tests.test_tryton import TEST_NETWORK, load_doc_tests
def load_tests(*args, **kwargs):
if not TEST_NETWORK:
kwargs.setdefault('skips', set()).add('scenario_country_import.rst')
return load_doc_tests(__name__, __file__, *args, **kwargs)

View File

@@ -0,0 +1,18 @@
[tryton]
version=7.8.0
depends:
ir
res
xml:
country.xml
organization.xml
region.xml
[register]
model:
country.Organization
country.OrganizationMember
country.Region
country.Country
country.Subdivision
country.PostalCode

View File

@@ -0,0 +1,21 @@
<?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. -->
<form col="6">
<label name="name"/>
<field name="name"/>
<label name="region"/>
<field name="region"/>
<label name="active"/>
<field name="active"/>
<label name="code"/>
<field name="code"/>
<label name="code3"/>
<field name="code3"/>
<label name="code_numeric"/>
<field name="code_numeric"/>
<field name="subdivisions" colspan="6"/>
<field name="members" colspan="6"/>
</form>

View File

@@ -0,0 +1,12 @@
<?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. -->
<tree>
<field name="name" expand="2">
<prefix name="flag"/>
</field>
<field name="code" optional="1"/>
<field name="code3" optional="1"/>
<field name="code_numeric" optional="1"/>
<field name="region" expand="1" optional="1"/>
</tree>

View File

@@ -0,0 +1,11 @@
<?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. -->
<form>
<label name="name"/>
<field name="name"/>
<label name="code"/>
<field name="code"/>
<field name="members" colspan="4"/>
</form>

View File

@@ -0,0 +1,7 @@
<?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. -->
<tree>
<field name="name" expand="2"/>
<field name="code" optional="0"/>
</tree>

View File

@@ -0,0 +1,14 @@
<?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. -->
<form>
<label name="country"/>
<field name="country"/>
<label name="organization"/>
<field name="organization"/>
<label name="from_date"/>
<field name="from_date"/>
<label name="to_date"/>
<field name="to_date"/>
</form>

View File

@@ -0,0 +1,9 @@
<?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. -->
<tree>
<field name="country" expand="2"/>
<field name="organization" expand="2"/>
<field name="from_date" optional="0"/>
<field name="to_date" optional="0"/>
</tree>

View File

@@ -0,0 +1,13 @@
<?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. -->
<form>
<label name="country"/>
<field name="country"/>
<label name="subdivision"/>
<field name="subdivision"/>
<label name="postal_code"/>
<field name="postal_code"/>
<label name="city"/>
<field name="city"/>
</form>

View File

@@ -0,0 +1,9 @@
<?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. -->
<tree>
<field name="country" expand="1"/>
<field name="subdivision" expand="1"/>
<field name="postal_code" expand="1"/>
<field name="city" expand="1"/>
</tree>

View File

@@ -0,0 +1,16 @@
<?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. -->
<form>
<label name="name"/>
<field name="name"/>
<label name="code_numeric"/>
<field name="code_numeric"/>
<label name="parent"/>
<field name="parent"/>
<field name="subregions" colspan="4"/>
<field name="countries" colspan="4"/>
</form>

View File

@@ -0,0 +1,7 @@
<?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. -->
<tree>
<field name="name" expand="1"/>
<field name="code_numeric" optional="1"/>
</tree>

View File

@@ -0,0 +1,7 @@
<?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. -->
<tree keyword_open="1">
<field name="name" expand="1"/>
<field name="code_numeric" optional="1"/>
</tree>

View File

@@ -0,0 +1,18 @@
<?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. -->
<form col="6">
<label name="country"/>
<field name="country"/>
<newline/>
<label name="name"/>
<field name="name"/>
<label name="code"/>
<field name="code"/>
<label name="active"/>
<field name="active"/>
<label name="type"/>
<field name="type"/>
<label name="parent"/>
<field name="parent"/>
</form>

View File

@@ -0,0 +1,8 @@
<?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. -->
<tree>
<field name="name" expand="1"/>
<field name="code"/>
<field name="type"/>
</tree>