Browse Source

[ENH] Backport 9.0 improvements to partner_multi_relation.

pull/301/head
Ronald Portier 9 years ago
committed by Holger Brunn
parent
commit
1051dd19c1
  1. 3
      partner_relations/models/res_partner.py
  2. 25
      partner_relations/models/res_partner_relation.py
  3. 167
      partner_relations/models/res_partner_relation_all.py
  4. 93
      partner_relations/models/res_partner_relation_type.py
  5. 8
      partner_relations/tests/__init__.py
  6. 250
      partner_relations/tests/test_partner_relation.py
  7. 241
      partner_relations/tests/test_partner_relation_all.py
  8. 113
      partner_relations/tests/test_partner_relation_common.py
  9. 76
      partner_relations/tests/test_partner_search.py

3
partner_relations/models/res_partner.py

@ -116,9 +116,6 @@ class ResPartner(models.Model):
def _search_relation_date(self, operator, value): def _search_relation_date(self, operator, value):
"""Look only for relations valid at date of search.""" """Look only for relations valid at date of search."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
if operator != '=':
raise exceptions.ValidationError(
_('Unsupported search operator "%s"') % operator)
return [ return [
'&', '&',
'|', '|',

25
partner_relations/models/res_partner_relation.py

@ -2,7 +2,8 @@
# © 2013-2016 Therp BV <http://therp.nl> # © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
"""Store relations (connections) between partners.""" """Store relations (connections) between partners."""
from openerp import _, api, exceptions, fields, models
from openerp import _, api, fields, models
from openerp.exceptions import ValidationError
class ResPartnerRelation(models.Model): class ResPartnerRelation(models.Model):
@ -53,11 +54,11 @@ class ResPartnerRelation(models.Model):
def _check_dates(self): def _check_dates(self):
"""End date should not be before start date, if not filled """End date should not be before start date, if not filled
:raises exceptions.Warning: When constraint is violated
:raises ValidationError: When constraint is violated
""" """
if (self.date_start and self.date_end and if (self.date_start and self.date_end and
self.date_start > self.date_end): self.date_start > self.date_end):
raise exceptions.Warning(
raise ValidationError(
_('The starting date cannot be after the ending date.') _('The starting date cannot be after the ending date.')
) )
@ -66,7 +67,7 @@ class ResPartnerRelation(models.Model):
def _check_partner_left(self): def _check_partner_left(self):
"""Check left partner for required company or person """Check left partner for required company or person
:raises exceptions.Warning: When constraint is violated
:raises ValidationError: When constraint is violated
""" """
self._check_partner("left") self._check_partner("left")
@ -75,7 +76,7 @@ class ResPartnerRelation(models.Model):
def _check_partner_right(self): def _check_partner_right(self):
"""Check right partner for required company or person """Check right partner for required company or person
:raises exceptions.Warning: When constraint is violated
:raises ValidationError: When constraint is violated
""" """
self._check_partner("right") self._check_partner("right")
@ -84,20 +85,20 @@ class ResPartnerRelation(models.Model):
"""Check partner for required company or person, and for category """Check partner for required company or person, and for category
:param str side: left or right :param str side: left or right
:raises exceptions.Warning: When constraint is violated
:raises ValidationError: When constraint is violated
""" """
assert side in ['left', 'right'] assert side in ['left', 'right']
ptype = getattr(self.type_id, "contact_type_%s" % side) ptype = getattr(self.type_id, "contact_type_%s" % side)
partner = getattr(self, '%s_partner_id' % side) partner = getattr(self, '%s_partner_id' % side)
if ((ptype == 'c' and not partner.is_company) or if ((ptype == 'c' and not partner.is_company) or
(ptype == 'p' and partner.is_company)): (ptype == 'p' and partner.is_company)):
raise exceptions.Warning(
raise ValidationError(
_('The %s partner is not applicable for this relation type.') % _('The %s partner is not applicable for this relation type.') %
side side
) )
category = getattr(self.type_id, "partner_category_%s" % side) category = getattr(self.type_id, "partner_category_%s" % side)
if category and category.id not in partner.category_id.ids: if category and category.id not in partner.category_id.ids:
raise exceptions.Warning(
raise ValidationError(
_('The %s partner does not have category %s.') % _('The %s partner does not have category %s.') %
(side, category.name) (side, category.name)
) )
@ -107,11 +108,11 @@ class ResPartnerRelation(models.Model):
def _check_not_with_self(self): def _check_not_with_self(self):
"""Not allowed to link partner to same partner """Not allowed to link partner to same partner
:raises exceptions.Warning: When constraint is violated
:raises ValidationError: When constraint is violated
""" """
if self.left_partner_id == self.right_partner_id: if self.left_partner_id == self.right_partner_id:
if not (self.type_id and self.type_id.allow_self): if not (self.type_id and self.type_id.allow_self):
raise exceptions.Warning(
raise ValidationError(
_('Partners cannot have a relation with themselves.') _('Partners cannot have a relation with themselves.')
) )
@ -127,7 +128,7 @@ class ResPartnerRelation(models.Model):
"""Forbid multiple active relations of the same type between the same """Forbid multiple active relations of the same type between the same
partners partners
:raises exceptions.Warning: When constraint is violated
:raises ValidationError: When constraint is violated
""" """
# pylint: disable=no-member # pylint: disable=no-member
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
@ -150,6 +151,6 @@ class ResPartnerRelation(models.Model):
('date_start', '<=', self.date_end), ('date_start', '<=', self.date_end),
] ]
if self.search(domain): if self.search(domain):
raise exceptions.Warning(
raise ValidationError(
_('There is already a similar relation with overlapping dates') _('There is already a similar relation with overlapping dates')
) )

167
partner_relations/models/res_partner_relation_all.py

@ -76,7 +76,7 @@ class ResPartnerRelationAll(models.AbstractModel):
any_partner_id = fields.Many2many( any_partner_id = fields.Many2many(
comodel_name='res.partner', comodel_name='res.partner',
string='Partner', string='Partner',
compute='_compute_any_partner_id',
compute=lambda self: None,
search='_search_any_partner_id' search='_search_any_partner_id'
) )
@ -127,14 +127,6 @@ CREATE OR REPLACE VIEW %(table)s AS
cr, context=context cr, context=context
) )
@api.depends('this_partner_id', 'other_partner_id')
def _compute_any_partner_id(self):
"""Compute any_partner_id, used for searching for partner, independent
wether it is the one partner or the other partner in the relation.
"""
for rec in self:
rec.any_partner_id = rec.this_partner_id + rec.other_partner_id
@api.model @api.model
def _search_any_partner_id(self, operator, value): def _search_any_partner_id(self, operator, value):
"""Search relation with partner, no matter on which side.""" """Search relation with partner, no matter on which side."""
@ -165,8 +157,6 @@ CREATE OR REPLACE VIEW %(table)s AS
for partner, or wrong selection of partner already selected. for partner, or wrong selection of partner already selected.
""" """
warning = {} warning = {}
if not partner_domain:
return warning
if partner: if partner:
test_domain = [('id', '=', partner.id)] + partner_domain test_domain = [('id', '=', partner.id)] + partner_domain
else: else:
@ -174,16 +164,17 @@ CREATE OR REPLACE VIEW %(table)s AS
partner_model = self.env['res.partner'] partner_model = self.env['res.partner']
partners_found = partner_model.search(test_domain, limit=1) partners_found = partner_model.search(test_domain, limit=1)
if not partners_found: if not partners_found:
warning['title'] = _('Error!')
if partner: if partner:
message = _(
'%s partner incompatible with relation type.' %
warning['message'] = (
_('%s partner incompatible with relation type.') %
side.title() side.title()
) )
else: else:
message = _(
'No %s partner available for relation type.' % side
warning['message'] = (
_('No %s partner available for relation type.') %
side
) )
warning = {'title': _('Error!'), 'message': message}
return warning return warning
this_partner_domain = [] this_partner_domain = []
@ -213,17 +204,17 @@ CREATE OR REPLACE VIEW %(table)s AS
'other_partner_id': other_partner_domain, 'other_partner_id': other_partner_domain,
}} }}
# Check wether domain results in no choice or wrong choice of partners: # Check wether domain results in no choice or wrong choice of partners:
warning = check_partner_domain(
self.this_partner_id, this_partner_domain, _('this')
)
if warning:
result['warning'] = warning
else:
warning = {}
if this_partner_domain:
warning = check_partner_domain(
self.this_partner_id, this_partner_domain, _('this')
)
if not warning and other_partner_domain:
warning = check_partner_domain( warning = check_partner_domain(
self.other_partner_id, other_partner_domain, _('other') self.other_partner_id, other_partner_domain, _('other')
) )
if warning:
result['warning'] = warning
if warning:
result['warning'] = warning
return result return result
@api.onchange( @api.onchange(
@ -234,126 +225,25 @@ CREATE OR REPLACE VIEW %(table)s AS
"""Set domain on type_selection_id based on partner(s) selected.""" """Set domain on type_selection_id based on partner(s) selected."""
def check_type_selection_domain(type_selection_domain): def check_type_selection_domain(type_selection_domain):
"""Check wether type_selection_domain results in empty selection
for type_selection_id, or wrong selection if already selected.
"""If type_selection_id already selected, check wether it
is compatible with the computed type_selection_domain. An empty
selection can practically only occur in a practically empty
database, and will not lead to problems. Therefore not tested.
""" """
warning = {} warning = {}
if not type_selection_domain:
if not (type_selection_domain and self.type_selection_id):
return warning return warning
if self.type_selection_id:
test_domain = (
[('id', '=', self.type_selection_id.id)] +
type_selection_domain
)
else:
test_domain = type_selection_domain
test_domain = (
[('id', '=', self.type_selection_id.id)] +
type_selection_domain
)
type_model = self.env['res.partner.relation.type.selection'] type_model = self.env['res.partner.relation.type.selection']
types_found = type_model.search(test_domain, limit=1) types_found = type_model.search(test_domain, limit=1)
if not types_found: if not types_found:
if self.type_selection_id:
message = _(
'Relation type incompatible with selected partner(s).'
)
else:
message = _(
'No relation type available for selected partners.'
)
warning = {'title': _('Error!'), 'message': message}
return warning
type_selection_domain = []
if self.this_partner_id:
type_selection_domain += [
'|',
('contact_type_this', '=', False),
('contact_type_this', '=',
self.this_partner_id.get_partner_type()),
'|',
('partner_category_this', '=', False),
('partner_category_this', 'in',
self.this_partner_id.category_id.ids),
]
if self.other_partner_id:
type_selection_domain += [
'|',
('contact_type_other', '=', False),
('contact_type_other', '=',
self.other_partner_id.get_partner_type()),
'|',
('partner_category_other', '=', False),
('partner_category_other', 'in',
self.other_partner_id.category_id.ids),
]
result = {'domain': {
'type_selection_id': type_selection_domain,
}}
# Check wether domain results in no choice or wrong choice for
# type_selection_id:
warning = check_type_selection_domain(type_selection_domain)
if warning:
result['warning'] = warning
return result
@api.model
def _correct_vals(self, vals):
"""Fill left and right partner from this and other partner."""
vals = vals.copy()
if 'this_partner_id' in vals:
vals['left_partner_id'] = vals['this_partner_id']
del vals['this_partner_id']
if 'other_partner_id' in vals:
vals['right_partner_id'] = vals['other_partner_id']
del vals['other_partner_id']
if 'type_selection_id' not in vals:
return vals
selection = self.type_selection_id.browse(vals['type_selection_id'])
type_id = selection.type_id.id
is_inverse = selection.is_inverse
vals['type_id'] = type_id
del vals['type_selection_id']
# Need to switch right and left partner if we are in reverse id:
if 'left_partner_id' in vals or 'right_partner_id' in vals:
if is_inverse:
left_partner_id = False
right_partner_id = False
if 'left_partner_id' in vals:
right_partner_id = vals['left_partner_id']
del vals['left_partner_id']
if 'right_partner_id' in vals:
left_partner_id = vals['right_partner_id']
del vals['right_partner_id']
if left_partner_id:
vals['left_partner_id'] = left_partner_id
if right_partner_id:
vals['right_partner_id'] = right_partner_id
return vals
def check_type_selection_domain(type_selection_domain):
"""Check wether type_selection_domain results in empty selection
for type_selection_id, or wrong selection if already selected.
"""
warning = {}
if not type_selection_domain:
return warning
if self.type_selection_id:
test_domain = (
[('id', '=', self.type_selection_id.id)] +
type_selection_domain
warning['title'] = _('Error!')
warning['message'] = _(
'Relation type incompatible with selected partner(s).'
) )
else:
test_domain = type_selection_domain
type_model = self.env['res.partner.relation.type.selection']
types_found = type_model.search(test_domain, limit=1)
if not types_found:
if self.type_selection_id:
message = _(
'Relation type incompatible with selected partner(s).'
)
else:
message = _(
'No relation type available for selected partners.'
)
warning = {'title': _('Error!'), 'message': message}
return warning return warning
type_selection_domain = [] type_selection_domain = []
@ -362,8 +252,7 @@ CREATE OR REPLACE VIEW %(table)s AS
'|', '|',
('contact_type_this', '=', False), ('contact_type_this', '=', False),
('contact_type_this', '=', ('contact_type_this', '=',
self.this_partner_id.get_partner_type()
),
self.this_partner_id.get_partner_type()),
'|', '|',
('partner_category_this', '=', False), ('partner_category_this', '=', False),
('partner_category_this', 'in', ('partner_category_this', 'in',

93
partner_relations/models/res_partner_relation_type.py

@ -4,6 +4,7 @@
"""Define the type of relations that can exist between partners.""" """Define the type of relations that can exist between partners."""
from openerp import _, api, fields, models from openerp import _, api, fields, models
from openerp.exceptions import ValidationError from openerp.exceptions import ValidationError
from openerp.osv.expression import AND, OR
HANDLE_INVALID_ONCHANGE = [ HANDLE_INVALID_ONCHANGE = [
@ -98,6 +99,30 @@ class ResPartnerRelationType(models.Model):
def check_existing(self, vals): def check_existing(self, vals):
"""Check wether records exist that do not fit new criteria.""" """Check wether records exist that do not fit new criteria."""
relation_model = self.env['res.partner.relation'] relation_model = self.env['res.partner.relation']
def get_type_condition(vals, side):
"""Add if needed check for contact type."""
fieldname1 = 'contact_type_%s' % side
fieldname2 = '%s_partner_id.is_company' % side
contact_type = fieldname1 in vals and vals[fieldname1] or False
if contact_type == 'c':
# Records that are not companies are invalid:
return [(fieldname2, '=', False)]
if contact_type == 'p':
# Records that are companies are invalid:
return [(fieldname2, '=', True)]
return []
def get_category_condition(vals, side):
"""Add if needed check for partner category."""
fieldname1 = 'partner_category_%s' % side
fieldname2 = '%s_partner_id.category_id' % side
category_id = fieldname1 in vals and vals[fieldname1] or False
if category_id:
# Records that do not have the specified category are invalid:
return [(fieldname2, 'not in', [category_id])]
return []
for rec in self: for rec in self:
handling = ( handling = (
'handle_invalid_onchange' in vals and 'handle_invalid_onchange' in vals and
@ -106,60 +131,22 @@ class ResPartnerRelationType(models.Model):
) )
if handling == 'ignore': if handling == 'ignore':
continue continue
invalid_conditions = []
for side in ['left', 'right']:
invalid_conditions = OR([
invalid_conditions,
get_type_condition(vals, side),
])
invalid_conditions = OR([
invalid_conditions,
get_category_condition(vals, side),
])
if not invalid_conditions:
return
# only look at relations for this type # only look at relations for this type
invalid_domain = [
('type_id', '=', rec.id),
]
contact_type_left = (
'contact_type_left' in vals and vals['contact_type_left'] or
False
)
if contact_type_left == 'c':
# Valid records are companies:
invalid_domain.append(
('left_partner_id.is_company', '=', False)
)
if contact_type_left == 'p':
# Valid records are persons:
invalid_domain.append(
('left_partner_id.is_company', '=', True)
)
contact_type_right = (
'contact_type_right' in vals and vals['contact_type_right'] or
False
)
if contact_type_right == 'c':
# Valid records are companies:
invalid_domain.append(
('right_partner_id.is_company', '=', False)
)
if contact_type_right == 'p':
# Valid records are persons:
invalid_domain.append(
('right_partner_id.is_company', '=', True)
)
partner_category_left = (
'partner_category_left' in vals and
vals['partner_category_left'] or
False
)
if partner_category_left:
# records that do not have the specified category are invalid:
invalid_domain.append(
('left_partner_id.category_id', 'not in',
partner_category_left)
)
partner_category_right = (
'partner_category_right' in vals and
vals['partner_category_right'] or
False
)
if partner_category_right:
# records that do not have the specified category are invalid:
invalid_domain.append(
('right_partner_id.category_id', 'not in',
partner_category_right)
)
invalid_domain = AND([
[('type_id', '=', rec.id)], invalid_conditions
])
invalid_relations = relation_model.with_context( invalid_relations = relation_model.with_context(
active_test=False active_test=False
).search(invalid_domain) ).search(invalid_domain)

8
partner_relations/tests/__init__.py

@ -1 +1,7 @@
from . import test_partner_relations
# -*- coding: utf-8 -*-
# Copyright 2016 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_partner_relation_common
from . import test_partner_relation
from . import test_partner_relation_all
from . import test_partner_search

250
partner_relations/tests/test_partner_relations.py → partner_relations/tests/test_partner_relation.py

@ -1,97 +1,26 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2015 Camptocamp SA
# Copyright 2016 Therp BV # Copyright 2016 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import date
from dateutil.relativedelta import relativedelta
from openerp import fields from openerp import fields
from openerp.tests import common
from openerp.exceptions import ValidationError from openerp.exceptions import ValidationError
from .test_partner_relation_common import TestPartnerRelationCommon
class TestPartnerRelation(common.TransactionCase):
def setUp(self):
super(TestPartnerRelation, self).setUp()
class TestPartnerRelation(TestPartnerRelationCommon):
self.partner_model = self.env['res.partner']
self.category_model = self.env['res.partner.category']
self.type_model = self.env['res.partner.relation.type']
self.selection_model = self.env['res.partner.relation.type.selection']
self.relation_model = self.env['res.partner.relation']
self.relation_all_model = self.env['res.partner.relation.all']
self.partner_01_person = self.partner_model.create({
'name': 'Test User 1',
'is_company': False,
'ref': 'PR01',
})
self.partner_02_company = self.partner_model.create({
'name': 'Test Company',
'is_company': True,
'ref': 'PR02',
})
self.type_company2person = self.type_model.create({
'name': 'mixed',
'name_inverse': 'mixed_inverse',
'contact_type_left': 'c',
'contact_type_right': 'p',
})
# Create partners with specific categories:
self.category_01_ngo = self.category_model.create({
'name': 'NGO',
})
self.partner_03_ngo = self.partner_model.create({
'name': 'Test NGO',
'is_company': True,
'ref': 'PR03',
'category_id': [(4, self.category_01_ngo.id)],
})
self.category_02_volunteer = self.category_model.create({
'name': 'Volunteer',
})
self.partner_04_volunteer = self.partner_model.create({
'name': 'Test Volunteer',
'is_company': False,
'ref': 'PR04',
'category_id': [(4, self.category_02_volunteer.id)],
})
# Determine the two records in res.partner.type.selection that came
# into existance by creating one res.partner.relation.type:
selection_types = self.selection_model.search([
('type_id', '=', self.type_company2person.id),
])
for st in selection_types:
if st.is_inverse:
self.selection_person2company = st
else:
self.selection_company2person = st
assert self.selection_person2company, (
"Failed to create person to company selection in setup."
)
assert self.selection_company2person, (
"Failed to create company to person selection in setup."
)
# Create realion type between NGO and volunteer, and then lookup
# resulting type_selection_id's:
self.type_ngo2volunteer = self.type_model.create({
'name': 'NGO has volunteer',
'name_inverse': 'volunteer works for NGO',
'contact_type_left': 'c',
'contact_type_right': 'p',
'partner_category_left': self.category_01_ngo.id,
'partner_category_right': self.category_02_volunteer.id,
})
selection_types = self.selection_model.search([
('type_id', '=', self.type_ngo2volunteer.id),
])
for st in selection_types:
if st.is_inverse:
self.selection_volunteer2ngo = st
else:
self.selection_ngo2volunteer = st
assert self.selection_volunteer2ngo, (
"Failed to create volunteer to NGO selection in setup."
def test_selection_name_search(self):
"""Test wether we can find type selection on reverse name."""
selection_types = self.selection_model.name_search(
name=self.selection_person2company.name
) )
assert self.selection_ngo2volunteer, (
"Failed to create NGO to volunteer selection in setup."
self.assertTrue(selection_types)
self.assertTrue(
(self.selection_person2company.id,
self.selection_person2company.name) in selection_types
) )
def test_self_allowed(self): def test_self_allowed(self):
@ -103,11 +32,13 @@ class TestPartnerRelation(common.TransactionCase):
'contact_type_right': 'p', 'contact_type_right': 'p',
'allow_self': True 'allow_self': True
}) })
self.relation_model.create({
self.assertTrue(type_allow)
reflexive_relation = self.relation_model.create({
'type_id': type_allow.id, 'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id, 'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id, 'right_partner_id': self.partner_01_person.id,
}) })
self.assertTrue(reflexive_relation)
def test_self_disallowed(self): def test_self_disallowed(self):
"""Test creating relation to same partner when disallowed. """Test creating relation to same partner when disallowed.
@ -122,6 +53,7 @@ class TestPartnerRelation(common.TransactionCase):
'contact_type_right': 'p', 'contact_type_right': 'p',
'allow_self': False 'allow_self': False
}) })
self.assertTrue(type_disallow)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({ self.relation_model.create({
'type_id': type_disallow.id, 'type_id': type_disallow.id,
@ -142,6 +74,7 @@ class TestPartnerRelation(common.TransactionCase):
'contact_type_left': 'p', 'contact_type_left': 'p',
'contact_type_right': 'p', 'contact_type_right': 'p',
}) })
self.assertTrue(type_default)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({ self.relation_model.create({
'type_id': type_default.id, 'type_id': type_default.id,
@ -162,90 +95,6 @@ class TestPartnerRelation(common.TransactionCase):
'right_partner_id': self.partner_02_company.id, 'right_partner_id': self.partner_02_company.id,
}) })
def test_searching(self):
"""Test searching on relations.
Interaction with the relations should always be through
res.partner.relation.all.
"""
relation = self.relation_all_model.create({
'type_selection_id': self.selection_company2person.id,
'this_partner_id': self.partner_02_company.id,
'other_partner_id': self.partner_01_person.id,
})
partners = self.partner_model.search([
('search_relation_type_id', '=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_02_company in partners)
partners = self.partner_model.search([
('search_relation_type_id', '!=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_01_person in partners)
partners = self.partner_model.search([
('search_relation_type_id', '=', self.type_company2person.name)
])
self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners)
partners = self.partner_model.search([
('search_relation_type_id', '=', 'unknown relation')
])
self.assertFalse(partners)
partners = self.partner_model.search([
('search_relation_partner_id', '=', self.partner_02_company.id),
])
self.assertTrue(self.partner_01_person in partners)
partners = self.partner_model.search([
('search_relation_date', '=', fields.Date.today()),
])
self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners)
def test_relation_all(self):
"""Test interactions through res.partner.relation.all."""
# Check wether we can create connection from company to person,
# taking the particular company from the active records:
relation_all_record = self.relation_all_model.with_context(
active_id=self.partner_02_company.id,
active_ids=self.partner_02_company.ids,
).create({
'other_partner_id': self.partner_01_person.id,
'type_selection_id': self.selection_company2person.id,
})
# Check wether display name is what we should expect:
self.assertEqual(
relation_all_record.display_name, '%s %s %s' % (
self.partner_02_company.name,
self.selection_company2person.name,
self.partner_01_person.name,
)
)
# Check wether the inverse record is present and looks like expected:
inverse_relation = self.relation_all_model.search([
('this_partner_id', '=', self.partner_01_person.id),
('other_partner_id', '=', self.partner_02_company.id),
])
self.assertEqual(len(inverse_relation), 1)
self.assertEqual(
inverse_relation.type_selection_id.name,
self.selection_person2company.name
)
# Check wether on_change_type_selection works as expected:
domain = relation_all_record.onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', False) in domain['other_partner_id']
)
domain = relation_all_record.onchange_partner_id()['domain']
self.assertTrue(
('contact_type_this', '=', 'c') in domain['type_selection_id']
)
relation_all_record.write({
'type_id': self.type_company2person.id,
})
# Check wether underlying record is removed when record is removed:
relation = relation_all_record.relation_id
relation_all_record.unlink()
self.assertFalse(relation.exists())
def test_symmetric(self): def test_symmetric(self):
"""Test creating symmetric relation.""" """Test creating symmetric relation."""
# Start out with non symmetric relation: # Start out with non symmetric relation:
@ -323,41 +172,18 @@ class TestPartnerRelation(common.TransactionCase):
'left_partner_id': self.partner_03_ngo.id, 'left_partner_id': self.partner_03_ngo.id,
'right_partner_id': self.partner_01_person.id, 'right_partner_id': self.partner_01_person.id,
}) })
# Creating a relation with a type referring to a certain category
# should only allow partners for that category.
relation_all_record = self.relation_all_model.create({
'this_partner_id': self.partner_03_ngo.id,
'type_selection_id': self.selection_ngo2volunteer.id,
'other_partner_id': self.partner_04_volunteer.id,
})
# Check wether on_change_type_selection works as expected:
domain = relation_all_record.onchange_type_selection_id()['domain']
self.assertTrue(
('category_id', 'in', [self.category_01_ngo.id]) in
domain['this_partner_id']
)
self.assertTrue(
('category_id', 'in', [self.category_02_volunteer.id]) in
domain['other_partner_id']
)
def test_relation_type_change(self): def test_relation_type_change(self):
"""Test change in relation type conditions.""" """Test change in relation type conditions."""
# First create a relation type having no particular conditions. # First create a relation type having no particular conditions.
type_school2student = self.type_model.create({
'name': 'school has student',
'name_inverse': 'studies at school',
})
selection_types = self.selection_model.search([
('type_id', '=', type_school2student.id),
])
for st in selection_types:
if st.is_inverse:
student2school = st
else:
school2student = st
self.assertTrue(school2student)
self.assertTrue(student2school)
(type_school2student,
school2student,
school2student_inverse) = (
self._create_relation_type_selection({
'name': 'school has student',
'name_inverse': 'studies at school',
})
)
# Second create relations based on those conditions. # Second create relations based on those conditions.
partner_school = self.partner_model.create({ partner_school = self.partner_model.create({
'name': 'Test School', 'name': 'Test School',
@ -375,20 +201,20 @@ class TestPartnerRelation(common.TransactionCase):
'ref': 'LS', 'ref': 'LS',
}) })
relation_school2bart = self.relation_all_model.create({ relation_school2bart = self.relation_all_model.create({
'type_selection_id': school2student.id,
'this_partner_id': partner_school.id, 'this_partner_id': partner_school.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_bart.id, 'other_partner_id': partner_bart.id,
}) })
self.assertTrue(relation_school2bart) self.assertTrue(relation_school2bart)
relation_school2lisa = self.relation_all_model.create({ relation_school2lisa = self.relation_all_model.create({
'type_selection_id': school2student.id,
'this_partner_id': partner_school.id, 'this_partner_id': partner_school.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_lisa.id, 'other_partner_id': partner_lisa.id,
}) })
self.assertTrue(relation_school2lisa) self.assertTrue(relation_school2lisa)
relation_bart2lisa = self.relation_all_model.create({ relation_bart2lisa = self.relation_all_model.create({
'type_selection_id': school2student.id,
'this_partner_id': partner_bart.id, 'this_partner_id': partner_bart.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_lisa.id, 'other_partner_id': partner_lisa.id,
}) })
self.assertTrue(relation_bart2lisa) self.assertTrue(relation_bart2lisa)
@ -421,6 +247,22 @@ class TestPartnerRelation(common.TransactionCase):
partner_lisa.write({ partner_lisa.write({
'category_id': [(4, category_student.id)], 'category_id': [(4, category_student.id)],
}) })
# Future student to be deleted by end action:
partner_homer = self.partner_model.create({
'name': 'Homer Simpson',
'is_company': False,
'ref': 'HS',
'category_id': [(4, category_student.id)],
})
relation_lisa2homer = self.relation_all_model.create({
'this_partner_id': partner_lisa.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_homer.id,
'date_start': fields.Date.to_string(
date.today() + relativedelta(months=+6)
),
})
self.assertTrue(relation_lisa2homer)
type_school2student.write({ type_school2student.write({
'handle_invalid_onchange': 'end', 'handle_invalid_onchange': 'end',
'contact_type_left': 'c', 'contact_type_left': 'c',
@ -429,8 +271,10 @@ class TestPartnerRelation(common.TransactionCase):
relation_bart2lisa.date_end, relation_bart2lisa.date_end,
fields.Date.today() fields.Date.today()
) )
self.assertFalse(relation_lisa2homer.exists())
type_school2student.write({ type_school2student.write({
'handle_invalid_onchange': 'delete', 'handle_invalid_onchange': 'delete',
'contact_type_left': 'c', 'contact_type_left': 'c',
'contact_type_right': 'p',
}) })
self.assertFalse(relation_bart2lisa.exists()) self.assertFalse(relation_bart2lisa.exists())

241
partner_relations/tests/test_partner_relation_all.py

@ -0,0 +1,241 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.exceptions import ValidationError
from .test_partner_relation_common import TestPartnerRelationCommon
class TestPartnerRelation(TestPartnerRelationCommon):
def setUp(self):
super(TestPartnerRelation, self).setUp()
# Create a new relation type which will not have valid relations:
category_nobody = self.category_model.create({
'name': 'Nobody',
})
(self.type_nobody,
self.selection_nobody,
self.selection_nobody_inverse) = (
self._create_relation_type_selection({
'name': 'has relation with nobody',
'name_inverse': 'nobody has relation with',
'contact_type_left': 'c',
'contact_type_right': 'p',
'partner_category_left': category_nobody.id,
'partner_category_right': category_nobody.id,
})
)
def _get_empty_relation(self):
"""Get empty relation record for onchange tests."""
# Need English, because we will compare text
return self.relation_all_model.with_context(lang='en_US').new({})
def test_create_with_active_id(self):
"""Test creation with this_partner_id from active_id."""
# Check wether we can create connection from company to person,
# taking the particular company from the active records:
relation = self.relation_all_model.with_context(
active_id=self.partner_02_company.id,
active_ids=self.partner_02_company.ids,
).create({
'other_partner_id': self.partner_01_person.id,
'type_selection_id': self.selection_company2person.id,
})
self.assertTrue(relation)
self.assertEqual(relation.this_partner_id, self.partner_02_company)
# Partner should have one relation now:
self.assertEqual(self.partner_01_person.relation_count, 1)
def test_display_name(self):
"""Test display name"""
relation = self._create_company2person_relation()
self.assertEqual(
relation.display_name, '%s %s %s' % (
relation.this_partner_id.name,
relation.type_selection_id.name,
relation.other_partner_id.name,
)
)
def test__regular_write(self):
"""Test write with valid data."""
relation = self._create_company2person_relation()
relation.write({
'date_start': '2014-09-01',
})
relation.invalidate_cache(ids=relation.ids)
self.assertEqual(relation.date_start, '2014-09-01')
def test_write_incompatible_dates(self):
"""Test write with date_end before date_start."""
relation = self._create_company2person_relation()
with self.assertRaises(ValidationError):
relation.write({
'date_start': '2016-09-01',
'date_end': '2016-08-01',
})
def test_validate_overlapping_01(self):
"""Test create overlapping with no start / end dates."""
relation = self._create_company2person_relation()
with self.assertRaises(ValidationError):
# New relation with no start / end should give error
self.relation_all_model.create({
'this_partner_id': relation.this_partner_id.id,
'type_selection_id': relation.type_selection_id.id,
'other_partner_id': relation.other_partner_id.id,
})
def test_validate_overlapping_02(self):
"""Test create overlapping with start / end dates."""
relation = self.relation_all_model.create({
'this_partner_id': self.partner_02_company.id,
'type_selection_id': self.selection_company2person.id,
'other_partner_id': self.partner_01_person.id,
'date_start': '2015-09-01',
'date_end': '2016-08-31',
})
# New relation with overlapping start / end should give error
with self.assertRaises(ValidationError):
self.relation_all_model.create({
'this_partner_id': relation.this_partner_id.id,
'type_selection_id': relation.type_selection_id.id,
'other_partner_id': relation.other_partner_id.id,
'date_start': '2016-08-01',
'date_end': '2017-07-30',
})
def test_validate_overlapping_03(self):
"""Test create not overlapping."""
relation = self.relation_all_model.create({
'this_partner_id': self.partner_02_company.id,
'type_selection_id': self.selection_company2person.id,
'other_partner_id': self.partner_01_person.id,
'date_start': '2015-09-01',
'date_end': '2016-08-31',
})
relation_another_record = self.relation_all_model.create({
'this_partner_id': relation.this_partner_id.id,
'type_selection_id': relation.type_selection_id.id,
'other_partner_id': relation.other_partner_id.id,
'date_start': '2016-09-01',
'date_end': '2017-08-31',
})
self.assertTrue(relation_another_record)
def test_inverse_record(self):
"""Test creation of inverse record."""
relation = self._create_company2person_relation()
inverse_relation = self.relation_all_model.search([
('this_partner_id', '=', relation.other_partner_id.id),
('other_partner_id', '=', relation.this_partner_id.id),
])
self.assertEqual(len(inverse_relation), 1)
self.assertEqual(
inverse_relation.type_selection_id.name,
self.selection_person2company.name
)
def test_inverse_creation(self):
"""Test creation of record through inverse selection."""
relation = self.relation_all_model.create({
'this_partner_id': self.partner_01_person.id,
'type_selection_id': self.selection_person2company.id,
'other_partner_id': self.partner_02_company.id,
})
# Check wether display name is what we should expect:
self.assertEqual(
relation.display_name, '%s %s %s' % (
self.partner_01_person.name,
self.selection_person2company.name,
self.partner_02_company.name,
)
)
def test_unlink(self):
"""Unlinking derived relation should unlink base relation."""
# Check wether underlying record is removed when record is removed:
relation = self._create_company2person_relation()
base_relation = relation.relation_id
relation.unlink()
self.assertFalse(base_relation.exists())
def test_on_change_type_selection(self):
"""Test on_change_type_selection."""
# 1. Test call with empty relation
relation_empty = self._get_empty_relation()
result = relation_empty.onchange_type_selection_id()
self.assertTrue('domain' in result)
self.assertFalse('warning' in result)
self.assertTrue('this_partner_id' in result['domain'])
self.assertFalse(result['domain']['this_partner_id'])
self.assertTrue('other_partner_id' in result['domain'])
self.assertFalse(result['domain']['other_partner_id'])
# 2. Test call with company 2 person relation
relation = self._create_company2person_relation()
domain = relation.onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', False) in domain['other_partner_id']
)
# 3. Test with relation needing categories.
relation_ngo_volunteer = self.relation_all_model.create({
'this_partner_id': self.partner_03_ngo.id,
'type_selection_id': self.selection_ngo2volunteer.id,
'other_partner_id': self.partner_04_volunteer.id,
})
domain = relation_ngo_volunteer.onchange_type_selection_id()['domain']
self.assertTrue(
('category_id', 'in', [self.category_01_ngo.id]) in
domain['this_partner_id']
)
self.assertTrue(
('category_id', 'in', [self.category_02_volunteer.id]) in
domain['other_partner_id']
)
# 4. Test with invalid or impossible combinations
relation_nobody = self._get_empty_relation()
with self.env.do_in_draft():
relation_nobody.type_selection_id = self.selection_nobody
warning = relation_nobody.onchange_type_selection_id()['warning']
self.assertTrue('message' in warning)
self.assertTrue('No this partner available' in warning['message'])
with self.env.do_in_draft():
relation_nobody.this_partner_id = self.partner_02_company
warning = relation_nobody.onchange_type_selection_id()['warning']
self.assertTrue('message' in warning)
self.assertTrue('incompatible' in warning['message'])
# Allow left partner and check message for other partner:
self.type_nobody.write({
'partner_category_left': False,
})
self.selection_nobody.invalidate_cache(ids=self.selection_nobody.ids)
warning = relation_nobody.onchange_type_selection_id()['warning']
self.assertTrue('message' in warning)
self.assertTrue('No other partner available' in warning['message'])
def test_on_change_partner_id(self):
"""Test on_change_partner_id."""
# 1. Test call with empty relation
relation_empty = self._get_empty_relation()
result = relation_empty.onchange_partner_id()
self.assertTrue('domain' in result)
self.assertFalse('warning' in result)
self.assertTrue('type_selection_id' in result['domain'])
self.assertFalse(result['domain']['type_selection_id'])
# 2. Test call with company 2 person relation
relation = self._create_company2person_relation()
domain = relation.onchange_partner_id()['domain']
self.assertTrue(
('contact_type_this', '=', 'c') in domain['type_selection_id']
)
# 3. Test with invalid or impossible combinations
relation_nobody = self._get_empty_relation()
with self.env.do_in_draft():
relation_nobody.this_partner_id = self.partner_02_company
relation_nobody.type_selection_id = self.selection_nobody
warning = relation_nobody.onchange_partner_id()['warning']
self.assertTrue('message' in warning)
self.assertTrue('incompatible' in warning['message'])

113
partner_relations/tests/test_partner_relation_common.py

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tests import common
class TestPartnerRelationCommon(common.TransactionCase):
def setUp(self):
super(TestPartnerRelationCommon, self).setUp()
self.partner_model = self.env['res.partner']
self.category_model = self.env['res.partner.category']
self.type_model = self.env['res.partner.relation.type']
self.selection_model = self.env['res.partner.relation.type.selection']
self.relation_model = self.env['res.partner.relation']
self.relation_all_model = self.env['res.partner.relation.all']
self.partner_01_person = self.partner_model.create({
'name': 'Test User 1',
'is_company': False,
'ref': 'PR01',
})
self.partner_02_company = self.partner_model.create({
'name': 'Test Company',
'is_company': True,
'ref': 'PR02',
})
# Create partners with specific categories:
self.category_01_ngo = self.category_model.create({
'name': 'NGO',
})
self.partner_03_ngo = self.partner_model.create({
'name': 'Test NGO',
'is_company': True,
'ref': 'PR03',
'category_id': [(4, self.category_01_ngo.id)],
})
self.category_02_volunteer = self.category_model.create({
'name': 'Volunteer',
})
self.partner_04_volunteer = self.partner_model.create({
'name': 'Test Volunteer',
'is_company': False,
'ref': 'PR04',
'category_id': [(4, self.category_02_volunteer.id)],
})
# Create a new relation type withouth categories:
(self.type_company2person,
self.selection_company2person,
self.selection_person2company) = (
self._create_relation_type_selection({
'name': 'mixed',
'name_inverse': 'mixed_inverse',
'contact_type_left': 'c',
'contact_type_right': 'p',
})
)
# Create a new relation type with categories:
(self.type_ngo2volunteer,
self.selection_ngo2volunteer,
self.selection_volunteer2ngo) = (
self._create_relation_type_selection({
'name': 'NGO has volunteer',
'name_inverse': 'volunteer works for NGO',
'contact_type_left': 'c',
'contact_type_right': 'p',
'partner_category_left': self.category_01_ngo.id,
'partner_category_right': self.category_02_volunteer.id,
})
)
def _create_relation_type_selection(self, vals):
"""Create relation type and return this with selection types."""
assert 'name' in vals, (
"Name missing in vals to create relation type. Vals: %s."
% vals
)
assert 'name' in vals, (
"Name_inverse missing in vals to create relation type. Vals: %s."
% vals
)
new_type = self.type_model.create(vals)
self.assertTrue(
new_type,
msg="No relation type created with vals %s." % vals
)
selection_types = self.selection_model.search([
('type_id', '=', new_type.id),
])
for st in selection_types:
if st.is_inverse:
inverse_type_selection = st
else:
type_selection = st
self.assertTrue(
inverse_type_selection,
msg="Failed to find inverse type selection based on"
" relation type created with vals %s." % vals
)
self.assertTrue(
type_selection,
msg="Failed to find type selection based on"
" relation type created with vals %s." % vals
)
return (new_type, type_selection, inverse_type_selection)
def _create_company2person_relation(self):
"""Utility function to get a relation from company 2 partner."""
return self.relation_all_model.create({
'type_selection_id': self.selection_company2person.id,
'this_partner_id': self.partner_02_company.id,
'other_partner_id': self.partner_01_person.id,
})

76
partner_relations/tests/test_partner_search.py

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
# Copyright 2015 Camptocamp SA
# Copyright 2016 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields
from openerp.exceptions import ValidationError
from .test_partner_relation_common import TestPartnerRelationCommon
class TestPartnerSearch(TestPartnerRelationCommon):
def test_search_relation_type(self):
"""Test searching on relation type."""
relation = self._create_company2person_relation()
partners = self.partner_model.search([
('search_relation_type_id', '=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_02_company in partners)
partners = self.partner_model.search([
('search_relation_type_id', '!=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_01_person in partners)
partners = self.partner_model.search([
('search_relation_type_id', '=', self.type_company2person.name)
])
self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners)
partners = self.partner_model.search([
('search_relation_type_id', '=', 'unknown relation')
])
self.assertFalse(partners)
# Check error with invalid search operator:
with self.assertRaises(ValidationError):
partners = self.partner_model.search([
('search_relation_type_id', 'child_of', 'some parent')
])
def test_search_relation_partner(self):
"""Test searching on related partner."""
self._create_company2person_relation()
partners = self.partner_model.search([
('search_relation_partner_id', '=', self.partner_02_company.id),
])
self.assertTrue(self.partner_01_person in partners)
def test_search_relation_date(self):
"""Test searching on relations valid on a certain date."""
self._create_company2person_relation()
partners = self.partner_model.search([
('search_relation_date', '=', fields.Date.today()),
])
self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners)
def test_search_any_partner(self):
"""Test searching for partner left or right."""
self._create_company2person_relation()
both_relations = self.relation_all_model.search([
('any_partner_id', '=', self.partner_02_company.id),
])
self.assertEqual(len(both_relations), 2)
def test_search_partner_category(self):
"""Test searching for partners related to partners having category."""
relation_ngo_volunteer = self.relation_all_model.create({
'this_partner_id': self.partner_03_ngo.id,
'type_selection_id': self.selection_ngo2volunteer.id,
'other_partner_id': self.partner_04_volunteer.id,
})
self.assertTrue(relation_ngo_volunteer)
partners = self.partner_model.search([
('search_relation_partner_category_id', '=',
self.category_02_volunteer.id)
])
self.assertTrue(self.partner_03_ngo in partners)
Loading…
Cancel
Save