diff --git a/CHANGELOG b/CHANGELOG
index 9eed3a2ae3..2b3f684f11 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,4 @@
+* Add country organizations
* Add UN M49 regions
* Add flag symbol for country and subdivision
diff --git a/__init__.py b/__init__.py
index b229037c5f..dcc3549d5f 100644
--- a/__init__.py
+++ b/__init__.py
@@ -8,6 +8,8 @@ def register():
# Prevent to import backend when importing scripts
from . import country
Pool.register(
+ country.Organization,
+ country.OrganizationMember,
country.Region,
country.Country,
country.Subdivision,
diff --git a/country.py b/country.py
index a53df52a01..51805c1239 100644
--- a/country.py
+++ b/country.py
@@ -1,13 +1,109 @@
# 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 sql import Literal
+from sql.conditionals import Coalesce
+
from trytond import backend
from trytond.model import DeactivableMixin, ModelSQL, ModelView, fields, tree
from trytond.pool import Pool
-from trytond.pyson import Eval
+from trytond.pyson import Eval, If
from trytond.tools import lstrip_wildcard
from trytond.transaction import Transaction
+class Organization(ModelSQL, ModelView):
+ "Organization"
+ __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):
+ "Organization Member"
+ __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):
"Region"
__name__ = 'country.region'
@@ -60,6 +156,14 @@ class Country(DeactivableMixin, ModelSQL, ModelView):
'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):
@@ -115,6 +219,21 @@ class Country(DeactivableMixin, ModelSQL, ModelView):
('code_numeric', clause[1], code_value) + tuple(clause[3:]),
]
+ 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 create(cls, vlist):
vlist = [x.copy() for x in vlist]
diff --git a/country.xml b/country.xml
index f848c2ebde..97b5906142 100644
--- a/country.xml
+++ b/country.xml
@@ -20,6 +20,66 @@ this repository contains the full copyright notices and license terms. -->
+
+ country.organization
+ form
+ organization_form
+
+
+
+ country.organization
+ tree
+ organization_list
+
+
+
+ Organizations
+ country.organization
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ country.organization.member
+ form
+ organization_member_form
+
+
+
+ country.organization.member
+ tree
+ organization_member_list
+
+
country.region
form
diff --git a/doc/design.rst b/doc/design.rst
index 609a4f23c7..1d3c3ed9d6 100644
--- a/doc/design.rst
+++ b/doc/design.rst
@@ -4,6 +4,24 @@ Design
The *Country Module* introduces the following concepts:
+.. _model-country.organization:
+
+Organization
+============
+
+The *Organization* represents an international organization of `Countries
+`.
+
+.. seealso::
+
+ Organizations can be found under the main menu entry:
+
+ |Administration --> Countries --> Organizations|__
+
+ .. |Administration --> Countries --> Organizations| replace:: :menuselection:`Administration --> Countries --> Organizations`
+ __ https://demo.tryton.org/model/country.organization
+
+
.. _model-country.region:
Region
diff --git a/organization.xml b/organization.xml
new file mode 100644
index 0000000000..f74f7bb827
--- /dev/null
+++ b/organization.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+ European Union
+ EU
+
+
+
+ Benelux Union
+ Benelux
+
+
+
+ North American Free Trade Agreement
+ NAFTA
+
+
+
+ Southern Common Market
+ Mercosur
+
+
+
+ Andean Community
+ CAN
+
+
+
+ Caribbean Community
+ CARICOM
+
+
+
+ Asia-Pacific Economic Cooperation
+ APEC
+
+
+
+ Association of Southeast Asian Nations
+ ASEAN
+
+
+
+ South Asian Free Trade Area
+ SAFTA
+
+
+
+ Cooperation Council for the Arab States of the Gulf
+ GCC
+
+
+
+ Economic and Monetary Community of Central Africa
+ CEMAC
+
+
+
+ Economic Community of Central African States
+ ECCAS
+
+
+
+ Economic Community of West African States
+ ECOWAS
+
+
+
+ Community of Sahel–Saharan States
+ CEN-SAD
+
+
+
+ Common Market for Eastern and Southern Africa
+ COMESA
+
+
+
+ East African Community
+ EAC
+
+
+
+ Intergovernmental Authority on Development
+ IGAD
+
+
+
+ Southern African Development Community
+ SADC
+
+
+
+ Arab Maghreb Union
+ AMU
+
+
+
diff --git a/scripts/import_countries.py b/scripts/import_countries.py
index 6d33eac824..463d055e1e 100755
--- a/scripts/import_countries.py
+++ b/scripts/import_countries.py
@@ -2,6 +2,8 @@
# 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
@@ -25,6 +27,286 @@ 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'],
@@ -109,10 +391,18 @@ def get_countries():
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([])}
@@ -122,11 +412,26 @@ def update_countries(countries):
if code in countries:
record = countries[code]
else:
- record = Country(code=code)
+ 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)
diff --git a/setup.py b/setup.py
index e51364c83d..b0d5fe4e8c 100755
--- a/setup.py
+++ b/setup.py
@@ -61,7 +61,7 @@ else:
if local_version:
version += '+' + '.'.join(local_version)
-requires = []
+requires = ['python-sql']
for dep in info.get('depends', []):
if not re.match(r'(ir|res)(\W|$)', dep):
requires.append(get_require_version('trytond_%s' % dep))
diff --git a/tests/test_module.py b/tests/test_module.py
index 66e772ea28..8a02c3e6db 100644
--- a/tests/test_module.py
+++ b/tests/test_module.py
@@ -1,12 +1,82 @@
# 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 ModuleTestCase
+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))
+
del ModuleTestCase
diff --git a/tryton.cfg b/tryton.cfg
index 65b4cf72da..c2a9f0cc12 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -5,4 +5,5 @@ depends:
res
xml:
country.xml
+ organization.xml
region.xml
diff --git a/view/country_form.xml b/view/country_form.xml
index 7042e8479b..c670232a96 100644
--- a/view/country_form.xml
+++ b/view/country_form.xml
@@ -16,4 +16,6 @@ this repository contains the full copyright notices and license terms. -->
+
+
diff --git a/view/organization_form.xml b/view/organization_form.xml
new file mode 100644
index 0000000000..f260225166
--- /dev/null
+++ b/view/organization_form.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/view/organization_list.xml b/view/organization_list.xml
new file mode 100644
index 0000000000..e6ee148fa6
--- /dev/null
+++ b/view/organization_list.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/view/organization_member_form.xml b/view/organization_member_form.xml
new file mode 100644
index 0000000000..bb509abd8f
--- /dev/null
+++ b/view/organization_member_form.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/view/organization_member_list.xml b/view/organization_member_list.xml
new file mode 100644
index 0000000000..b2d567d57e
--- /dev/null
+++ b/view/organization_member_list.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+