Browse Source

[MIG] partner_multi_relation. Black, isort, pretty...

14.0
Ronald Portier 4 years ago
committed by Raf Ven
parent
commit
2dc738c623
  1. 19
      partner_multi_relation/__manifest__.py
  2. 215
      partner_multi_relation/data/demo.xml
  3. 181
      partner_multi_relation/models/res_partner.py
  4. 94
      partner_multi_relation/models/res_partner_relation.py
  5. 329
      partner_multi_relation/models/res_partner_relation_all.py
  6. 190
      partner_multi_relation/models/res_partner_relation_type.py
  7. 79
      partner_multi_relation/models/res_partner_relation_type_selection.py
  8. 2
      partner_multi_relation/readme/USAGE.rst
  9. 524
      partner_multi_relation/tests/test_partner_relation.py
  10. 327
      partner_multi_relation/tests/test_partner_relation_all.py
  11. 138
      partner_multi_relation/tests/test_partner_relation_common.py
  12. 74
      partner_multi_relation/tests/test_partner_search.py
  13. 55
      partner_multi_relation/views/menu.xml
  14. 78
      partner_multi_relation/views/res_partner.xml
  15. 174
      partner_multi_relation/views/res_partner_relation_all.xml
  16. 95
      partner_multi_relation/views/res_partner_relation_type.xml

19
partner_multi_relation/__manifest__.py

@ -2,25 +2,20 @@
# 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).
{ {
"name": "Partner Relations", "name": "Partner Relations",
"version": "12.0.1.2.1",
"version": "13.0.1.0.0",
"author": "Therp BV,Camptocamp,Odoo Community Association (OCA)", "author": "Therp BV,Camptocamp,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/partner-contact", "website": "https://github.com/OCA/partner-contact",
"complexity": "normal", "complexity": "normal",
"category": "Customer Relationship Management", "category": "Customer Relationship Management",
"license": "AGPL-3", "license": "AGPL-3",
"depends": [
'contacts',
'sales_team',
],
"demo": [
"data/demo.xml",
],
"depends": ["contacts", "sales_team"],
"demo": ["data/demo.xml"],
"data": [ "data": [
'security/ir.model.access.csv',
"security/ir.model.access.csv",
"views/res_partner_relation_all.xml", "views/res_partner_relation_all.xml",
'views/res_partner.xml',
'views/res_partner_relation_type.xml',
'views/menu.xml',
"views/res_partner.xml",
"views/res_partner_relation_type.xml",
"views/menu.xml",
], ],
"auto_install": False, "auto_install": False,
"installable": True, "installable": True,

215
partner_multi_relation/data/demo.xml

@ -1,116 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<!-- Added partner categories and partners to this file, because it
<!-- Added partner categories and partners to this file, because it
turned out to be a bad idea to rely on demo data in base module, turned out to be a bad idea to rely on demo data in base module,
that can change from release to release. Only dependency on that can change from release to release. Only dependency on
countries remain. They are less likely to change/disappear. countries remain. They are less likely to change/disappear.
--> -->
<!-- Partner relation types -->
<record id="rel_type_assistant" model="res.partner.relation.type">
<field name="name">Is assistant of</field>
<field name="name_inverse">Has assistant</field>
<field name="contact_type_left">p</field>
<field name="contact_type_right">p</field>
</record>
<record id="rel_type_competitor" model="res.partner.relation.type">
<field name="name">Is competitor of</field>
<field name="name_inverse">Is competitor of</field>
<field name="contact_type_left">c</field>
<field name="contact_type_right">c</field>
<field name="is_symmetric" eval="True" />
</record>
<record id="rel_type_has_worked_for" model="res.partner.relation.type">
<field name="name">Has worked for</field>
<field name="name_inverse">Has former employee</field>
<field name="contact_type_left">p</field>
<field name="contact_type_right">c</field>
</record>
<!-- Categories -->
<record id="res_partner_category_pmr_0" model="res.partner.category">
<field name="name">Washing Companies</field>
</record>
<record id="res_partner_category_pmr_4" model="res.partner.category">
<field name="name">Washing Gold</field>
<field name="parent_id" ref="res_partner_category_pmr_0"/>
</record>
<record id="res_partner_category_pmr_5" model="res.partner.category">
<field name="name">Washing Silver</field>
<field name="parent_id" ref="res_partner_category_pmr_0"/>
</record>
<record id="res_partner_category_pmr_11" model="res.partner.category">
<field name="name">Washing Services</field>
<field name="parent_id" ref="res_partner_category_pmr_0"/>
</record>
<!-- Partners -->
<record id="res_partner_pmr_great" model="res.partner">
<field name="name">Great Washing Powder Company</field>
<field
name="category_id"
eval="[(6, 0, [ref('res_partner_category_pmr_11'), ref('res_partner_category_pmr_4')])]"
/>
<field name="supplier">1</field>
<field name="customer">1</field>
<field name="is_company">1</field>
<field name="city">Le Bourget du Lac</field>
<field name="zip">73377</field>
<field name="phone">+33 4 49 23 44 54</field>
<field name="country_id" ref="base.fr"/>
<field name="street">93, Press Avenue</field>
<field name="email">great@yourcompany.example.com</field>
<field name="website">http://www.great.com</field>
</record>
<record id="res_partner_pmr_best" model="res.partner">
<field name="name">Best Washing Powder Company</field>
<field
name="category_id"
eval="[(6, 0, [ref('res_partner_category_pmr_4'), ref('res_partner_category_pmr_11')])]"
/>
<field name="supplier">1</field>
<field name="is_company">1</field>
<field name="city">Champs sur Marne</field>
<field name="zip">77420</field>
<field name="country_id" ref="base.fr"/>
<field name="email">best@yourcompany.example.com</field>
<field name="phone">+33 1 64 61 04 01</field>
<field name="street">12 rue Albert Einstein</field>
<field name="website">http://www.best.com/</field>
</record>
<record id="res_partner_pmr_super" model="res.partner">
<field name="name">Super Washing Powder Company</field>
<field
name="category_id"
eval="[(6,0,[ref('res_partner_category_pmr_5')])]"
/>
<field name="supplier">1</field>
<field eval="1" name="customer"/>
<field name="is_company">1</field>
<field name="street">3rd Floor, Room 3-C,</field>
<field name="street2">Carretera Panamericana, Km 1, Urb. Delgado Chalbaud</field>
<field name="city">Caracas</field>
<field name="zip">1090</field>
<field name="email">super@yourcompany.example.com</field>
<field name="phone">+58 212 681 0538</field>
<field name="country_id" ref="base.ve"/>
<field name="website">super.com</field>
</record>
<!-- Relations -->
<record id="rel_1" model="res.partner.relation">
<field name="left_partner_id" ref="res_partner_pmr_great" />
<field name="right_partner_id" ref="res_partner_pmr_super" />
<field name="type_id" ref="rel_type_competitor" />
</record>
<record id="rel_2" model="res.partner.relation">
<field name="left_partner_id" ref="res_partner_pmr_best" />
<field name="right_partner_id" ref="res_partner_pmr_super" />
<field name="type_id" ref="rel_type_competitor" />
</record>
<record id="rel_3" model="res.partner.relation">
<field name="left_partner_id" ref="res_partner_pmr_great" />
<field name="right_partner_id" ref="res_partner_pmr_best" />
<field name="type_id" ref="rel_type_competitor" />
</record>
<!-- Partner relation types -->
<record id="rel_type_assistant" model="res.partner.relation.type">
<field name="name">Is assistant of</field>
<field name="name_inverse">Has assistant</field>
<field name="contact_type_left">p</field>
<field name="contact_type_right">p</field>
</record>
<record id="rel_type_competitor" model="res.partner.relation.type">
<field name="name">Is competitor of</field>
<field name="name_inverse">Is competitor of</field>
<field name="contact_type_left">c</field>
<field name="contact_type_right">c</field>
<field name="is_symmetric" eval="True" />
</record>
<record id="rel_type_has_worked_for" model="res.partner.relation.type">
<field name="name">Has worked for</field>
<field name="name_inverse">Has former employee</field>
<field name="contact_type_left">p</field>
<field name="contact_type_right">c</field>
</record>
<!-- Categories -->
<record id="res_partner_category_pmr_0" model="res.partner.category">
<field name="name">Washing Companies</field>
</record>
<record id="res_partner_category_pmr_4" model="res.partner.category">
<field name="name">Washing Gold</field>
<field name="parent_id" ref="res_partner_category_pmr_0" />
</record>
<record id="res_partner_category_pmr_5" model="res.partner.category">
<field name="name">Washing Silver</field>
<field name="parent_id" ref="res_partner_category_pmr_0" />
</record>
<record id="res_partner_category_pmr_11" model="res.partner.category">
<field name="name">Washing Services</field>
<field name="parent_id" ref="res_partner_category_pmr_0" />
</record>
<!-- Partners -->
<record id="res_partner_pmr_great" model="res.partner">
<field name="name">Great Washing Powder Company</field>
<field
name="category_id"
eval="[(6, 0, [ref('res_partner_category_pmr_11'), ref('res_partner_category_pmr_4')])]"
/>
<field name="supplier">1</field>
<field name="customer">1</field>
<field name="is_company">1</field>
<field name="city">Le Bourget du Lac</field>
<field name="zip">73377</field>
<field name="phone">+33 4 49 23 44 54</field>
<field name="country_id" ref="base.fr" />
<field name="street">93, Press Avenue</field>
<field name="email">great@yourcompany.example.com</field>
<field name="website">http://www.great.com</field>
</record>
<record id="res_partner_pmr_best" model="res.partner">
<field name="name">Best Washing Powder Company</field>
<field
name="category_id"
eval="[(6, 0, [ref('res_partner_category_pmr_4'), ref('res_partner_category_pmr_11')])]"
/>
<field name="supplier">1</field>
<field name="is_company">1</field>
<field name="city">Champs sur Marne</field>
<field name="zip">77420</field>
<field name="country_id" ref="base.fr" />
<field name="email">best@yourcompany.example.com</field>
<field name="phone">+33 1 64 61 04 01</field>
<field name="street">12 rue Albert Einstein</field>
<field name="website">http://www.best.com/</field>
</record>
<record id="res_partner_pmr_super" model="res.partner">
<field name="name">Super Washing Powder Company</field>
<field name="category_id" eval="[(6,0,[ref('res_partner_category_pmr_5')])]" />
<field name="supplier">1</field>
<field eval="1" name="customer" />
<field name="is_company">1</field>
<field name="street">3rd Floor, Room 3-C,</field>
<field
name="street2"
>Carretera Panamericana, Km 1, Urb. Delgado Chalbaud</field>
<field name="city">Caracas</field>
<field name="zip">1090</field>
<field name="email">super@yourcompany.example.com</field>
<field name="phone">+58 212 681 0538</field>
<field name="country_id" ref="base.ve" />
<field name="website">super.com</field>
</record>
<!-- Relations -->
<record id="rel_1" model="res.partner.relation">
<field name="left_partner_id" ref="res_partner_pmr_great" />
<field name="right_partner_id" ref="res_partner_pmr_super" />
<field name="type_id" ref="rel_type_competitor" />
</record>
<record id="rel_2" model="res.partner.relation">
<field name="left_partner_id" ref="res_partner_pmr_best" />
<field name="right_partner_id" ref="res_partner_pmr_super" />
<field name="type_id" ref="rel_type_competitor" />
</record>
<record id="rel_3" model="res.partner.relation">
<field name="left_partner_id" ref="res_partner_pmr_great" />
<field name="right_partner_id" ref="res_partner_pmr_best" />
<field name="type_id" ref="rel_type_competitor" />
</record>
</odoo> </odoo>

181
partner_multi_relation/models/res_partner.py

@ -4,51 +4,51 @@
import numbers import numbers
from odoo import _, api, exceptions, fields, models from odoo import _, api, exceptions, fields, models
from odoo.osv.expression import is_leaf, OR, FALSE_LEAF
from odoo.osv.expression import FALSE_LEAF, OR, is_leaf
class ResPartner(models.Model): class ResPartner(models.Model):
"""Extend partner with relations and allow to search for relations """Extend partner with relations and allow to search for relations
in various ways. in various ways.
""" """
# pylint: disable=invalid-name # pylint: disable=invalid-name
# pylint: disable=no-member # pylint: disable=no-member
_inherit = 'res.partner'
_inherit = "res.partner"
relation_count = fields.Integer( relation_count = fields.Integer(
string='Relation Count',
compute="_compute_relation_count"
string="Relation Count", compute="_compute_relation_count"
) )
relation_all_ids = fields.One2many( relation_all_ids = fields.One2many(
comodel_name='res.partner.relation.all',
inverse_name='this_partner_id',
string='All relations with current partner',
comodel_name="res.partner.relation.all",
inverse_name="this_partner_id",
string="All relations with current partner",
auto_join=True, auto_join=True,
selectable=False, selectable=False,
copy=False, copy=False,
) )
search_relation_type_id = fields.Many2one( search_relation_type_id = fields.Many2one(
comodel_name='res.partner.relation.type.selection',
comodel_name="res.partner.relation.type.selection",
compute=lambda self: None, compute=lambda self: None,
search='_search_relation_type_id',
string='Has relation of type',
search="_search_relation_type_id",
string="Has relation of type",
) )
search_relation_partner_id = fields.Many2one( search_relation_partner_id = fields.Many2one(
comodel_name='res.partner',
comodel_name="res.partner",
compute=lambda self: None, compute=lambda self: None,
search='_search_related_partner_id',
string='Has relation with',
search="_search_related_partner_id",
string="Has relation with",
) )
search_relation_date = fields.Date( search_relation_date = fields.Date(
compute=lambda self: None, compute=lambda self: None,
search='_search_relation_date',
string='Relation valid',
search="_search_relation_date",
string="Relation valid",
) )
search_relation_partner_category_id = fields.Many2one( search_relation_partner_category_id = fields.Many2one(
comodel_name='res.partner.category',
comodel_name="res.partner.category",
compute=lambda self: None, compute=lambda self: None,
search='_search_related_partner_category_id',
string='Has relation with a partner in category',
search="_search_related_partner_category_id",
string="Has relation with a partner in category",
) )
@api.depends("relation_all_ids") @api.depends("relation_all_ids")
@ -58,80 +58,78 @@ class ResPartner(models.Model):
Don't count inactive relations. Don't count inactive relations.
""" """
for rec in self: for rec in self:
rec.relation_count = len(rec.relation_all_ids.filtered('active'))
rec.relation_count = len(rec.relation_all_ids.filtered("active"))
@api.model @api.model
def _search_relation_type_id(self, operator, value): def _search_relation_type_id(self, operator, value):
"""Search partners based on their type of relations.""" """Search partners based on their type of relations."""
result = [] result = []
SUPPORTED_OPERATORS = ( SUPPORTED_OPERATORS = (
'=',
'!=',
'like',
'not like',
'ilike',
'not ilike',
'in',
'not in',
"=",
"!=",
"like",
"not like",
"ilike",
"not ilike",
"in",
"not in",
) )
if operator not in SUPPORTED_OPERATORS: if operator not in SUPPORTED_OPERATORS:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_('Unsupported search operator "%s"') % operator)
type_selection_model = self.env['res.partner.relation.type.selection']
_('Unsupported search operator "%s"') % operator
)
type_selection_model = self.env["res.partner.relation.type.selection"]
relation_type_selection = [] relation_type_selection = []
if operator == '=' and isinstance(value, numbers.Integral):
if operator == "=" and isinstance(value, numbers.Integral):
relation_type_selection += type_selection_model.browse(value) relation_type_selection += type_selection_model.browse(value)
elif operator == '!=' and isinstance(value, numbers.Integral):
relation_type_selection = type_selection_model.search([
('id', operator, value),
])
elif operator == "!=" and isinstance(value, numbers.Integral):
relation_type_selection = type_selection_model.search(
[("id", operator, value)]
)
else: else:
relation_type_selection = type_selection_model.search([
'|',
('type_id.name', operator, value),
('type_id.name_inverse', operator, value),
])
relation_type_selection = type_selection_model.search(
[
"|",
("type_id.name", operator, value),
("type_id.name_inverse", operator, value),
]
)
if not relation_type_selection: if not relation_type_selection:
result = [FALSE_LEAF] result = [FALSE_LEAF]
for relation_type in relation_type_selection: for relation_type in relation_type_selection:
result = OR([
result,
result = OR(
[ [
('relation_all_ids.type_selection_id.id', '=',
relation_type.id),
],
])
result,
[("relation_all_ids.type_selection_id.id", "=", relation_type.id)],
]
)
return result return result
@api.model @api.model
def _search_related_partner_id(self, operator, value): def _search_related_partner_id(self, operator, value):
"""Find partner based on relation with other partner.""" """Find partner based on relation with other partner."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
return [
('relation_all_ids.other_partner_id', operator, value),
]
return [("relation_all_ids.other_partner_id", operator, value)]
@api.model @api.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
return [ return [
'&',
'|',
('relation_all_ids.date_start', '=', False),
('relation_all_ids.date_start', '<=', value),
'|',
('relation_all_ids.date_end', '=', False),
('relation_all_ids.date_end', '>=', value),
"&",
"|",
("relation_all_ids.date_start", "=", False),
("relation_all_ids.date_start", "<=", value),
"|",
("relation_all_ids.date_end", "=", False),
("relation_all_ids.date_end", ">=", value),
] ]
@api.model @api.model
def _search_related_partner_category_id(self, operator, value): def _search_related_partner_category_id(self, operator, value):
"""Search for partner related to a partner with search category.""" """Search for partner related to a partner with search category."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
return [
('relation_all_ids.other_partner_id.category_id', operator, value),
]
return [("relation_all_ids.other_partner_id.category_id", operator, value)]
@api.model @api.model
def search(self, args, offset=0, limit=None, order=None, count=False): def search(self, args, offset=0, limit=None, order=None, count=False):
@ -142,26 +140,34 @@ class ResPartner(models.Model):
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
date_args = [] date_args = []
for arg in args: for arg in args:
if (is_leaf(arg) and isinstance(arg[0], str) and
arg[0].startswith('search_relation')):
if arg[0] == 'search_relation_date':
if (
is_leaf(arg)
and isinstance(arg[0], str)
and arg[0].startswith("search_relation")
):
if arg[0] == "search_relation_date":
date_args = [] date_args = []
break break
if not date_args: if not date_args:
date_args = [
('search_relation_date', '=', fields.Date.today()),
]
date_args = [("search_relation_date", "=", fields.Date.today())]
# because of auto_join, we have to do the active test by hand # because of auto_join, we have to do the active test by hand
active_args = [] active_args = []
if self.env.context.get('active_test', True):
if self.env.context.get("active_test", True):
for arg in args: for arg in args:
if (is_leaf(arg) and isinstance(arg[0], str) and
arg[0].startswith('search_relation')):
active_args = [('relation_all_ids.active', '=', True)]
if (
is_leaf(arg)
and isinstance(arg[0], str)
and arg[0].startswith("search_relation")
):
active_args = [("relation_all_ids.active", "=", True)]
break break
return super(ResPartner, self).search( return super(ResPartner, self).search(
args + date_args + active_args, offset=offset, limit=limit,
order=order, count=count)
args + date_args + active_args,
offset=offset,
limit=limit,
order=order,
count=count,
)
@api.multi @api.multi
def get_partner_type(self): def get_partner_type(self):
@ -170,28 +176,35 @@ class ResPartner(models.Model):
:rtype: str :rtype: str
""" """
self.ensure_one() self.ensure_one()
return 'c' if self.is_company else 'p'
return "c" if self.is_company else "p"
@api.multi @api.multi
def action_view_relations(self): def action_view_relations(self):
for contact in self: for contact in self:
relation_model = self.env['res.partner.relation.all']
relation_ids = relation_model.\
search(['|',
('this_partner_id', '=', contact.id),
('other_partner_id', '=', contact.id)])
relation_model = self.env["res.partner.relation.all"]
relation_ids = relation_model.search(
[
"|",
("this_partner_id", "=", contact.id),
("other_partner_id", "=", contact.id),
]
)
action = self.env.ref( action = self.env.ref(
'partner_multi_relation.action_res_partner_relation_all'
"partner_multi_relation.action_res_partner_relation_all"
).read()[0] ).read()[0]
action['domain'] = [('id', 'in', relation_ids.ids)]
context = action.get('context', '{}').strip()[1:-1]
elements = context.split(',') if context else []
to_add = ["""'search_default_this_partner_id': {0},
action["domain"] = [("id", "in", relation_ids.ids)]
context = action.get("context", "{}").strip()[1:-1]
elements = context.split(",") if context else []
to_add = [
"""'search_default_this_partner_id': {0},
'default_this_partner_id': {0}, 'default_this_partner_id': {0},
'active_model': 'res.partner', 'active_model': 'res.partner',
'active_id': {0}, 'active_id': {0},
'active_ids': [{0}], 'active_ids': [{0}],
'active_test': False""".format(contact.id)]
context = '{' + ', '.join(elements + to_add) + '}'
action['context'] = context
'active_test': False""".format(
contact.id
)
]
context = "{" + ", ".join(elements + to_add) + "}"
action["context"] = context
return action return action

94
partner_multi_relation/models/res_partner_relation.py

@ -15,54 +15,58 @@ class ResPartnerRelation(models.Model):
two times, once for the normal relation, once for the inverse relation, two times, once for the normal relation, once for the inverse relation,
will be used to maintain the data. will be used to maintain the data.
""" """
_name = 'res.partner.relation'
_description = 'Partner relation'
_name = "res.partner.relation"
_description = "Partner relation"
left_partner_id = fields.Many2one( left_partner_id = fields.Many2one(
comodel_name='res.partner',
string='Source Partner',
comodel_name="res.partner",
string="Source Partner",
required=True, required=True,
auto_join=True, auto_join=True,
ondelete='cascade',
ondelete="cascade",
) )
right_partner_id = fields.Many2one( right_partner_id = fields.Many2one(
comodel_name='res.partner',
string='Destination Partner',
comodel_name="res.partner",
string="Destination Partner",
required=True, required=True,
auto_join=True, auto_join=True,
ondelete='cascade',
ondelete="cascade",
) )
type_id = fields.Many2one( type_id = fields.Many2one(
comodel_name='res.partner.relation.type',
string='Type',
comodel_name="res.partner.relation.type",
string="Type",
required=True, required=True,
auto_join=True, auto_join=True,
) )
date_start = fields.Date('Starting date')
date_end = fields.Date('Ending date')
date_start = fields.Date("Starting date")
date_end = fields.Date("Ending date")
@api.model @api.model
def create(self, vals): def create(self, vals):
"""Override create to correct values, before being stored.""" """Override create to correct values, before being stored."""
context = self.env.context context = self.env.context
if 'left_partner_id' not in vals and context.get('active_id'):
vals['left_partner_id'] = context.get('active_id')
if "left_partner_id" not in vals and context.get("active_id"):
vals["left_partner_id"] = context.get("active_id")
return super(ResPartnerRelation, self).create(vals) return super(ResPartnerRelation, self).create(vals)
@api.constrains('date_start', 'date_end')
@api.constrains("date_start", "date_end")
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 ValidationError: When constraint is violated :raises ValidationError: When constraint is violated
""" """
for record in self: for record in self:
if (record.date_start and record.date_end and
record.date_start > record.date_end):
if (
record.date_start
and record.date_end
and record.date_start > record.date_end
):
raise ValidationError( raise ValidationError(
_('The starting date cannot be after the ending date.')
_("The starting date cannot be after the ending date.")
) )
@api.constrains('left_partner_id', 'type_id')
@api.constrains("left_partner_id", "type_id")
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
@ -70,7 +74,7 @@ class ResPartnerRelation(models.Model):
""" """
self._check_partner("left") self._check_partner("left")
@api.constrains('right_partner_id', 'type_id')
@api.constrains("right_partner_id", "type_id")
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
@ -86,23 +90,24 @@ class ResPartnerRelation(models.Model):
:raises ValidationError: When constraint is violated :raises ValidationError: When constraint is violated
""" """
for record in self: for record in self:
assert side in ['left', 'right']
assert side in ["left", "right"]
ptype = getattr(record.type_id, "contact_type_%s" % side) ptype = getattr(record.type_id, "contact_type_%s" % side)
partner = getattr(record, '%s_partner_id' % side)
if ((ptype == 'c' and not partner.is_company) or
(ptype == 'p' and partner.is_company)):
partner = getattr(record, "%s_partner_id" % side)
if (ptype == "c" and not partner.is_company) or (
ptype == "p" and partner.is_company
):
raise ValidationError( raise ValidationError(
_('The %s partner is not applicable for this '
'relation type.') % side
_("The %s partner is not applicable for this " "relation type.")
% side
) )
category = getattr(record.type_id, "partner_category_%s" % side) category = getattr(record.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 ValidationError( raise ValidationError(
_('The %s partner does not have category %s.') %
(side, category.name)
_("The %s partner does not have category %s.")
% (side, category.name)
) )
@api.constrains('left_partner_id', 'right_partner_id')
@api.constrains("left_partner_id", "right_partner_id")
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
@ -112,15 +117,11 @@ class ResPartnerRelation(models.Model):
if record.left_partner_id == record.right_partner_id: if record.left_partner_id == record.right_partner_id:
if not (record.type_id and record.type_id.allow_self): if not (record.type_id and record.type_id.allow_self):
raise ValidationError( raise ValidationError(
_('Partners cannot have a relation with themselves.')
_("Partners cannot have a relation with themselves.")
) )
@api.constrains( @api.constrains(
'left_partner_id',
'type_id',
'right_partner_id',
'date_start',
'date_end',
"left_partner_id", "type_id", "right_partner_id", "date_start", "date_end"
) )
def _check_relation_uniqueness(self): def _check_relation_uniqueness(self):
"""Forbid multiple active relations of the same type between the same """Forbid multiple active relations of the same type between the same
@ -132,25 +133,24 @@ class ResPartnerRelation(models.Model):
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
for record in self: for record in self:
domain = [ domain = [
('type_id', '=', record.type_id.id),
('id', '!=', record.id),
('left_partner_id', '=', record.left_partner_id.id),
('right_partner_id', '=', record.right_partner_id.id),
("type_id", "=", record.type_id.id),
("id", "!=", record.id),
("left_partner_id", "=", record.left_partner_id.id),
("right_partner_id", "=", record.right_partner_id.id),
] ]
if record.date_start: if record.date_start:
domain += [ domain += [
'|',
('date_end', '=', False),
('date_end', '>=', record.date_start),
"|",
("date_end", "=", False),
("date_end", ">=", record.date_start),
] ]
if record.date_end: if record.date_end:
domain += [ domain += [
'|',
('date_start', '=', False),
('date_start', '<=', record.date_end),
"|",
("date_start", "=", False),
("date_start", "<=", record.date_end),
] ]
if record.search(domain): if record.search(domain):
raise ValidationError( raise ValidationError(
_('There is already a similar relation with '
'overlapping dates')
_("There is already a similar relation with " "overlapping dates")
) )

329
partner_multi_relation/models/res_partner_relation_all.py

@ -10,7 +10,6 @@ from odoo import _, api, fields, models
from odoo.exceptions import MissingError, ValidationError from odoo.exceptions import MissingError, ValidationError
from odoo.tools import drop_view_if_exists from odoo.tools import drop_view_if_exists
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -47,97 +46,100 @@ FROM res_partner_relation rel"""
class ResPartnerRelationAll(models.AbstractModel): class ResPartnerRelationAll(models.AbstractModel):
"""Abstract model to show each relation from two sides.""" """Abstract model to show each relation from two sides."""
_auto = False _auto = False
_log_access = False _log_access = False
_name = 'res.partner.relation.all'
_description = 'All (non-inverse + inverse) relations between partners'
_order = \
'this_partner_id, type_selection_id, date_end desc, date_start desc'
_name = "res.partner.relation.all"
_description = "All (non-inverse + inverse) relations between partners"
_order = "this_partner_id, type_selection_id, date_end desc, date_start desc"
res_model = fields.Char( res_model = fields.Char(
string='Resource Model',
string="Resource Model",
readonly=True, readonly=True,
required=True, required=True,
help="The database object this relation is based on.")
help="The database object this relation is based on.",
)
res_id = fields.Integer( res_id = fields.Integer(
string='Resource ID',
string="Resource ID",
readonly=True, readonly=True,
required=True, required=True,
help="The id of the object in the model this relation is based on.")
help="The id of the object in the model this relation is based on.",
)
this_partner_id = fields.Many2one( this_partner_id = fields.Many2one(
comodel_name='res.partner',
string='One Partner',
required=True)
comodel_name="res.partner", string="One Partner", required=True
)
other_partner_id = fields.Many2one( other_partner_id = fields.Many2one(
comodel_name='res.partner',
string='Other Partner',
required=True)
comodel_name="res.partner", string="Other Partner", required=True
)
type_id = fields.Many2one( type_id = fields.Many2one(
comodel_name='res.partner.relation.type',
string='Underlying Relation Type',
comodel_name="res.partner.relation.type",
string="Underlying Relation Type",
readonly=True, readonly=True,
required=True)
date_start = fields.Date('Starting date')
date_end = fields.Date('Ending date')
required=True,
)
date_start = fields.Date("Starting date")
date_end = fields.Date("Ending date")
is_inverse = fields.Boolean( is_inverse = fields.Boolean(
string="Is reverse type?", string="Is reverse type?",
readonly=True, readonly=True,
help="Inverse relations are from right to left partner.")
help="Inverse relations are from right to left partner.",
)
type_selection_id = fields.Many2one( type_selection_id = fields.Many2one(
comodel_name='res.partner.relation.type.selection',
string='Relation Type',
required=True)
comodel_name="res.partner.relation.type.selection",
string="Relation Type",
required=True,
)
active = fields.Boolean( active = fields.Boolean(
string='Active',
string="Active",
readonly=True, readonly=True,
help="Records with date_end in the past are inactive")
help="Records with date_end in the past are inactive",
)
any_partner_id = fields.Many2many( any_partner_id = fields.Many2many(
comodel_name='res.partner',
string='Partner',
comodel_name="res.partner",
string="Partner",
compute=lambda self: None, compute=lambda self: None,
search='_search_any_partner_id')
search="_search_any_partner_id",
)
def register_specification(
self, register, base_name, is_inverse, select_sql):
_last_key_offset = register['_lastkey']
key_name = base_name + (is_inverse and '_inverse' or '')
def register_specification(self, register, base_name, is_inverse, select_sql):
_last_key_offset = register["_lastkey"]
key_name = base_name + (is_inverse and "_inverse" or "")
assert key_name not in register assert key_name not in register
assert '%%(padding)s' in select_sql
assert '%(key_offset)s' in select_sql
assert '%(is_inverse)s' in select_sql
assert "%%(padding)s" in select_sql
assert "%(key_offset)s" in select_sql
assert "%(is_inverse)s" in select_sql
_last_key_offset += 1 _last_key_offset += 1
register['_lastkey'] = _last_key_offset
register["_lastkey"] = _last_key_offset
register[key_name] = dict( register[key_name] = dict(
base_name=base_name, base_name=base_name,
is_inverse=is_inverse, is_inverse=is_inverse,
key_offset=_last_key_offset, key_offset=_last_key_offset,
select_sql=select_sql % {
'key_offset': _last_key_offset,
'is_inverse': is_inverse,
'extra_additional_columns':
self._get_additional_relation_columns(),
})
select_sql=select_sql
% {
"key_offset": _last_key_offset,
"is_inverse": is_inverse,
"extra_additional_columns": self._get_additional_relation_columns(),
},
)
def get_register(self): def get_register(self):
register = collections.OrderedDict() register = collections.OrderedDict()
register['_lastkey'] = -1
self.register_specification(
register, 'relation', False, RELATIONS_SQL)
self.register_specification(
register, 'relation', True, RELATIONS_SQL_INVERSE)
register["_lastkey"] = -1
self.register_specification(register, "relation", False, RELATIONS_SQL)
self.register_specification(register, "relation", True, RELATIONS_SQL_INVERSE)
return register return register
def get_select_specification(self, base_name, is_inverse): def get_select_specification(self, base_name, is_inverse):
register = self.get_register() register = self.get_register()
key_name = base_name + (is_inverse and '_inverse' or '')
key_name = base_name + (is_inverse and "_inverse" or "")
return register[key_name] return register[key_name]
def _get_statement(self): def _get_statement(self):
"""Allow other modules to add to statement.""" """Allow other modules to add to statement."""
register = self.get_register() register = self.get_register()
union_select = ' UNION '.join(
[register[key]['select_sql']
for key in register if key != '_lastkey'])
union_select = " UNION ".join(
[register[key]["select_sql"] for key in register if key != "_lastkey"]
)
return """\ return """\
CREATE OR REPLACE VIEW %%(table)s AS CREATE OR REPLACE VIEW %%(table)s AS
WITH base_selection AS (%(union_select)s) WITH base_selection AS (%(union_select)s)
@ -153,7 +155,9 @@ CREATE OR REPLACE VIEW %%(table)s AS
FROM base_selection bas FROM base_selection bas
JOIN res_partner_relation_type typ ON (bas.type_id = typ.id) JOIN res_partner_relation_type typ ON (bas.type_id = typ.id)
%%(additional_tables)s %%(additional_tables)s
""" % {'union_select': union_select}
""" % {
"union_select": union_select
}
def _get_padding(self): def _get_padding(self):
"""Utility function to define padding in one place.""" """Utility function to define padding in one place."""
@ -167,7 +171,7 @@ CREATE OR REPLACE VIEW %%(table)s AS
:return: ', rel.column_a, rel.column_b_id' :return: ', rel.column_a, rel.column_b_id'
""" """
return ''
return ""
def _get_additional_view_fields(self): def _get_additional_view_fields(self):
"""Allow inherit models to add fields to view. """Allow inherit models to add fields to view.
@ -176,7 +180,7 @@ CREATE OR REPLACE VIEW %%(table)s AS
prepended by a comma, like so: prepended by a comma, like so:
return ', typ.allow_self, typ.left_partner_category' return ', typ.allow_self, typ.left_partner_category'
""" """
return ''
return ""
def _get_additional_tables(self): def _get_additional_tables(self):
"""Allow inherit models to add tables (JOIN's) to view. """Allow inherit models to add tables (JOIN's) to view.
@ -184,7 +188,7 @@ CREATE OR REPLACE VIEW %%(table)s AS
Example: Example:
return 'JOIN type_extention ext ON (bas.type_id = ext.id)' return 'JOIN type_extention ext ON (bas.type_id = ext.id)'
""" """
return ''
return ""
@api.model_cr_context @api.model_cr_context
def _auto_init(self): def _auto_init(self):
@ -192,12 +196,13 @@ CREATE OR REPLACE VIEW %%(table)s AS
drop_view_if_exists(cr, self._table) drop_view_if_exists(cr, self._table)
cr.execute( cr.execute(
self._get_statement(), self._get_statement(),
{'table': AsIs(self._table),
'padding': self._get_padding(),
'additional_view_fields':
AsIs(self._get_additional_view_fields()),
'additional_tables':
AsIs(self._get_additional_tables())})
{
"table": AsIs(self._table),
"padding": self._get_padding(),
"additional_view_fields": AsIs(self._get_additional_view_fields()),
"additional_tables": AsIs(self._get_additional_tables()),
},
)
return super(ResPartnerRelationAll, self)._auto_init() return super(ResPartnerRelationAll, self)._auto_init()
@api.model @api.model
@ -205,20 +210,24 @@ CREATE OR REPLACE VIEW %%(table)s AS
"""Search relation with partner, no matter on which side.""" """Search relation with partner, no matter on which side."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
return [ return [
'|',
('this_partner_id', operator, value),
('other_partner_id', operator, value)]
"|",
("this_partner_id", operator, value),
("other_partner_id", operator, value),
]
@api.multi @api.multi
def name_get(self): def name_get(self):
return { return {
this.id: '%s %s %s' % (
this.id: "%s %s %s"
% (
this.this_partner_id.name, this.this_partner_id.name,
this.type_selection_id.display_name, this.type_selection_id.display_name,
this.other_partner_id.name, this.other_partner_id.name,
) for this in self}
)
for this in self
}
@api.onchange('type_selection_id')
@api.onchange("type_selection_id")
def onchange_type_selection_id(self): def onchange_type_selection_id(self):
"""Add domain on partners according to category and contact_type.""" """Add domain on partners according to category and contact_type."""
@ -228,72 +237,74 @@ CREATE OR REPLACE VIEW %%(table)s AS
""" """
warning = {} warning = {}
if partner: if partner:
test_domain = [('id', '=', partner.id)] + partner_domain
test_domain = [("id", "=", partner.id)] + partner_domain
else: else:
test_domain = partner_domain test_domain = partner_domain
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!')
warning["title"] = _("Error!")
if partner: if partner:
warning['message'] = (
_('%s partner incompatible with relation type.') %
side.title())
warning["message"] = (
_("%s partner incompatible with relation type.") % side.title()
)
else: else:
warning['message'] = (
_('No %s partner available for relation type.') %
side)
warning["message"] = (
_("No %s partner available for relation type.") % side
)
return warning return warning
this_partner_domain = [] this_partner_domain = []
other_partner_domain = [] other_partner_domain = []
if self.type_selection_id.contact_type_this: if self.type_selection_id.contact_type_this:
this_partner_domain.append((
'is_company', '=',
self.type_selection_id.contact_type_this == 'c'))
this_partner_domain.append(
("is_company", "=", self.type_selection_id.contact_type_this == "c")
)
if self.type_selection_id.partner_category_this: if self.type_selection_id.partner_category_this:
this_partner_domain.append((
'category_id', 'in',
self.type_selection_id.partner_category_this.ids))
this_partner_domain.append(
("category_id", "in", self.type_selection_id.partner_category_this.ids)
)
if self.type_selection_id.contact_type_other: if self.type_selection_id.contact_type_other:
other_partner_domain.append((
'is_company', '=',
self.type_selection_id.contact_type_other == 'c'))
other_partner_domain.append(
("is_company", "=", self.type_selection_id.contact_type_other == "c")
)
if self.type_selection_id.partner_category_other: if self.type_selection_id.partner_category_other:
other_partner_domain.append((
'category_id', 'in',
self.type_selection_id.partner_category_other.ids))
result = {'domain': {
'this_partner_id': this_partner_domain,
'other_partner_id': other_partner_domain}}
other_partner_domain.append(
("category_id", "in", self.type_selection_id.partner_category_other.ids)
)
result = {
"domain": {
"this_partner_id": this_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 = {} warning = {}
partner_model = self.env['res.partner']
partner_model = self.env["res.partner"]
if this_partner_domain: if this_partner_domain:
this_partner = False this_partner = False
if bool(self.this_partner_id.id): if bool(self.this_partner_id.id):
this_partner = self.this_partner_id this_partner = self.this_partner_id
else: else:
this_partner_id = \
'default_this_partner_id' in self.env.context and \
self.env.context['default_this_partner_id'] or \
'active_id' in self.env.context and \
self.env.context['active_id'] or \
False
this_partner_id = (
"default_this_partner_id" in self.env.context
and self.env.context["default_this_partner_id"]
or "active_id" in self.env.context
and self.env.context["active_id"]
or False
)
if this_partner_id: if this_partner_id:
this_partner = partner_model.browse(this_partner_id) this_partner = partner_model.browse(this_partner_id)
warning = check_partner_domain(
this_partner, this_partner_domain, _('this'))
warning = check_partner_domain(this_partner, this_partner_domain, _("this"))
if not warning and other_partner_domain: 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: if warning:
result['warning'] = warning
result["warning"] = warning
return result return result
@api.onchange(
'this_partner_id',
'other_partner_id')
@api.onchange("this_partner_id", "other_partner_id")
def onchange_partner_id(self): def onchange_partner_id(self):
"""Set domain on type_selection_id based on partner(s) selected.""" """Set domain on type_selection_id based on partner(s) selected."""
@ -306,69 +317,68 @@ CREATE OR REPLACE VIEW %%(table)s AS
warning = {} warning = {}
if not (type_selection_domain and self.type_selection_id): if not (type_selection_domain and self.type_selection_id):
return warning return warning
test_domain = (
[('id', '=', self.type_selection_id.id)] +
type_selection_domain)
type_model = self.env['res.partner.relation.type.selection']
test_domain = [
("id", "=", self.type_selection_id.id)
] + type_selection_domain
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:
warning['title'] = _('Error!')
warning['message'] = _(
'Relation type incompatible with selected partner(s).')
warning["title"] = _("Error!")
warning["message"] = _(
"Relation type incompatible with selected partner(s)."
)
return warning return warning
type_selection_domain = [] type_selection_domain = []
if self.this_partner_id: if self.this_partner_id:
type_selection_domain += [ 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)]
"|",
("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: if self.other_partner_id:
type_selection_domain += [ 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}}
"|",
("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 # Check wether domain results in no choice or wrong choice for
# type_selection_id: # type_selection_id:
warning = check_type_selection_domain(type_selection_domain) warning = check_type_selection_domain(type_selection_domain)
if warning: if warning:
result['warning'] = warning
result["warning"] = warning
return result return result
@api.model @api.model
def _correct_vals(self, vals, type_selection): def _correct_vals(self, vals, type_selection):
"""Fill left and right partner from this and other partner.""" """Fill left and right partner from this and other partner."""
vals = vals.copy() vals = vals.copy()
if 'type_selection_id' in vals:
vals['type_id'] = type_selection.type_id.id
if "type_selection_id" in vals:
vals["type_id"] = type_selection.type_id.id
if type_selection.is_inverse: if type_selection.is_inverse:
if 'this_partner_id' in vals:
vals['right_partner_id'] = vals['this_partner_id']
if 'other_partner_id' in vals:
vals['left_partner_id'] = vals['other_partner_id']
if "this_partner_id" in vals:
vals["right_partner_id"] = vals["this_partner_id"]
if "other_partner_id" in vals:
vals["left_partner_id"] = vals["other_partner_id"]
else: else:
if 'this_partner_id' in vals:
vals['left_partner_id'] = vals['this_partner_id']
if 'other_partner_id' in vals:
vals['right_partner_id'] = vals['other_partner_id']
if "this_partner_id" in vals:
vals["left_partner_id"] = vals["this_partner_id"]
if "other_partner_id" in vals:
vals["right_partner_id"] = vals["other_partner_id"]
# Delete values not in underlying table: # Delete values not in underlying table:
for key in ( for key in (
'this_partner_id',
'type_selection_id',
'other_partner_id',
'is_inverse'):
"this_partner_id",
"type_selection_id",
"other_partner_id",
"is_inverse",
):
if key in vals: if key in vals:
del vals[key] del vals[key]
return vals return vals
@ -386,7 +396,7 @@ CREATE OR REPLACE VIEW %%(table)s AS
self.ensure_one() self.ensure_one()
# write for models other then res.partner.relation SHOULD # write for models other then res.partner.relation SHOULD
# be handled in inherited models: # be handled in inherited models:
relation_model = self.env['res.partner.relation']
relation_model = self.env["res.partner.relation"]
assert self.res_model == relation_model._name assert self.res_model == relation_model._name
base_resource.write(vals) base_resource.write(vals)
@ -394,14 +404,17 @@ CREATE OR REPLACE VIEW %%(table)s AS
def _get_type_selection_from_vals(self, vals): def _get_type_selection_from_vals(self, vals):
"""Get type_selection_id straight from vals or compute from type_id. """Get type_selection_id straight from vals or compute from type_id.
""" """
type_selection_id = vals.get('type_selection_id', False)
type_selection_id = vals.get("type_selection_id", False)
if not type_selection_id: if not type_selection_id:
type_id = vals.get('type_id', False)
type_id = vals.get("type_id", False)
if type_id: if type_id:
is_inverse = vals.get('is_inverse')
is_inverse = vals.get("is_inverse")
type_selection_id = type_id * 2 + (is_inverse and 1 or 0) type_selection_id = type_id * 2 + (is_inverse and 1 or 0)
return type_selection_id and self.type_selection_id.browse(
type_selection_id) or False
return (
type_selection_id
and self.type_selection_id.browse(type_selection_id)
or False
)
@api.multi @api.multi
def write(self, vals): def write(self, vals):
@ -421,19 +434,20 @@ CREATE OR REPLACE VIEW %%(table)s AS
@api.model @api.model
def _compute_base_name(self, type_selection): def _compute_base_name(self, type_selection):
"""This will be overridden for each inherit model.""" """This will be overridden for each inherit model."""
return 'relation'
return "relation"
@api.model @api.model
def _compute_id(self, base_resource, type_selection): def _compute_id(self, base_resource, type_selection):
"""Compute id. Allow for enhancements in inherit model.""" """Compute id. Allow for enhancements in inherit model."""
base_name = self._compute_base_name(type_selection) base_name = self._compute_base_name(type_selection)
key_offset = self.get_select_specification( key_offset = self.get_select_specification(
base_name, type_selection.is_inverse)['key_offset']
base_name, type_selection.is_inverse
)["key_offset"]
return base_resource.id * self._get_padding() + key_offset return base_resource.id * self._get_padding() + key_offset
@api.model @api.model
def create_resource(self, vals, type_selection): def create_resource(self, vals, type_selection):
relation_model = self.env['res.partner.relation']
relation_model = self.env["res.partner.relation"]
return relation_model.create(vals) return relation_model.create(vals)
@api.model @api.model
@ -444,8 +458,7 @@ CREATE OR REPLACE VIEW %%(table)s AS
""" """
type_selection = self._get_type_selection_from_vals(vals) type_selection = self._get_type_selection_from_vals(vals)
if not type_selection: # Should not happen if not type_selection: # Should not happen
raise ValidationError(
_('No relation type specified in vals: %s.') % vals)
raise ValidationError(_("No relation type specified in vals: %s.") % vals)
vals = self._correct_vals(vals, type_selection) vals = self._correct_vals(vals, type_selection)
base_resource = self.create_resource(vals, type_selection) base_resource = self.create_resource(vals, type_selection)
res_id = self._compute_id(base_resource, type_selection) res_id = self._compute_id(base_resource, type_selection)
@ -457,7 +470,7 @@ CREATE OR REPLACE VIEW %%(table)s AS
self.ensure_one() self.ensure_one()
# unlink for models other then res.partner.relation SHOULD # unlink for models other then res.partner.relation SHOULD
# be handled in inherited models: # be handled in inherited models:
relation_model = self.env['res.partner.relation']
relation_model = self.env["res.partner.relation"]
assert self.res_model == relation_model._name assert self.res_model == relation_model._name
base_resource.unlink() base_resource.unlink()

190
partner_multi_relation/models/res_partner_relation_type.py

@ -5,83 +5,62 @@ from odoo import _, api, fields, models
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.osv.expression import AND, OR from odoo.osv.expression import AND, OR
HANDLE_INVALID_ONCHANGE = [ HANDLE_INVALID_ONCHANGE = [
('restrict',
_('Do not allow change that will result in invalid relations')),
('ignore',
_('Allow existing relations that do not fit changed conditions')),
('end',
_('End relations per today, if they do not fit changed conditions')),
('delete',
_('Delete relations that do not fit changed conditions')),
("restrict", _("Do not allow change that will result in invalid relations")),
("ignore", _("Allow existing relations that do not fit changed conditions")),
("end", _("End relations per today, if they do not fit changed conditions")),
("delete", _("Delete relations that do not fit changed conditions")),
] ]
class ResPartnerRelationType(models.Model): class ResPartnerRelationType(models.Model):
"""Model that defines relation types that might exist between partners""" """Model that defines relation types that might exist between partners"""
_name = 'res.partner.relation.type'
_description = 'Partner Relation Type'
_order = 'name'
name = fields.Char(
string='Name',
required=True,
translate=True,
)
name_inverse = fields.Char(
string='Inverse name',
required=True,
translate=True,
)
_name = "res.partner.relation.type"
_description = "Partner Relation Type"
_order = "name"
name = fields.Char(string="Name", required=True, translate=True)
name_inverse = fields.Char(string="Inverse name", required=True, translate=True)
contact_type_left = fields.Selection( contact_type_left = fields.Selection(
selection='get_partner_types',
string='Left partner type',
selection="get_partner_types", string="Left partner type"
) )
contact_type_right = fields.Selection( contact_type_right = fields.Selection(
selection='get_partner_types',
string='Right partner type',
selection="get_partner_types", string="Right partner type"
) )
partner_category_left = fields.Many2one( partner_category_left = fields.Many2one(
comodel_name='res.partner.category',
string='Left partner category',
comodel_name="res.partner.category", string="Left partner category"
) )
partner_category_right = fields.Many2one( partner_category_right = fields.Many2one(
comodel_name='res.partner.category',
string='Right partner category',
comodel_name="res.partner.category", string="Right partner category"
) )
allow_self = fields.Boolean( allow_self = fields.Boolean(
string='Reflexive',
help='This relation can be set up with the same partner left and '
'right',
string="Reflexive",
help="This relation can be set up with the same partner left and " "right",
default=False, default=False,
) )
is_symmetric = fields.Boolean( is_symmetric = fields.Boolean(
string='Symmetric',
help="This relation is the same from right to left as from left to"
" right",
string="Symmetric",
help="This relation is the same from right to left as from left to" " right",
default=False, default=False,
) )
handle_invalid_onchange = fields.Selection( handle_invalid_onchange = fields.Selection(
selection=HANDLE_INVALID_ONCHANGE, selection=HANDLE_INVALID_ONCHANGE,
string='Invalid relation handling',
string="Invalid relation handling",
required=True, required=True,
default='restrict',
default="restrict",
help="When adding relations criteria like partner type and category" help="When adding relations criteria like partner type and category"
" are checked.\n"
"However when you change the criteria, there might be relations"
" that do not fit the new criteria.\n"
"Specify how this situation should be handled.",
" are checked.\n"
"However when you change the criteria, there might be relations"
" that do not fit the new criteria.\n"
"Specify how this situation should be handled.",
) )
@api.model @api.model
def get_partner_types(self): def get_partner_types(self):
"""A partner can be an organisation or an individual.""" """A partner can be an organisation or an individual."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
return [
('c', _('Organisation')),
('p', _('Person')),
]
return [("c", _("Organisation")), ("p", _("Person"))]
@api.model @api.model
def _end_active_relations(self, relations): def _end_active_relations(self, relations):
@ -102,70 +81,68 @@ class ResPartnerRelationType(models.Model):
relation.unlink() relation.unlink()
elif not relation.date_end or relation.date_end > today: elif not relation.date_end or relation.date_end > today:
relation.write({'date_end': today})
relation.write({"date_end": today})
@api.multi @api.multi
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): def get_type_condition(vals, side):
"""Add if needed check for contact type.""" """Add if needed check for contact type."""
fieldname1 = 'contact_type_%s' % side
fieldname2 = '%s_partner_id.is_company' % side
fieldname1 = "contact_type_%s" % side
fieldname2 = "%s_partner_id.is_company" % side
contact_type = fieldname1 in vals and vals[fieldname1] or False contact_type = fieldname1 in vals and vals[fieldname1] or False
if contact_type == 'c':
if contact_type == "c":
# Records that are not companies are invalid: # Records that are not companies are invalid:
return [(fieldname2, '=', False)]
if contact_type == 'p':
return [(fieldname2, "=", False)]
if contact_type == "p":
# Records that are companies are invalid: # Records that are companies are invalid:
return [(fieldname2, '=', True)]
return [(fieldname2, "=", True)]
return [] return []
def get_category_condition(vals, side): def get_category_condition(vals, side):
"""Add if needed check for partner category.""" """Add if needed check for partner category."""
fieldname1 = 'partner_category_%s' % side
fieldname2 = '%s_partner_id.category_id' % side
fieldname1 = "partner_category_%s" % side
fieldname2 = "%s_partner_id.category_id" % side
category_id = fieldname1 in vals and vals[fieldname1] or False category_id = fieldname1 in vals and vals[fieldname1] or False
if category_id: if category_id:
# Records that do not have the specified category are invalid: # Records that do not have the specified category are invalid:
return [(fieldname2, 'not in', [category_id])]
return [(fieldname2, "not in", [category_id])]
return [] return []
for this in self: for this in self:
handling = ( handling = (
'handle_invalid_onchange' in vals and
vals['handle_invalid_onchange'] or
this.handle_invalid_onchange
"handle_invalid_onchange" in vals
and vals["handle_invalid_onchange"]
or this.handle_invalid_onchange
) )
if handling == 'ignore':
if handling == "ignore":
continue continue
invalid_conditions = [] 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),
])
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: if not invalid_conditions:
return return
# only look at relations for this type # only look at relations for this type
invalid_domain = AND([
[('type_id', '=', this.id)], invalid_conditions
])
invalid_relations = relation_model.with_context(
active_test=False
).search(invalid_domain)
invalid_domain = AND([[("type_id", "=", this.id)], invalid_conditions])
invalid_relations = relation_model.with_context(active_test=False).search(
invalid_domain
)
if invalid_relations: if invalid_relations:
if handling == 'restrict':
if handling == "restrict":
raise ValidationError( raise ValidationError(
_('There are already relations not satisfying the'
' conditions for partner type or category.')
_(
"There are already relations not satisfying the"
" conditions for partner type or category."
)
) )
elif handling == 'delete':
elif handling == "delete":
invalid_relations.unlink() invalid_relations.unlink()
else: else:
self._end_active_relations(invalid_relations) self._end_active_relations(invalid_relations)
@ -180,12 +157,11 @@ class ResPartnerRelationType(models.Model):
SELECT id FROM res_partner_relation SELECT id FROM res_partner_relation
WHERE left_partner_id = right_partner_id WHERE left_partner_id = right_partner_id
AND type_id = %(relation_type_id)s AND type_id = %(relation_type_id)s
""", {
'relation_type_id': self.id,
}
""",
{"relation_type_id": self.id},
) )
reflexive_relation_ids = [r[0] for r in self.env.cr.fetchall()] reflexive_relation_ids = [r[0] for r in self.env.cr.fetchall()]
return self.env['res.partner.relation'].browse(reflexive_relation_ids)
return self.env["res.partner.relation"].browse(reflexive_relation_ids)
def _check_no_existing_reflexive_relations(self): def _check_no_existing_reflexive_relations(self):
"""Check that no reflexive relation exists for these relation types.""" """Check that no reflexive relation exists for these relation types."""
@ -193,13 +169,16 @@ class ResPartnerRelationType(models.Model):
relations = relation_type._get_reflexive_relations() relations = relation_type._get_reflexive_relations()
if relations: if relations:
raise ValidationError( raise ValidationError(
_("Reflexivity could not be disabled for the relation "
"type {relation_type}. There are existing reflexive "
"relations defined for the following partners: "
"{partners}").format(
_(
"Reflexivity could not be disabled for the relation "
"type {relation_type}. There are existing reflexive "
"relations defined for the following partners: "
"{partners}"
).format(
relation_type=relation_type.display_name, relation_type=relation_type.display_name,
partners=relations.mapped(
'left_partner_id.display_name')))
partners=relations.mapped("left_partner_id.display_name"),
)
)
def _delete_existing_reflexive_relations(self): def _delete_existing_reflexive_relations(self):
"""Delete existing reflexive relations for these relation types.""" """Delete existing reflexive relations for these relation types."""
@ -216,15 +195,16 @@ class ResPartnerRelationType(models.Model):
def _handle_deactivation_of_allow_self(self): def _handle_deactivation_of_allow_self(self):
"""Handle the deactivation of reflexivity on these relations types.""" """Handle the deactivation of reflexivity on these relations types."""
restrict_relation_types = self.filtered( restrict_relation_types = self.filtered(
lambda t: t.handle_invalid_onchange == 'restrict')
lambda t: t.handle_invalid_onchange == "restrict"
)
restrict_relation_types._check_no_existing_reflexive_relations() restrict_relation_types._check_no_existing_reflexive_relations()
delete_relation_types = self.filtered( delete_relation_types = self.filtered(
lambda t: t.handle_invalid_onchange == 'delete')
lambda t: t.handle_invalid_onchange == "delete"
)
delete_relation_types._delete_existing_reflexive_relations() delete_relation_types._delete_existing_reflexive_relations()
end_relation_types = self.filtered(
lambda t: t.handle_invalid_onchange == 'end')
end_relation_types = self.filtered(lambda t: t.handle_invalid_onchange == "end")
end_relation_types._end_active_reflexive_relations() end_relation_types._end_active_reflexive_relations()
@api.multi @api.multi
@ -235,19 +215,19 @@ class ResPartnerRelationType(models.Model):
replaced by the values of the fields whose names end replaced by the values of the fields whose names end
in `_left`. in `_left`.
""" """
vals['name_inverse'] = vals.get('name', self.name)
vals["name_inverse"] = vals.get("name", self.name)
# For all left keys in model, take value for right either from # For all left keys in model, take value for right either from
# left key in vals, or if not present, from right key in self: # left key in vals, or if not present, from right key in self:
left_keys = [key for key in self._fields if key.endswith('_left')]
left_keys = [key for key in self._fields if key.endswith("_left")]
for left_key in left_keys: for left_key in left_keys:
right_key = left_key.replace('_left', '_right')
right_key = left_key.replace("_left", "_right")
vals[right_key] = vals.get(left_key, self[left_key]) vals[right_key] = vals.get(left_key, self[left_key])
if hasattr(vals[right_key], 'id'):
if hasattr(vals[right_key], "id"):
vals[right_key] = vals[right_key].id vals[right_key] = vals[right_key].id
@api.model @api.model
def create(self, vals): def create(self, vals):
if vals.get('is_symmetric'):
if vals.get("is_symmetric"):
self._update_right_vals(vals) self._update_right_vals(vals)
return super(ResPartnerRelationType, self).create(vals) return super(ResPartnerRelationType, self).create(vals)
@ -258,11 +238,11 @@ class ResPartnerRelationType(models.Model):
for rec in self: for rec in self:
rec_vals = vals.copy() rec_vals = vals.copy()
if rec_vals.get('is_symmetric', rec.is_symmetric):
if rec_vals.get("is_symmetric", rec.is_symmetric):
self._update_right_vals(rec_vals) self._update_right_vals(rec_vals)
super(ResPartnerRelationType, rec).write(rec_vals) super(ResPartnerRelationType, rec).write(rec_vals)
allow_self_disabled = 'allow_self' in vals and not vals['allow_self']
allow_self_disabled = "allow_self" in vals and not vals["allow_self"]
if allow_self_disabled: if allow_self_disabled:
self._handle_deactivation_of_allow_self() self._handle_deactivation_of_allow_self()
@ -274,13 +254,11 @@ class ResPartnerRelationType(models.Model):
Relations can be deleted if relation type allows it. Relations can be deleted if relation type allows it.
""" """
relation_model = self.env['res.partner.relation']
relation_model = self.env["res.partner.relation"]
for rec in self: for rec in self:
if rec.handle_invalid_onchange == 'delete':
if rec.handle_invalid_onchange == "delete":
# Automatically delete relations, so existing relations # Automatically delete relations, so existing relations
# do not prevent unlink of relation type: # do not prevent unlink of relation type:
relations = relation_model.search([
('type_id', '=', rec.id),
])
relations = relation_model.search([("type_id", "=", rec.id)])
relations.unlink() relations.unlink()
return super(ResPartnerRelationType, self).unlink() return super(ResPartnerRelationType, self).unlink()

79
partner_multi_relation/models/res_partner_relation_type_selection.py

@ -20,51 +20,41 @@ from odoo.tools import drop_view_if_exists
class ResPartnerRelationTypeSelection(models.Model): class ResPartnerRelationTypeSelection(models.Model):
"""Virtual relation types""" """Virtual relation types"""
_name = 'res.partner.relation.type.selection'
_description = 'All relation types'
_name = "res.partner.relation.type.selection"
_description = "All relation types"
_auto = False # Do not try to create table in _auto_init(..) _auto = False # Do not try to create table in _auto_init(..)
_foreign_keys = [] _foreign_keys = []
_log_access = False _log_access = False
_order = 'name asc'
_order = "name asc"
@api.model @api.model
def get_partner_types(self): def get_partner_types(self):
"""Partner types are defined by model res.partner.relation.type.""" """Partner types are defined by model res.partner.relation.type."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
rprt_model = self.env['res.partner.relation.type']
rprt_model = self.env["res.partner.relation.type"]
return rprt_model.get_partner_types() return rprt_model.get_partner_types()
type_id = fields.Many2one(
comodel_name='res.partner.relation.type',
string='Type',
)
name = fields.Char('Name')
type_id = fields.Many2one(comodel_name="res.partner.relation.type", string="Type")
name = fields.Char("Name")
contact_type_this = fields.Selection( contact_type_this = fields.Selection(
selection='get_partner_types',
string='Current record\'s partner type',
selection="get_partner_types", string="Current record's partner type"
) )
is_inverse = fields.Boolean( is_inverse = fields.Boolean(
string="Is reverse type?", string="Is reverse type?",
help="Inverse relations are from right to left partner.", help="Inverse relations are from right to left partner.",
) )
contact_type_other = fields.Selection( contact_type_other = fields.Selection(
selection='get_partner_types',
string='Other record\'s partner type',
selection="get_partner_types", string="Other record's partner type"
) )
partner_category_this = fields.Many2one( partner_category_this = fields.Many2one(
comodel_name='res.partner.category',
string='Current record\'s category',
comodel_name="res.partner.category", string="Current record's category"
) )
partner_category_other = fields.Many2one( partner_category_other = fields.Many2one(
comodel_name='res.partner.category',
string='Other record\'s category',
)
allow_self = fields.Boolean(
string='Reflexive',
)
is_symmetric = fields.Boolean(
string='Symmetric',
comodel_name="res.partner.category", string="Other record's category"
) )
allow_self = fields.Boolean(string="Reflexive")
is_symmetric = fields.Boolean(string="Symmetric")
def _get_additional_view_fields(self): def _get_additional_view_fields(self):
"""Allow inherit models to add fields to view. """Allow inherit models to add fields to view.
@ -73,7 +63,7 @@ class ResPartnerRelationTypeSelection(models.Model):
prepended by a comma, like so: prepended by a comma, like so:
return ', typ.allow_self, typ.left_partner_category' return ', typ.allow_self, typ.left_partner_category'
""" """
return ''
return ""
def _get_additional_tables(self): def _get_additional_tables(self):
"""Allow inherit models to add tables (JOIN's) to view. """Allow inherit models to add tables (JOIN's) to view.
@ -81,7 +71,7 @@ class ResPartnerRelationTypeSelection(models.Model):
Example: Example:
return 'JOIN type_extention ext ON (bas.type_id = ext.id)' return 'JOIN type_extention ext ON (bas.type_id = ext.id)'
""" """
return ''
return ""
@api.model_cr_context @api.model_cr_context
def _auto_init(self): def _auto_init(self):
@ -122,29 +112,38 @@ CREATE OR REPLACE VIEW %(table)s AS
JOIN res_partner_relation_type typ ON (bas.type_id = typ.id) JOIN res_partner_relation_type typ ON (bas.type_id = typ.id)
%(additional_tables)s %(additional_tables)s
""", """,
{'table': AsIs(self._table),
'underlying_table': AsIs('res_partner_relation_type'),
'additional_view_fields':
AsIs(self._get_additional_view_fields()),
'additional_tables':
AsIs(self._get_additional_tables())})
{
"table": AsIs(self._table),
"underlying_table": AsIs("res_partner_relation_type"),
"additional_view_fields": AsIs(self._get_additional_view_fields()),
"additional_tables": AsIs(self._get_additional_tables()),
},
)
return super(ResPartnerRelationTypeSelection, self)._auto_init() return super(ResPartnerRelationTypeSelection, self)._auto_init()
@api.multi @api.multi
def name_get(self): def name_get(self):
"""Get name or name_inverse from underlying model.""" """Get name or name_inverse from underlying model."""
return [ return [
(this.id,
this.is_inverse and this.type_id.name_inverse or
this.type_id.display_name)
for this in self]
(
this.id,
this.is_inverse
and this.type_id.name_inverse
or this.type_id.display_name,
)
for this in self
]
@api.model @api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
def name_search(self, name="", args=None, operator="ilike", limit=100):
"""Search for name or inverse name in underlying model.""" """Search for name or inverse name in underlying model."""
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
return self.search( return self.search(
['|',
('type_id.name', operator, name),
('type_id.name_inverse', operator, name)] + (args or []),
limit=limit).name_get()
[
"|",
("type_id.name", operator, name),
("type_id.name_inverse", operator, name),
]
+ (args or []),
limit=limit,
).name_get()

2
partner_multi_relation/readme/USAGE.rst

@ -52,7 +52,7 @@ Symmetric
A symetric relation has the same value for the left and right sides. A symetric relation has the same value for the left and right sides.
For example, in a competitor relation, both companies are competitors of each other.
For example, in a competitor relation, both companies are competitors of each other.
.. image:: https://raw.githubusercontent.com/OCA/partner-contact/12.0/partner_multi_relation/static/description/relation_type_symmetric.png .. image:: https://raw.githubusercontent.com/OCA/partner-contact/12.0/partner_multi_relation/static/description/relation_type_symmetric.png

524
partner_multi_relation/tests/test_partner_relation.py

@ -1,6 +1,7 @@
# Copyright 2016-2017 Therp BV # Copyright 2016-2017 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 datetime, date, timedelta
from datetime import date, datetime, timedelta
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import fields from odoo import fields
@ -16,25 +17,33 @@ class TestPartnerRelation(TestPartnerRelationCommon):
def test_selection_name_search(self): def test_selection_name_search(self):
"""Test wether we can find type selection on reverse name.""" """Test wether we can find type selection on reverse name."""
selection_types = self.selection_model.name_search( selection_types = self.selection_model.name_search(
name=self.selection_person2company.name)
name=self.selection_person2company.name
)
self.assertTrue(selection_types) self.assertTrue(selection_types)
self.assertTrue( self.assertTrue(
(self.selection_person2company.id,
self.selection_person2company.name) in selection_types)
(self.selection_person2company.id, self.selection_person2company.name)
in selection_types
)
def test_self_allowed(self): def test_self_allowed(self):
"""Test creation of relation to same partner when type allows.""" """Test creation of relation to same partner when type allows."""
type_allow = self.type_model.create({
'name': 'allow',
'name_inverse': 'allow_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p',
'allow_self': True})
type_allow = self.type_model.create(
{
"name": "allow",
"name_inverse": "allow_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
"allow_self": True,
}
)
self.assertTrue(type_allow) self.assertTrue(type_allow)
reflexive_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id})
reflexive_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
}
)
self.assertTrue(reflexive_relation) self.assertTrue(reflexive_relation)
def test_self_disallowed(self): def test_self_disallowed(self):
@ -43,18 +52,24 @@ class TestPartnerRelation(TestPartnerRelationCommon):
Attempt to create a relation of a partner to the same partner should Attempt to create a relation of a partner to the same partner should
raise an error when the type of relation explicitly disallows this. raise an error when the type of relation explicitly disallows this.
""" """
type_disallow = self.type_model.create({
'name': 'disallow',
'name_inverse': 'disallow_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p',
'allow_self': False})
type_disallow = self.type_model.create(
{
"name": "disallow",
"name_inverse": "disallow_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
"allow_self": False,
}
)
self.assertTrue(type_disallow) self.assertTrue(type_disallow)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({
'type_id': type_disallow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id})
self.relation_model.create(
{
"type_id": type_disallow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
}
)
def test_self_disallowed_after_self_relation_created(self): def test_self_disallowed_after_self_relation_created(self):
"""Test that allow_self can not be true if a reflexive relation already exists. """Test that allow_self can not be true if a reflexive relation already exists.
@ -62,17 +77,23 @@ class TestPartnerRelation(TestPartnerRelationCommon):
If at least one reflexive relation exists for the given type, If at least one reflexive relation exists for the given type,
reflexivity can not be disallowed. reflexivity can not be disallowed.
""" """
type_allow = self.type_model.create({
'name': 'allow',
'name_inverse': 'allow_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p',
'allow_self': True})
type_allow = self.type_model.create(
{
"name": "allow",
"name_inverse": "allow_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
"allow_self": True,
}
)
self.assertTrue(type_allow) self.assertTrue(type_allow)
reflexive_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id})
reflexive_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
}
)
self.assertTrue(reflexive_relation) self.assertTrue(reflexive_relation)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
type_allow.allow_self = False type_allow.allow_self = False
@ -85,24 +106,30 @@ class TestPartnerRelation(TestPartnerRelationCommon):
Non reflexive relations are not modified. Non reflexive relations are not modified.
""" """
type_allow = self.type_model.create({
'name': 'allow',
'name_inverse': 'allow_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p',
'allow_self': True,
'handle_invalid_onchange': 'delete',
})
reflexive_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id,
})
normal_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_04_volunteer.id,
})
type_allow = self.type_model.create(
{
"name": "allow",
"name_inverse": "allow_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
"allow_self": True,
"handle_invalid_onchange": "delete",
}
)
reflexive_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
}
)
normal_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_04_volunteer.id,
}
)
type_allow.allow_self = False type_allow.allow_self = False
self.assertFalse(reflexive_relation.exists()) self.assertFalse(reflexive_relation.exists())
@ -119,31 +146,39 @@ class TestPartnerRelation(TestPartnerRelationCommon):
Reflexive relations with an end date prior to the current date Reflexive relations with an end date prior to the current date
are not modified. are not modified.
""" """
type_allow = self.type_model.create({
'name': 'allow',
'name_inverse': 'allow_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p',
'allow_self': True,
'handle_invalid_onchange': 'end',
})
reflexive_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id,
'date_start': '2000-01-02',
})
past_reflexive_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id,
'date_end': '2000-01-01',
})
normal_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_04_volunteer.id,
})
type_allow = self.type_model.create(
{
"name": "allow",
"name_inverse": "allow_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
"allow_self": True,
"handle_invalid_onchange": "end",
}
)
reflexive_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
"date_start": "2000-01-02",
}
)
past_reflexive_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
"date_end": "2000-01-01",
}
)
normal_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_04_volunteer.id,
}
)
type_allow.allow_self = False type_allow.allow_self = False
self.assertEqual(reflexive_relation.date_end, fields.Date.today()) self.assertEqual(reflexive_relation.date_end, fields.Date.today())
@ -156,20 +191,24 @@ class TestPartnerRelation(TestPartnerRelationCommon):
If handle_invalid_onchange is set to end, then deactivating If handle_invalid_onchange is set to end, then deactivating
reflexivity will delete invalid relations in the future. reflexivity will delete invalid relations in the future.
""" """
type_allow = self.type_model.create({
'name': 'allow',
'name_inverse': 'allow_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p',
'allow_self': True,
'handle_invalid_onchange': 'end',
})
future_reflexive_relation = self.relation_model.create({
'type_id': type_allow.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id,
'date_start': datetime.now() + timedelta(1),
})
type_allow = self.type_model.create(
{
"name": "allow",
"name_inverse": "allow_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
"allow_self": True,
"handle_invalid_onchange": "end",
}
)
future_reflexive_relation = self.relation_model.create(
{
"type_id": type_allow.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
"date_start": datetime.now() + timedelta(1),
}
)
type_allow.allow_self = False type_allow.allow_self = False
self.assertFalse(future_reflexive_relation.exists()) self.assertFalse(future_reflexive_relation.exists())
@ -180,17 +219,23 @@ class TestPartnerRelation(TestPartnerRelationCommon):
raise an error when the type of relation does not explicitly allow raise an error when the type of relation does not explicitly allow
this. this.
""" """
type_default = self.type_model.create({
'name': 'default',
'name_inverse': 'default_inverse',
'contact_type_left': 'p',
'contact_type_right': 'p'})
type_default = self.type_model.create(
{
"name": "default",
"name_inverse": "default_inverse",
"contact_type_left": "p",
"contact_type_right": "p",
}
)
self.assertTrue(type_default) self.assertTrue(type_default)
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({
'type_id': type_default.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_01_person.id})
self.relation_model.create(
{
"type_id": type_default.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_01_person.id,
}
)
def test_self_mixed(self): def test_self_mixed(self):
"""Test creation of relation with wrong types. """Test creation of relation with wrong types.
@ -199,54 +244,65 @@ class TestPartnerRelation(TestPartnerRelationCommon):
type should raise an error. type should raise an error.
""" """
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({
'type_id': self.type_company2person.id,
'left_partner_id': self.partner_01_person.id,
'right_partner_id': self.partner_02_company.id})
self.relation_model.create(
{
"type_id": self.type_company2person.id,
"left_partner_id": self.partner_01_person.id,
"right_partner_id": self.partner_02_company.id,
}
)
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:
type_symmetric = self.type_model.create({
'name': 'not yet symmetric',
'name_inverse': 'the other side of not symmetric',
'is_symmetric': False,
'contact_type_left': False,
'contact_type_right': 'p'})
type_symmetric = self.type_model.create(
{
"name": "not yet symmetric",
"name_inverse": "the other side of not symmetric",
"is_symmetric": False,
"contact_type_left": False,
"contact_type_right": "p",
}
)
# not yet symmetric relation should result in two records in # not yet symmetric relation should result in two records in
# selection: # selection:
selection_symmetric = self.selection_model.search([
('type_id', '=', type_symmetric.id)])
selection_symmetric = self.selection_model.search(
[("type_id", "=", type_symmetric.id)]
)
self.assertEqual(len(selection_symmetric), 2) self.assertEqual(len(selection_symmetric), 2)
# Now change to symmetric and test name and inverse name: # Now change to symmetric and test name and inverse name:
with self.env.do_in_draft(): with self.env.do_in_draft():
type_symmetric.write({
'name': 'sym',
'is_symmetric': True})
type_symmetric.write({"name": "sym", "is_symmetric": True})
self.assertEqual(type_symmetric.is_symmetric, True) self.assertEqual(type_symmetric.is_symmetric, True)
self.assertEqual(type_symmetric.name_inverse, type_symmetric.name)
self.assertEqual( self.assertEqual(
type_symmetric.name_inverse,
type_symmetric.name)
self.assertEqual(
type_symmetric.contact_type_right,
type_symmetric.contact_type_left)
type_symmetric.contact_type_right, type_symmetric.contact_type_left
)
# now update the database: # now update the database:
type_symmetric.write({
'name': type_symmetric.name,
'is_symmetric': type_symmetric.is_symmetric,
'name_inverse': type_symmetric.name_inverse,
'contact_type_right': type_symmetric.contact_type_right})
type_symmetric.write(
{
"name": type_symmetric.name,
"is_symmetric": type_symmetric.is_symmetric,
"name_inverse": type_symmetric.name_inverse,
"contact_type_right": type_symmetric.contact_type_right,
}
)
# symmetric relation should result in only one record in # symmetric relation should result in only one record in
# selection: # selection:
selection_symmetric = self.selection_model.search([
('type_id', '=', type_symmetric.id)])
selection_symmetric = self.selection_model.search(
[("type_id", "=", type_symmetric.id)]
)
self.assertEqual(len(selection_symmetric), 1) self.assertEqual(len(selection_symmetric), 1)
relation = self.relation_all_model.create({
'type_selection_id': selection_symmetric.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)])
relation = self.relation_all_model.create(
{
"type_selection_id": selection_symmetric.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_01_person in partners) self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners) self.assertTrue(self.partner_02_company in partners)
@ -254,124 +310,148 @@ class TestPartnerRelation(TestPartnerRelationCommon):
"""Test check on category in relations.""" """Test check on category in relations."""
# Check on left side: # Check on left side:
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({
'type_id': self.type_ngo2volunteer.id,
'left_partner_id': self.partner_02_company.id,
'right_partner_id': self.partner_04_volunteer.id})
self.relation_model.create(
{
"type_id": self.type_ngo2volunteer.id,
"left_partner_id": self.partner_02_company.id,
"right_partner_id": self.partner_04_volunteer.id,
}
)
# Check on right side: # Check on right side:
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_model.create({
'type_id': self.type_ngo2volunteer.id,
'left_partner_id': self.partner_03_ngo.id,
'right_partner_id': self.partner_01_person.id})
self.relation_model.create(
{
"type_id": self.type_ngo2volunteer.id,
"left_partner_id": self.partner_03_ngo.id,
"right_partner_id": self.partner_01_person.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,
school2student,
school2student_inverse) = \
self._create_relation_type_selection({
'name': 'school has student',
'name_inverse': 'studies at school'})
(
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({
'name': 'Test School',
'is_company': True,
'ref': 'TS'})
partner_bart = self.partner_model.create({
'name': 'Bart Simpson',
'is_company': False,
'ref': 'BS'})
partner_lisa = self.partner_model.create({
'name': 'Lisa Simpson',
'is_company': False,
'ref': 'LS'})
relation_school2bart = self.relation_all_model.create({
'this_partner_id': partner_school.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_bart.id})
partner_school = self.partner_model.create(
{"name": "Test School", "is_company": True, "ref": "TS"}
)
partner_bart = self.partner_model.create(
{"name": "Bart Simpson", "is_company": False, "ref": "BS"}
)
partner_lisa = self.partner_model.create(
{"name": "Lisa Simpson", "is_company": False, "ref": "LS"}
)
relation_school2bart = self.relation_all_model.create(
{
"this_partner_id": partner_school.id,
"type_selection_id": school2student.id,
"other_partner_id": partner_bart.id,
}
)
self.assertTrue(relation_school2bart) self.assertTrue(relation_school2bart)
relation_school2lisa = self.relation_all_model.create({
'this_partner_id': partner_school.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_lisa.id})
relation_school2lisa = self.relation_all_model.create(
{
"this_partner_id": partner_school.id,
"type_selection_id": school2student.id,
"other_partner_id": partner_lisa.id,
}
)
self.assertTrue(relation_school2lisa) self.assertTrue(relation_school2lisa)
relation_bart2lisa = self.relation_all_model.create({
'this_partner_id': partner_bart.id,
'type_selection_id': school2student.id,
'other_partner_id': partner_lisa.id})
relation_bart2lisa = self.relation_all_model.create(
{
"this_partner_id": partner_bart.id,
"type_selection_id": school2student.id,
"other_partner_id": partner_lisa.id,
}
)
self.assertTrue(relation_bart2lisa) self.assertTrue(relation_bart2lisa)
# Third creata a category and make it a condition for the # Third creata a category and make it a condition for the
# relation type. # relation type.
# - Test restriction # - Test restriction
# - Test ignore # - Test ignore
category_student = self.category_model.create({'name': 'Student'})
category_student = self.category_model.create({"name": "Student"})
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
type_school2student.write({
'partner_category_right': category_student.id})
type_school2student.write({"partner_category_right": category_student.id})
self.assertFalse(type_school2student.partner_category_right.id) self.assertFalse(type_school2student.partner_category_right.id)
type_school2student.write({
'handle_invalid_onchange': 'ignore',
'partner_category_right': category_student.id})
type_school2student.write(
{
"handle_invalid_onchange": "ignore",
"partner_category_right": category_student.id,
}
)
self.assertEqual( self.assertEqual(
type_school2student.partner_category_right.id,
category_student.id)
type_school2student.partner_category_right.id, category_student.id
)
# Fourth make company type a condition for left partner # Fourth make company type a condition for left partner
# - Test ending # - Test ending
# - Test deletion # - Test deletion
partner_bart.write({
'category_id': [(4, category_student.id)]})
partner_lisa.write({
'category_id': [(4, category_student.id)]})
partner_bart.write({"category_id": [(4, category_student.id)]})
partner_lisa.write({"category_id": [(4, category_student.id)]})
# Future student to be deleted by end action: # 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': date.today() + relativedelta(months=+6)})
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": date.today() + relativedelta(months=+6),
}
)
self.assertTrue(relation_lisa2homer) self.assertTrue(relation_lisa2homer)
type_school2student.write({
'handle_invalid_onchange': 'end',
'contact_type_left': 'c'})
self.assertEqual(
relation_bart2lisa.date_end,
fields.Date.today())
type_school2student.write(
{"handle_invalid_onchange": "end", "contact_type_left": "c"}
)
self.assertEqual(relation_bart2lisa.date_end, fields.Date.today())
self.assertFalse(relation_lisa2homer.exists()) self.assertFalse(relation_lisa2homer.exists())
type_school2student.write({
'handle_invalid_onchange': 'delete',
'contact_type_left': 'c',
'contact_type_right': 'p'})
type_school2student.write(
{
"handle_invalid_onchange": "delete",
"contact_type_left": "c",
"contact_type_right": "p",
}
)
self.assertFalse(relation_bart2lisa.exists()) self.assertFalse(relation_bart2lisa.exists())
def test_relation_type_unlink(self): def test_relation_type_unlink(self):
"""Test delete of relation type, including deleting relations.""" """Test delete of relation type, including deleting relations."""
# First create a relation type having restrict particular conditions. # First create a relation type having restrict particular conditions.
type_model = self.env['res.partner.relation.type']
relation_model = self.env['res.partner.relation']
partner_model = self.env['res.partner']
type_school2student = type_model.create({
'name': 'school has student',
'name_inverse': 'studies at school',
'handle_invalid_onchange': 'delete'})
type_model = self.env["res.partner.relation.type"]
relation_model = self.env["res.partner.relation"]
partner_model = self.env["res.partner"]
type_school2student = type_model.create(
{
"name": "school has student",
"name_inverse": "studies at school",
"handle_invalid_onchange": "delete",
}
)
# Second create relation based on those conditions. # Second create relation based on those conditions.
partner_school = partner_model.create({
'name': 'Test School',
'is_company': True,
'ref': 'TS'})
partner_bart = partner_model.create({
'name': 'Bart Simpson',
'is_company': False,
'ref': 'BS'})
relation_school2bart = relation_model.create({
'left_partner_id': partner_school.id,
'type_id': type_school2student.id,
'right_partner_id': partner_bart.id})
partner_school = partner_model.create(
{"name": "Test School", "is_company": True, "ref": "TS"}
)
partner_bart = partner_model.create(
{"name": "Bart Simpson", "is_company": False, "ref": "BS"}
)
relation_school2bart = relation_model.create(
{
"left_partner_id": partner_school.id,
"type_id": type_school2student.id,
"right_partner_id": partner_bart.id,
}
)
# Delete type. Relations with type should also cease to exist: # Delete type. Relations with type should also cease to exist:
type_school2student.unlink() type_school2student.unlink()
self.assertFalse(relation_school2bart.exists()) self.assertFalse(relation_school2bart.exists())

327
partner_multi_relation/tests/test_partner_relation_all.py

@ -2,74 +2,87 @@
# 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 datetime import date
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from .test_partner_relation_common import TestPartnerRelationCommon from .test_partner_relation_common import TestPartnerRelationCommon
class TestPartnerRelation(TestPartnerRelationCommon): class TestPartnerRelation(TestPartnerRelationCommon):
def setUp(self): def setUp(self):
super(TestPartnerRelation, self).setUp() super(TestPartnerRelation, self).setUp()
# Create a new relation type which will not have valid relations: # 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}))
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): def _get_empty_relation(self):
"""Get empty relation record for onchange tests.""" """Get empty relation record for onchange tests."""
# Need English, because we will compare text # Need English, because we will compare text
return self.relation_all_model.with_context(lang='en_US').new({})
return self.relation_all_model.with_context(lang="en_US").new({})
def test_get_partner_types(self): def test_get_partner_types(self):
"""Partner types should contain at least 'c' and 'p'.""" """Partner types should contain at least 'c' and 'p'."""
partner_types = self.selection_model.get_partner_types() partner_types = self.selection_model.get_partner_types()
type_codes = [ptype[0] for ptype in partner_types] type_codes = [ptype[0] for ptype in partner_types]
self.assertTrue('c' in type_codes)
self.assertTrue('p' in type_codes)
self.assertTrue("c" in type_codes)
self.assertTrue("p" in type_codes)
def test_create_with_active_id(self): def test_create_with_active_id(self):
"""Test creation with this_partner_id from active_id.""" """Test creation with this_partner_id from active_id."""
# Check wether we can create connection from company to person, # Check wether we can create connection from company to person,
# taking the particular company from the active records: # taking the particular company from the active records:
relation = self.relation_all_model.with_context( 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})
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.assertTrue(relation)
self.assertEqual(relation.this_partner_id, self.partner_02_company) self.assertEqual(relation.this_partner_id, self.partner_02_company)
# Partner should have one relation now: # Partner should have one relation now:
self.assertEqual(self.partner_01_person.relation_count, 1) self.assertEqual(self.partner_01_person.relation_count, 1)
# Test create without type_selection_id: # Test create without type_selection_id:
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.relation_all_model.create({
'this_partner_id': self.partner_02_company.id,
'other_partner_id': self.partner_01_person.id})
self.relation_all_model.create(
{
"this_partner_id": self.partner_02_company.id,
"other_partner_id": self.partner_01_person.id,
}
)
def test_display_name(self): def test_display_name(self):
"""Test display name""" """Test display name"""
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
self.assertEqual( self.assertEqual(
relation.display_name, '%s %s %s' % (
relation.display_name,
"%s %s %s"
% (
relation.this_partner_id.name, relation.this_partner_id.name,
relation.type_selection_id.name, relation.type_selection_id.name,
relation.other_partner_id.name))
relation.other_partner_id.name,
),
)
def test__regular_write(self): def test__regular_write(self):
"""Test write with valid data.""" """Test write with valid data."""
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
relation.write({'date_start': '2014-09-01'})
relation.write({"date_start": "2014-09-01"})
relation.invalidate_cache(ids=relation.ids) relation.invalidate_cache(ids=relation.ids)
self.assertEqual(relation.date_start, date(2014, 9, 1)) self.assertEqual(relation.date_start, date(2014, 9, 1))
@ -77,90 +90,120 @@ class TestPartnerRelation(TestPartnerRelationCommon):
"""Test write with date_end before date_start.""" """Test write with date_end before date_start."""
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
relation.write({
'date_start': '2016-09-01',
'date_end': '2016-08-01'})
relation.write({"date_start": "2016-09-01", "date_end": "2016-08-01"})
def test_validate_overlapping_01(self): def test_validate_overlapping_01(self):
"""Test create overlapping with no start / end dates.""" """Test create overlapping with no start / end dates."""
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
# New relation with no start / end should give error # 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})
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): def test_validate_overlapping_02(self):
"""Test create overlapping with start / end dates.""" """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'})
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 # New relation with overlapping start / end should give error
with self.assertRaises(ValidationError): 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'})
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): def test_validate_overlapping_03(self):
"""Test create not overlapping.""" """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'})
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) self.assertTrue(relation_another_record)
def test_inverse_record(self): def test_inverse_record(self):
"""Test creation of inverse record.""" """Test creation of inverse record."""
relation = self._create_company2person_relation() 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)])
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(len(inverse_relation), 1)
self.assertEqual( self.assertEqual(
inverse_relation.type_selection_id.name,
self.selection_person2company.name)
inverse_relation.type_selection_id.name, self.selection_person2company.name
)
def test_inverse_creation(self): def test_inverse_creation(self):
"""Test creation of record through inverse selection.""" """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})
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: # Check wether display name is what we should expect:
self.assertEqual( self.assertEqual(
relation.display_name, '%s %s %s' % (
relation.display_name,
"%s %s %s"
% (
self.partner_01_person.name, self.partner_01_person.name,
self.selection_person2company.name, self.selection_person2company.name,
self.partner_02_company.name))
self.partner_02_company.name,
),
)
def test_inverse_creation_type_id(self): def test_inverse_creation_type_id(self):
"""Test creation of record through inverse selection with type_id.""" """Test creation of record through inverse selection with type_id."""
relation = self.relation_all_model.create({
'this_partner_id': self.partner_01_person.id,
'type_id': self.selection_person2company.type_id.id,
'is_inverse': True,
'other_partner_id': self.partner_02_company.id})
relation = self.relation_all_model.create(
{
"this_partner_id": self.partner_01_person.id,
"type_id": self.selection_person2company.type_id.id,
"is_inverse": True,
"other_partner_id": self.partner_02_company.id,
}
)
# Check wether display name is what we should expect: # Check wether display name is what we should expect:
self.assertEqual( self.assertEqual(
relation.display_name, '%s %s %s' % (
relation.display_name,
"%s %s %s"
% (
self.partner_01_person.name, self.partner_01_person.name,
self.selection_person2company.name, self.selection_person2company.name,
self.partner_02_company.name))
self.partner_02_company.name,
),
)
def test_unlink(self): def test_unlink(self):
"""Unlinking derived relation should unlink base relation.""" """Unlinking derived relation should unlink base relation."""
@ -178,104 +221,114 @@ class TestPartnerRelation(TestPartnerRelationCommon):
# 1. Test call with empty relation # 1. Test call with empty relation
relation_empty = self._get_empty_relation() relation_empty = self._get_empty_relation()
result = relation_empty.onchange_type_selection_id() 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'])
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 # 2. Test call with company 2 person relation
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
domain = relation.onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', False) in domain['other_partner_id'])
domain = relation.onchange_type_selection_id()["domain"]
self.assertTrue(("is_company", "=", False) in domain["other_partner_id"])
# 3. Test with relation needing categories, # 3. Test with relation needing categories,
# take active partner from active_id: # take active partner from active_id:
relation_ngo_volunteer = self.relation_all_model.with_context( relation_ngo_volunteer = self.relation_all_model.with_context(
active_id=self.partner_03_ngo.id).create({
'type_selection_id': self.selection_ngo2volunteer.id,
'other_partner_id': self.partner_04_volunteer.id})
domain = relation_ngo_volunteer.onchange_type_selection_id()['domain']
active_id=self.partner_03_ngo.id
).create(
{
"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( self.assertTrue(
('category_id', 'in', [self.category_01_ngo.id]) in
domain['this_partner_id'])
("category_id", "in", [self.category_01_ngo.id])
in domain["this_partner_id"]
)
self.assertTrue( self.assertTrue(
('category_id', 'in', [self.category_02_volunteer.id]) in
domain['other_partner_id'])
("category_id", "in", [self.category_02_volunteer.id])
in domain["other_partner_id"]
)
# 4. Test with invalid or impossible combinations # 4. Test with invalid or impossible combinations
relation_nobody = self._get_empty_relation() relation_nobody = self._get_empty_relation()
with self.env.do_in_draft(): with self.env.do_in_draft():
relation_nobody.type_selection_id = self.selection_nobody 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'])
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(): with self.env.do_in_draft():
relation_nobody.this_partner_id = self.partner_02_company 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'])
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: # Allow left partner and check message for other partner:
self.type_nobody.write({'partner_category_left': False})
self.type_nobody.write({"partner_category_left": False})
self.selection_nobody.invalidate_cache(ids=self.selection_nobody.ids) 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'])
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): def test_on_change_partner_id(self):
"""Test on_change_partner_id.""" """Test on_change_partner_id."""
# 1. Test call with empty relation # 1. Test call with empty relation
relation_empty = self._get_empty_relation() relation_empty = self._get_empty_relation()
result = relation_empty.onchange_partner_id() 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'])
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 # 2. Test call with company 2 person relation
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
domain = relation.onchange_partner_id()['domain']
self.assertTrue(
('contact_type_this', '=', 'c') in domain['type_selection_id'])
domain = relation.onchange_partner_id()["domain"]
self.assertTrue(("contact_type_this", "=", "c") in domain["type_selection_id"])
# 3. Test with invalid or impossible combinations # 3. Test with invalid or impossible combinations
relation_nobody = self._get_empty_relation() relation_nobody = self._get_empty_relation()
with self.env.do_in_draft(): with self.env.do_in_draft():
relation_nobody.this_partner_id = self.partner_02_company relation_nobody.this_partner_id = self.partner_02_company
relation_nobody.type_selection_id = self.selection_nobody 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'])
warning = relation_nobody.onchange_partner_id()["warning"]
self.assertTrue("message" in warning)
self.assertTrue("incompatible" in warning["message"])
def test_write(self): def test_write(self):
"""Test write. Special attention for changing type.""" """Test write. Special attention for changing type."""
relation_company2person = self._create_company2person_relation() relation_company2person = self._create_company2person_relation()
company_partner = relation_company2person.this_partner_id company_partner = relation_company2person.this_partner_id
# First get another worker: # First get another worker:
partner_extra_person = self.partner_model.create({
'name': 'A new worker',
'is_company': False,
'ref': 'NW01'})
relation_company2person.write({
'other_partner_id': partner_extra_person.id})
partner_extra_person = self.partner_model.create(
{"name": "A new worker", "is_company": False, "ref": "NW01"}
)
relation_company2person.write({"other_partner_id": partner_extra_person.id})
self.assertEqual( self.assertEqual(
relation_company2person.other_partner_id.name,
partner_extra_person.name)
relation_company2person.other_partner_id.name, partner_extra_person.name
)
# We will also change to a type going from person to company: # We will also change to a type going from person to company:
(type_worker2company,
selection_worker2company,
selection_company2worker) = self._create_relation_type_selection({
'name': 'works for',
'name_inverse': 'has worker',
'contact_type_left': 'p',
'contact_type_right': 'c'})
relation_company2person.write({
'this_partner_id': partner_extra_person.id,
'type_selection_id': selection_worker2company.id,
'other_partner_id': company_partner.id})
(
type_worker2company,
selection_worker2company,
selection_company2worker,
) = self._create_relation_type_selection(
{
"name": "works for",
"name_inverse": "has worker",
"contact_type_left": "p",
"contact_type_right": "c",
}
)
relation_company2person.write(
{
"this_partner_id": partner_extra_person.id,
"type_selection_id": selection_worker2company.id,
"other_partner_id": company_partner.id,
}
)
self.assertEqual( self.assertEqual(
relation_company2person.this_partner_id.id,
partner_extra_person.id)
relation_company2person.this_partner_id.id, partner_extra_person.id
)
self.assertEqual( self.assertEqual(
relation_company2person.type_selection_id.id,
selection_worker2company.id)
relation_company2person.type_selection_id.id, selection_worker2company.id
)
self.assertEqual( self.assertEqual(
relation_company2person.other_partner_id.id,
company_partner.id)
relation_company2person.other_partner_id.id, company_partner.id
)

138
partner_multi_relation/tests/test_partner_relation_common.py

@ -4,73 +4,80 @@ from odoo.tests import common
class TestPartnerRelationCommon(common.TransactionCase): class TestPartnerRelationCommon(common.TransactionCase):
def setUp(self): def setUp(self):
super(TestPartnerRelationCommon, self).setUp() 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'})
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: # 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)]})
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: # 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'})
(
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: # 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})
(
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): def _create_relation_type_selection(self, vals):
"""Create relation type and return this with selection types.""" """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)
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) 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)])
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: for st in selection_types:
if st.is_inverse: if st.is_inverse:
inverse_type_selection = st inverse_type_selection = st
@ -79,16 +86,21 @@ class TestPartnerRelationCommon(common.TransactionCase):
self.assertTrue( self.assertTrue(
inverse_type_selection, inverse_type_selection,
msg="Failed to find inverse type selection based on" msg="Failed to find inverse type selection based on"
" relation type created with vals %s." % vals)
" relation type created with vals %s." % vals,
)
self.assertTrue( self.assertTrue(
type_selection, type_selection,
msg="Failed to find type selection based on" msg="Failed to find type selection based on"
" relation type created with vals %s." % vals)
" relation type created with vals %s." % vals,
)
return (new_type, type_selection, inverse_type_selection) return (new_type, type_selection, inverse_type_selection)
def _create_company2person_relation(self): def _create_company2person_relation(self):
"""Utility function to get a relation from company 2 partner.""" """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})
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,
}
)

74
partner_multi_relation/tests/test_partner_search.py

@ -8,68 +8,74 @@ from .test_partner_relation_common import TestPartnerRelationCommon
class TestPartnerSearch(TestPartnerRelationCommon): class TestPartnerSearch(TestPartnerRelationCommon):
def test_search_relation_type(self): def test_search_relation_type(self):
"""Test searching on relation type.""" """Test searching on relation type."""
relation = self._create_company2person_relation() relation = self._create_company2person_relation()
partners = self.partner_model.search([
('search_relation_type_id', '=', relation.type_selection_id.id)
])
partners = self.partner_model.search(
[("search_relation_type_id", "=", relation.type_selection_id.id)]
)
self.assertTrue(self.partner_02_company in partners) self.assertTrue(self.partner_02_company in partners)
partners = self.partner_model.search([
('search_relation_type_id', '!=', relation.type_selection_id.id)
])
partners = self.partner_model.search(
[("search_relation_type_id", "!=", relation.type_selection_id.id)]
)
self.assertTrue(self.partner_01_person in partners) self.assertTrue(self.partner_01_person in partners)
partners = self.partner_model.search([
('search_relation_type_id', '=', self.type_company2person.name)
])
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_01_person in partners)
self.assertTrue(self.partner_02_company in partners) self.assertTrue(self.partner_02_company in partners)
partners = self.partner_model.search([
('search_relation_type_id', '=', 'unknown relation')
])
partners = self.partner_model.search(
[("search_relation_type_id", "=", "unknown relation")]
)
self.assertFalse(partners) self.assertFalse(partners)
# Check error with invalid search operator: # Check error with invalid search operator:
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
partners = self.partner_model.search([
('search_relation_type_id', 'child_of', 'some parent')
])
partners = self.partner_model.search(
[("search_relation_type_id", "child_of", "some parent")]
)
def test_search_relation_partner(self): def test_search_relation_partner(self):
"""Test searching on related partner.""" """Test searching on related partner."""
self._create_company2person_relation() self._create_company2person_relation()
partners = self.partner_model.search([
('search_relation_partner_id', '=', self.partner_02_company.id),
])
partners = self.partner_model.search(
[("search_relation_partner_id", "=", self.partner_02_company.id)]
)
self.assertTrue(self.partner_01_person in partners) self.assertTrue(self.partner_01_person in partners)
def test_search_relation_date(self): def test_search_relation_date(self):
"""Test searching on relations valid on a certain date.""" """Test searching on relations valid on a certain date."""
self._create_company2person_relation() self._create_company2person_relation()
partners = self.partner_model.search([
('search_relation_date', '=', fields.Date.today()),
])
partners = self.partner_model.search(
[("search_relation_date", "=", fields.Date.today())]
)
self.assertTrue(self.partner_01_person in partners) self.assertTrue(self.partner_01_person in partners)
self.assertTrue(self.partner_02_company in partners) self.assertTrue(self.partner_02_company in partners)
def test_search_any_partner(self): def test_search_any_partner(self):
"""Test searching for partner left or right.""" """Test searching for partner left or right."""
self._create_company2person_relation() self._create_company2person_relation()
both_relations = self.relation_all_model.search([
('any_partner_id', '=', self.partner_02_company.id),
])
both_relations = self.relation_all_model.search(
[("any_partner_id", "=", self.partner_02_company.id)]
)
self.assertEqual(len(both_relations), 2) self.assertEqual(len(both_relations), 2)
def test_search_partner_category(self): def test_search_partner_category(self):
"""Test searching for partners related to partners having category.""" """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,
})
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) self.assertTrue(relation_ngo_volunteer)
partners = self.partner_model.search([
('search_relation_partner_category_id', '=',
self.category_02_volunteer.id)
])
partners = self.partner_model.search(
[
(
"search_relation_partner_category_id",
"=",
self.category_02_volunteer.id,
)
]
)
self.assertTrue(self.partner_03_ngo in partners) self.assertTrue(self.partner_03_ngo in partners)

55
partner_multi_relation/views/menu.xml

@ -1,32 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<menuitem
id="menu_res_partner_relation"
name="Relations"
sequence="3"
parent="contacts.menu_contacts"
/>
<menuitem
id="menu_res_partner_relation_all"
sequence="3"
parent="menu_res_partner_relation"
action="action_res_partner_relation_all"
/>
<act_window
id="action_res_partner_relation_type"
res_model="res.partner.relation.type"
view_mode="tree,form"
name="Partner Relations Types"
/>
<menuitem
id="menu_res_partner_relation_type"
name="Relation Types"
parent="menu_res_partner_relation"
action="action_res_partner_relation_type"
/>
<menuitem
id="menu_res_partner_relation"
name="Relations"
sequence="3"
parent="contacts.menu_contacts"
/>
<menuitem
id="menu_res_partner_relation_all"
sequence="3"
parent="menu_res_partner_relation"
action="action_res_partner_relation_all"
/>
<act_window
id="action_res_partner_relation_type"
res_model="res.partner.relation.type"
view_mode="tree,form"
name="Partner Relations Types"
/>
<menuitem
id="menu_res_partner_relation_type"
name="Relation Types"
parent="menu_res_partner_relation"
action="action_res_partner_relation_type"
/>
</odoo> </odoo>

78
partner_multi_relation/views/res_partner.xml

@ -1,43 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">partner_multi_relation.view_partner_filter</field>
<field name="inherit_id" ref="base.view_res_partner_filter" />
<field name="model">res.partner</field>
<field type="xml" name="arch">
<field name="parent_id" position="after">
<field name="search_relation_partner_id" />
<field name="search_relation_type_id" />
<field name="search_relation_date" />
<field name="search_relation_partner_category_id" />
</field>
<record id="view_res_partner_filter" model="ir.ui.view">
<field name="name">partner_multi_relation.view_partner_filter</field>
<field name="inherit_id" ref="base.view_res_partner_filter" />
<field name="model">res.partner</field>
<field type="xml" name="arch">
<field name="parent_id" position="after">
<field name="search_relation_partner_id" />
<field name="search_relation_type_id" />
<field name="search_relation_date" />
<field name="search_relation_partner_category_id" />
</field> </field>
</record>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">partner_multi_relation.view_partner_form</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="model">res.partner</field>
<field type="xml" name="arch">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_relations"
type="object"
class="oe_stat_button"
icon="fa-users">
<field name="relation_count"
widget="statinfo"
string="Relations"/>
</button>
</xpath>
</field>
</record>
<act_window id="action_show_partner_multi_relation"
name="Show partner's relations"
src_model="res.partner"
res_model="res.partner.relation.all"
domain="[('this_partner_id', 'in', active_ids)]"
key2="client_action_multi" />
</field>
</record>
<record id="view_partner_form" model="ir.ui.view">
<field name="name">partner_multi_relation.view_partner_form</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="model">res.partner</field>
<field type="xml" name="arch">
<xpath expr="//div[@name='button_box']" position="inside">
<button
name="action_view_relations"
type="object"
class="oe_stat_button"
icon="fa-users"
>
<field name="relation_count" widget="statinfo" string="Relations" />
</button>
</xpath>
</field>
</record>
<act_window
id="action_show_partner_multi_relation"
name="Show partner's relations"
src_model="res.partner"
res_model="res.partner.relation.all"
domain="[('this_partner_id', 'in', active_ids)]"
key2="client_action_multi"
/>
</odoo> </odoo>

174
partner_multi_relation/views/res_partner_relation_all.xml

@ -1,101 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<record id="tree_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field name="arch" type="xml">
<tree
string="Partner Relations"
colors="gray:not active; blue:date_start &gt; current_date"
editable="top"
>
<field
name="this_partner_id"
required="True"
options="{'no_create': True}"
/>
<field
name="type_selection_id"
required="True"
options="{'no_create': True}"
/>
<field
name="other_partner_id"
required="True"
options="{'no_create': True}"
/>
<field name="date_start" />
<field name="date_end" />
<field name="active" invisible="1" />
</tree>
</field>
</record>
<record id="search_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field name="arch" type="xml">
<search string="Search Relations">
<field name="any_partner_id" widget="many2one"/>
<field name="this_partner_id"/>
<field name="other_partner_id"/>
<field name="type_selection_id"/>
<record id="tree_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field name="arch" type="xml">
<tree
string="Partner Relations"
colors="gray:not active; blue:date_start &gt; current_date"
editable="top"
>
<field
name="this_partner_id"
required="True"
options="{'no_create': True}"
/>
<field
name="type_selection_id"
required="True"
options="{'no_create': True}"
/>
<field
name="other_partner_id"
required="True"
options="{'no_create': True}"
/>
<field name="date_start" />
<field name="date_end" />
<field name="active" invisible="1" />
</tree>
</field>
</record>
<record id="search_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field name="arch" type="xml">
<search string="Search Relations">
<field name="any_partner_id" widget="many2one" />
<field name="this_partner_id" />
<field name="other_partner_id" />
<field name="type_selection_id" />
<filter
name="left_to_right"
string="Left to right"
domain="[('is_inverse', '=', False)]"
/>
<filter
name="right_to_left"
string="Right to left"
domain="[('is_inverse', '=', True)]"
/>
<filter
name="include_part_records"
string="Include past records"
context="{'active_test': False}"
/>
<group expand="0" string="Group By">
<filter <filter
name="left_to_right"
string="Left to right"
domain="[('is_inverse', '=', False)]"
/>
name="group_by_this_partner_id"
string="One Partner"
context="{'group_by': 'this_partner_id'}"
/>
<filter <filter
name="right_to_left"
string="Right to left"
domain="[('is_inverse', '=', True)]"
/>
name="group_by_type_selection_id"
string="Relationship Type"
context="{'group_by': 'type_selection_id'}"
/>
<filter <filter
name="include_part_records"
string="Include past records"
context="{'active_test': False}"
/>
<group expand="0" string="Group By">
<filter
name="group_by_this_partner_id"
string="One Partner"
context="{'group_by': 'this_partner_id'}"
/>
<filter
name="group_by_type_selection_id"
string="Relationship Type"
context="{'group_by': 'type_selection_id'}"
/>
<filter
name="group_by_other_partner_id"
string="Other Partner"
context="{'group_by': 'other_partner_id'}"
/>
</group>
</search>
</field>
</record>
<record
id="action_res_partner_relation_all"
model="ir.actions.act_window"
>
<field name="name">Relations</field>
<field name="res_model">res.partner.relation.all</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="tree_res_partner_relation_all"/>
<field
name="search_view_id"
ref="search_res_partner_relation_all"
/>
<field name="context">{'active_test': 0}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
name="group_by_other_partner_id"
string="Other Partner"
context="{'group_by': 'other_partner_id'}"
/>
</group>
</search>
</field>
</record>
<record id="action_res_partner_relation_all" model="ir.actions.act_window">
<field name="name">Relations</field>
<field name="res_model">res.partner.relation.all</field>
<field name="view_type">form</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="tree_res_partner_relation_all" />
<field name="search_view_id" ref="search_res_partner_relation_all" />
<field name="context">{'active_test': 0}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Record and track your partners' relations. Relations may Record and track your partners' relations. Relations may
be linked to other partners with a type either directly be linked to other partners with a type either directly
or inversely. or inversely.
</p> </p>
</field>
</record>
</field>
</record>
</odoo> </odoo>

95
partner_multi_relation/views/res_partner_relation_type.xml

@ -1,56 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<record id="tree_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field type="xml" name="arch">
<tree>
<field name="name" />
<field name="name_inverse" />
<field name="contact_type_left" />
<field name="contact_type_right" />
<field name="allow_self" />
<field name="is_symmetric" />
</tree>
</field>
</record>
<record id="form_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field type="xml" name="arch">
<form>
<sheet>
<group>
<group
string="Left side of relation"
name="left"
>
<field name="name" />
<field name="contact_type_left" />
<field name="partner_category_left" />
</group>
<group
string="Right side of relation"
name="right"
attrs="{'invisible': [('is_symmetric', '=', True)]}"
>
<field name="name_inverse" attrs="{'required': [('is_symmetric', '=', False)]}"/>
<field name="contact_type_right" />
<field name="partner_category_right" />
</group>
<record id="tree_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field type="xml" name="arch">
<tree>
<field name="name" />
<field name="name_inverse" />
<field name="contact_type_left" />
<field name="contact_type_right" />
<field name="allow_self" />
<field name="is_symmetric" />
</tree>
</field>
</record>
<record id="form_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field type="xml" name="arch">
<form>
<sheet>
<group>
<group string="Left side of relation" name="left">
<field name="name" />
<field name="contact_type_left" />
<field name="partner_category_left" />
</group> </group>
<group <group
name="properties"
string="Properties"
colspan= "6" col="4"
>
<field name="allow_self" />
<field name="is_symmetric" />
<field name="handle_invalid_onchange" />
string="Right side of relation"
name="right"
attrs="{'invisible': [('is_symmetric', '=', True)]}"
>
<field
name="name_inverse"
attrs="{'required': [('is_symmetric', '=', False)]}"
/>
<field name="contact_type_right" />
<field name="partner_category_right" />
</group> </group>
</sheet>
</form>
</field>
</record>
</group>
<group name="properties" string="Properties" colspan="6" col="4">
<field name="allow_self" />
<field name="is_symmetric" />
<field name="handle_invalid_onchange" />
</group>
</sheet>
</form>
</field>
</record>
</odoo> </odoo>
Loading…
Cancel
Save