Browse Source

[RFR] v8 api, guidelines, tests

pull/301/head
Holger Brunn 8 years ago
parent
commit
e033ca626d
No known key found for this signature in database GPG Key ID: 1C9760FECA3AE18
  1. 23
      partner_relations/__init__.py
  2. 13
      partner_relations/__openerp__.py
  3. 1
      partner_relations/data/demo.xml
  4. 26
      partner_relations/model/__init__.py
  5. 339
      partner_relations/model/res_partner.py
  6. 175
      partner_relations/model/res_partner_relation_type_selection.py
  7. 8
      partner_relations/models/__init__.py
  8. 213
      partner_relations/models/res_partner.py
  9. 286
      partner_relations/models/res_partner_relation.py
  10. 71
      partner_relations/models/res_partner_relation_all.py
  11. 43
      partner_relations/models/res_partner_relation_type.py
  12. 149
      partner_relations/models/res_partner_relation_type_selection.py
  13. 22
      partner_relations/test/test_allow.yml
  14. 178
      partner_relations/tests/test_partner_relations.py
  15. 0
      partner_relations/views/menu.xml
  16. 0
      partner_relations/views/res_partner.xml
  17. 0
      partner_relations/views/res_partner_relation.xml
  18. 8
      partner_relations/views/res_partner_relation_all.xml
  19. 17
      partner_relations/views/res_partner_relation_type.xml

23
partner_relations/__init__.py

@ -1,22 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import model
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
from . import tests

13
partner_relations/__openerp__.py

@ -31,15 +31,12 @@
"demo": [
"data/demo.xml",
],
"test": [
"test/test_allow.yml",
],
"data": [
"view/res_partner_relation_all.xml",
'view/res_partner_relation.xml',
'view/res_partner.xml',
'view/res_partner_relation_type.xml',
'view/menu.xml',
"views/res_partner_relation_all.xml",
'views/res_partner_relation.xml',
'views/res_partner.xml',
'views/res_partner_relation_type.xml',
'views/menu.xml',
'security/ir.model.access.csv',
],
"auto_install": False,

1
partner_relations/data/demo.xml

@ -12,6 +12,7 @@
<field name="name_inverse">Is competitor of</field>
<field name="contact_type_left">c</field>
<field name="contact_type_right">c</field>
<field name="symmetric" eval="True" />
</record>
<record id="rel_type_has_worked_for" model="res.partner.relation.type">
<field name="name">Has worked for</field>

26
partner_relations/model/__init__.py

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import res_partner
from . import res_partner_relation
from . import res_partner_relation_type
from . import res_partner_relation_all
from . import res_partner_relation_type_selection

339
partner_relations/model/res_partner.py

@ -1,339 +0,0 @@
# -*- coding: utf-8 -*-
'''Extend res.partner model'''
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import time
from openerp import osv, models, fields, exceptions, api
from openerp.osv.expression import is_leaf, AND, OR, FALSE_LEAF
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.tools.translate import _
PADDING = 10
def get_partner_type(partner):
"""Get partner type for relation.
:param partner: a res.partner either a company or not
:return: 'c' for company or 'p' for person
:rtype: str
"""
return 'c' if partner.is_company else 'p'
class ResPartner(models.Model):
_inherit = 'res.partner'
relation_count = fields.Integer(
'Relation Count',
compute="_count_relations"
)
@api.one
@api.depends("relation_ids")
def _count_relations(self):
"""Count the number of relations this partner has for Smart Button
Don't count inactive relations.
"""
self.relation_count = len([r for r in self.relation_ids if r.active])
def _get_relation_ids_select(self, cr, uid, ids, field_name, arg,
context=None):
'''return the partners' relations as tuple
(id, left_partner_id, right_partner_id)'''
cr.execute(
'''select id, left_partner_id, right_partner_id
from res_partner_relation
where (left_partner_id in %s or right_partner_id in %s)''' +
' order by ' + self.pool['res.partner.relation']._order,
(tuple(ids), tuple(ids))
)
return cr.fetchall()
def _get_relation_ids(
self, cr, uid, ids, field_name, arg, context=None):
'''getter for relation_ids'''
if context is None:
context = {}
result = dict([(i, []) for i in ids])
# TODO: do a permission test on returned ids
for row in self._get_relation_ids_select(
cr, uid, ids, field_name, arg, context=context):
if row[1] in result:
result[row[1]].append(row[0])
if row[2] in result:
result[row[2]].append(row[0])
return result
def _set_relation_ids(
self, cr, uid, ids, dummy_name, field_value, dummy_arg,
context=None):
'''setter for relation_ids'''
if context is None:
context = {}
relation_obj = self.pool.get('res.partner.relation')
context2 = self.with_partner_relations_context(
cr, uid, ids, context=context).env.context
for value in field_value:
if value[0] == 0:
relation_obj.create(cr, uid, value[2], context=context2)
if value[0] == 1:
# if we write partner_id_display, we also need to pass
# type_selection_id in order to have this write end up on
# the correct field
if 'partner_id_display' in value[2] and 'type_selection_id'\
not in value[2]:
relation_data = relation_obj.read(
cr, uid, [value[1]], ['type_selection_id'],
context=context)[0]
value[2]['type_selection_id'] =\
relation_data['type_selection_id']
relation_obj.write(
cr, uid, value[1], value[2], context=context2)
if value[0] == 2:
relation_obj.unlink(cr, uid, value[1], context=context2)
def _search_relation_id(
self, cr, uid, dummy_obj, name, args, context=None):
result = []
for arg in args:
if isinstance(arg, tuple) and arg[0] == name:
if arg[1] not in ['=', '!=', 'like', 'not like', 'ilike',
'not ilike', 'in', 'not in']:
raise exceptions.ValidationError(
_('Unsupported search operand "%s"') % arg[1])
relation_type_selection_ids = []
relation_type_selection = self\
.pool['res.partner.relation.type.selection']
if arg[1] == '=' and isinstance(arg[2], (long, int)):
relation_type_selection_ids.append(arg[2])
elif arg[1] == '!=' and isinstance(arg[2], (long, int)):
type_id, is_inverse = (
relation_type_selection.browse(cr, uid, arg[2],
context=context)
.get_type_from_selection_id()
)
result = OR([
result,
[
('relation_all_ids.type_id', '!=', type_id),
]
])
continue
else:
relation_type_selection_ids = relation_type_selection\
.search(
cr, uid,
[
('type_id.name', arg[1], arg[2]),
('record_type', '=', 'a'),
],
context=context)
relation_type_selection_ids.extend(
relation_type_selection.search(
cr, uid,
[
('type_id.name_inverse', arg[1], arg[2]),
('record_type', '=', 'b'),
],
context=context))
if not relation_type_selection_ids:
result = AND([result, [FALSE_LEAF]])
for relation_type_selection_id in relation_type_selection_ids:
type_id, is_inverse = (
relation_type_selection.browse(
cr, uid, relation_type_selection_id,
context=context
).get_type_from_selection_id()
)
result = OR([
result,
[
'&',
('relation_all_ids.type_id', '=', type_id),
('relation_all_ids.record_type', '=',
'b' if is_inverse else 'a')
],
])
return result
def _search_relation_date(self, cr, uid, obj, name, args, context=None):
result = []
for arg in args:
if isinstance(arg, tuple) and arg[0] == name:
# TODO: handle {<,>}{,=}
if arg[1] != '=':
continue
result.extend([
'&',
'|',
('relation_all_ids.date_start', '=', False),
('relation_all_ids.date_start', '<=', arg[2]),
'|',
('relation_all_ids.date_end', '=', False),
('relation_all_ids.date_end', '>=', arg[2]),
])
return result
def _search_related_partner_id(
self, cr, uid, dummy_obj, name, args, context=None):
result = []
for arg in args:
if isinstance(arg, tuple) and arg[0] == name:
result.append(
(
'relation_all_ids.other_partner_id',
arg[1],
arg[2],
))
return result
def _search_related_partner_category_id(
self, cr, uid, dummy_obj, name, args, context=None):
result = []
for arg in args:
if isinstance(arg, tuple) and arg[0] == name:
result.append(
(
'relation_all_ids.other_partner_id.category_id',
arg[1],
arg[2],
))
return result
_columns = {
'relation_ids': osv.fields.function(
lambda self, *args, **kwargs: self._get_relation_ids(
*args, **kwargs),
fnct_inv=_set_relation_ids,
type='one2many', obj='res.partner.relation',
string='Relations',
selectable=False,
),
'relation_all_ids': osv.fields.one2many(
'res.partner.relation.all', 'this_partner_id',
string='All relations with current partner',
auto_join=True,
selectable=False,
),
'search_relation_id': osv.fields.function(
lambda self, cr, uid, ids, *args: dict([
(i, False) for i in ids]),
fnct_search=_search_relation_id,
string='Has relation of type',
type='many2one', obj='res.partner.relation.type.selection'
),
'search_relation_partner_id': osv.fields.function(
lambda self, cr, uid, ids, *args: dict([
(i, False) for i in ids]),
fnct_search=_search_related_partner_id,
string='Has relation with',
type='many2one', obj='res.partner'
),
'search_relation_date': osv.fields.function(
lambda self, cr, uid, ids, *args: dict([
(i, False) for i in ids]),
fnct_search=_search_relation_date,
string='Relation valid', type='date'
),
'search_relation_partner_category_id': osv.fields.function(
lambda self, cr, uid, ids, *args: dict([
(i, False) for i in ids]),
fnct_search=_search_related_partner_category_id,
string='Has relation with a partner in category',
type='many2one', obj='res.partner.category'
),
}
def copy_data(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default.setdefault('relation_ids', [])
default.setdefault('relation_all_ids', [])
return super(ResPartner, self).copy_data(cr, uid, id, default=default,
context=context)
def search(self, cr, uid, args, offset=0, limit=None, order=None,
context=None, count=False):
if context is None:
context = {}
# inject searching for current relation date if we search for relation
# properties and no explicit date was given
date_args = []
for arg in args:
if is_leaf(arg) and arg[0].startswith('search_relation'):
if arg[0] == 'search_relation_date':
date_args = []
break
if not date_args:
date_args = [
('search_relation_date', '=', time.strftime(
DEFAULT_SERVER_DATE_FORMAT))]
# because of auto_join, we have to do the active test by hand
active_args = []
if context.get('active_test', True):
for arg in args:
if is_leaf(arg) and\
arg[0].startswith('search_relation'):
active_args = [('relation_all_ids.active', '=', True)]
break
return super(ResPartner, self).search(
cr, uid, args + date_args + active_args, offset=offset,
limit=limit, order=order, context=context, count=count)
@api.v7
def read(self, cr, user, ids, fields=None, context=None,
load='_classic_read'):
return super(ResPartner, self).read(
cr, user, ids, fields=fields, context=context, load=load)
@api.v8
def read(self, fields=None, load='_classic_read'):
return super(ResPartner, self.with_partner_relations_context())\
.read(fields=fields, load=load)
@api.multi
def write(self, vals):
return super(ResPartner, self.with_partner_relations_context())\
.write(vals)
@api.multi
def with_partner_relations_context(self):
context = dict(self.env.context)
if context.get('active_model', self._name) == self._name:
existing = self.exists()
context.setdefault(
'active_id', existing.ids[0] if existing.ids else None)
context.setdefault('active_ids', existing.ids)
context.setdefault('active_model', self._name)
return self.with_context(context)

175
partner_relations/model/res_partner_relation_type_selection.py

@ -1,175 +0,0 @@
# -*- coding: utf-8 -*-
'''
Created on 23 may 2014
@author: Ronald Portier, Therp
rportier@therp.nl
http://www.therp.nl
For the model defined here _auto is set to False to prevent creating a
database file. All i/o operations are overridden to use a sql SELECT that
takes data from res_partner_connection_type where each type is included in the
result set twice, so it appears that the connection type and the inverse
type are separate records..
The original function _auto_init is still called because this function
normally (if _auto == True) not only creates the db tables, but it also takes
care of registering all fields in ir_model_fields. This is needed to make
the field labels translatable.
example content for last lines of _statement:
select id, record_type,
customer_id, customer_name, customer_city, customer_zip, customer_street,
caller_id, caller_name, caller_phone, caller_fax, caller_email
from FULL_LIST as ResPartnerRelationTypeSelection where record_type = 'c'
ORDER BY ResPartnerRelationTypeSelection.customer_name asc,
ResPartnerRelationTypeSelection.caller_name asc;
'''
from openerp import api
from openerp.osv import fields
from openerp.osv import orm
from openerp.tools import drop_view_if_exists
from .res_partner_relation_type import ResPartnerRelationType
from .res_partner import PADDING
class ResPartnerRelationTypeSelection(orm.Model):
'''Virtual relation types'''
_RECORD_TYPES = [
('a', 'Type'),
('b', 'Inverse type'),
]
_auto = False # Do not try to create table in _auto_init(..)
_log_access = False
@api.multi
def get_type_from_selection_id(self):
"""Selection id ic computed from id of underlying type and the
kind of record. This function does the inverse computation to give
back the original type id, and about the record type."""
type_id = self.id / PADDING
is_reverse = (self.id % PADDING) > 0
return type_id, is_reverse
def _auto_init(self, cr, context=None):
drop_view_if_exists(cr, self._table)
# TODO: we lose field value's translations here.
# probably we need to patch ir_translation.get_source for that
# to get res_partner_relation_type's translations
cr.execute(
'''create or replace view %(table)s as
select
id * %(padding)d as id,
id as type_id,
cast('a' as char(1)) as record_type,
name as name,
contact_type_left as contact_type_this,
contact_type_right as contact_type_other,
partner_category_left as partner_category_this,
partner_category_right as partner_category_other
from %(underlying_table)s
union select
id * %(padding)d + 1,
id,
cast('b' as char(1)),
name_inverse,
contact_type_right,
contact_type_left,
partner_category_right,
partner_category_left
from %(underlying_table)s''' % {
'table': self._table,
'padding': PADDING,
'underlying_table': 'res_partner_relation_type',
})
return super(ResPartnerRelationTypeSelection, self)._auto_init(
cr, context=context)
def _search_partner_category_this(self, cr, uid, obj, field_name, args,
context=None):
category_ids = []
for arg in args:
if isinstance(arg, tuple) and arg[0] == field_name\
and (arg[1] == '=' or arg[1] == 'in'):
# TODO don't we have an api function to eval that?
for delta in arg[2]:
if delta[0] == 6:
category_ids.extend(delta[2])
if category_ids:
return [
'|',
('partner_category_this', '=', False),
('partner_category_this', 'in', category_ids),
]
else:
return [('partner_category_this', '=', False)]
_name = 'res.partner.relation.type.selection'
_description = 'All relation types'
_foreign_keys = []
_columns = {
'record_type': fields.selection(_RECORD_TYPES, 'Record type', size=16),
'type_id': fields.many2one(
'res.partner.relation.type', 'Type'),
'name': fields.char('Name', size=64),
'contact_type_this': fields.selection(
ResPartnerRelationType._get_partner_types.im_func,
'Current record\'s partner type'),
'contact_type_other': fields.selection(
ResPartnerRelationType._get_partner_types.im_func,
'Other record\'s partner type'),
'partner_category_this': fields.many2one(
'res.partner.category', 'Current record\'s category'),
'partner_category_other': fields.many2one(
'res.partner.category', 'Other record\'s category'),
# search field to handle many2many deltas from the client
'search_partner_category_this': fields.function(
lambda self, cr, uid, ids, context=None: dict(
[(i, False) for i in ids]),
fnct_search=_search_partner_category_this,
type='many2many', obj='res.partner.category',
string='Current record\'s category'),
}
_order = 'name asc'
def name_get(self, cr, uid, ids, context=None):
'translate name using translations from res.partner.relation.type'
result = super(ResPartnerRelationTypeSelection, self).name_get(
cr, uid, ids, context=context)
ir_translation = self.pool['ir.translation']
return [
(i, ir_translation._get_source(
cr, uid,
'res.partner.relation.type,name_inverse'
if self.get_type_from_selection_id(cr, uid, i)[1]
else 'res.partner.relation.type,name',
'model', context.get('lang'), name))
for i, name in result]
def name_search(self, cr, uid, name='', args=None, operator='ilike',
context=None, limit=100):
'search for translated names in res.partner.relation.type'
res_partner_relation_type = self.pool['res.partner.relation.type']
relation_ids = res_partner_relation_type.search(
cr, uid, [('name', operator, name)],
context=context)
inverse_relation_ids = res_partner_relation_type.search(
cr, uid, [('name_inverse', operator, name)],
context=context)
all_ids = self.search(
cr, uid,
[
('id', 'in',
map(lambda x: x * PADDING, relation_ids) +
map(lambda x: x * PADDING + 1, inverse_relation_ids)),
] + (args or []),
context=context, limit=limit)
return self.name_get(cr, uid, all_ids, context=context)

8
partner_relations/models/__init__.py

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import res_partner
from . import res_partner_relation
from . import res_partner_relation_type
from . import res_partner_relation_all
from . import res_partner_relation_type_selection

213
partner_relations/models/res_partner.py

@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import numbers
from openerp import _, models, fields, exceptions, api
from openerp.osv.expression import is_leaf, OR, FALSE_LEAF
PADDING = 10
class ResPartner(models.Model):
_inherit = 'res.partner'
relation_count = fields.Integer(
'Relation Count',
compute="_compute_relation_count"
)
relation_ids = fields.One2many(
'res.partner.relation', string='Relations',
compute='_compute_relation_ids',
selectable=False,
)
relation_all_ids = fields.One2many(
'res.partner.relation.all', 'this_partner_id',
string='All relations with current partner',
auto_join=True, selectable=False, copy=False,
)
search_relation_id = fields.Many2one(
'res.partner.relation.type.selection', compute=lambda self: None,
search='_search_relation_id', string='Has relation of type',
)
search_relation_partner_id = fields.Many2one(
'res.partner', compute=lambda self: None,
search='_search_related_partner_id', string='Has relation with',
)
search_relation_date = fields.Date(
compute=lambda self: None, search='_search_relation_date',
string='Relation valid',
)
search_relation_partner_category_id = fields.Many2one(
'res.partner.category', compute=lambda self: None,
search='_search_related_partner_category_id',
string='Has relation with a partner in category',
)
@api.one
@api.depends("relation_ids")
def _compute_relation_count(self):
"""Count the number of relations this partner has for Smart Button
Don't count inactive relations.
"""
self.relation_count = len([r for r in self.relation_ids if r.active])
@api.multi
def _compute_relation_ids(self):
'''getter for relation_ids'''
self.env.cr.execute(
"select p.id, array_agg(r.id) "
"from res_partner p join res_partner_relation r "
"on r.left_partner_id=p.id or r.right_partner_id=p.id "
"where p.id in %s "
"group by p.id",
(tuple(self.ids),)
)
partner2relation = dict(self.env.cr.fetchall())
for this in self:
this.relation_ids += self.env['res.partner.relation'].browse(
partner2relation.get(this.id, []),
)
@api.model
def _search_relation_id(self, operator, value):
result = []
if operator not in [
'=', '!=', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in'
]:
raise exceptions.ValidationError(
_('Unsupported search operator "%s"') % operator)
relation_type_selection = []
if operator == '=' and isinstance(value, numbers.Integral):
relation_type_selection += self\
.env['res.partner.relation.type.selection']\
.browse(value)
elif operator == '!=' and isinstance(value, numbers.Integral):
relation_type_selection = self\
.env['res.partner.relation.type.selection']\
.search([
('id', operator, value),
])
else:
relation_type_selection = self\
.env['res.partner.relation.type.selection']\
.search([
('type_id.name', operator, value),
])
if not relation_type_selection:
result = [FALSE_LEAF]
for relation_type in relation_type_selection:
type_id, is_inverse = relation_type.get_type_from_selection_id()
result = OR([
result,
[
'&',
('relation_all_ids.type_id', '=', type_id),
(
'relation_all_ids.record_type', 'in',
['a', 'b']
if relation_type.type_id.symmetric
else
(['b'] if is_inverse else ['a'])
)
],
])
return result
@api.model
def _search_related_partner_id(self, operator, value):
return [
('relation_all_ids.other_partner_id', operator, value),
]
@api.model
def _search_relation_date(self, operator, value):
if operator != '=':
raise exceptions.ValidationError(
_('Unsupported search operator "%s"') % operator)
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),
]
@api.model
def _search_related_partner_category_id(self, operator, value):
return [
('relation_all_ids.other_partner_id.category_id', operator, value),
]
@api.model
def search(self, args, offset=0, limit=None, order=None, count=False):
# inject searching for current relation date if we search for relation
# properties and no explicit date was given
date_args = []
for arg in args:
if is_leaf(arg) and arg[0].startswith('search_relation'):
if arg[0] == 'search_relation_date':
date_args = []
break
if not date_args:
date_args = [
('search_relation_date', '=', fields.Date.today()),
]
# because of auto_join, we have to do the active test by hand
active_args = []
if self.env.context.get('active_test', True):
for arg in args:
if is_leaf(arg) and arg[0].startswith('search_relation'):
active_args = [('relation_all_ids.active', '=', True)]
break
return super(ResPartner, self).search(
args + date_args + active_args, offset=offset, limit=limit,
order=order, count=count)
@api.multi
def read(self, fields=None, load='_classic_read'):
return super(ResPartner, self.with_partner_relations_context())\
.read(fields=fields, load=load)
@api.multi
def write(self, vals):
return super(ResPartner, self.with_partner_relations_context())\
.write(vals)
@api.multi
def with_partner_relations_context(self):
context = dict(self.env.context)
if context.get('active_model', self._name) == self._name:
existing = self.exists()
context.setdefault(
'active_id', existing.ids[0] if existing.ids else None)
context.setdefault('active_ids', existing.ids)
context.setdefault('active_model', self._name)
return self.with_context(context)
@api.multi
def get_partner_type(self):
"""Get partner type for relation.
:return: 'c' for company or 'p' for person
:rtype: str
"""
self.ensure_one()
return 'c' if self.is_company else 'p'

286
partner_relations/model/res_partner_relation.py → partner_relations/models/res_partner_relation.py

@ -1,28 +1,9 @@
# -*- coding: utf-8 -*-
'''Define model res.partner.relation'''
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import osv, models, fields, api, exceptions, _
from .res_partner import get_partner_type
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, exceptions, _
from openerp.osv.expression import FALSE_LEAF
from .res_partner import PADDING
class ResPartnerRelation(models.Model):
@ -41,73 +22,40 @@ class ResPartnerRelation(models.Model):
_description = 'Partner relation'
_order = 'active desc, date_start desc, date_end desc'
def _search_any_partner_id(self, operator, value):
return [
'|',
('left_partner_id', operator, value),
('right_partner_id', operator, value),
]
type_selection_id = fields.Many2one(
'res.partner.relation.type.selection',
compute='_compute_fields',
fnct_inv=lambda *args: None,
string='Type',
)
def _get_computed_fields(
self, cr, uid, ids, field_names, arg, context=None):
'''Return a dictionary of dictionaries, with for every partner for
ids, the computed values.'''
def get_values(self, dummy_field_names, dummy_arg, context=None):
'''Get computed values for record'''
values = {}
on_right_partner = self._on_right_partner(self.right_partner_id.id)
# type_selection_id
values['type_selection_id'] = (
((self.type_id.id) * 10) + (on_right_partner and 1 or 0))
# partner_id_display
values['partner_id_display'] = (
self.left_partner_id.id
if on_right_partner
else self.right_partner_id.id
)
return values
return dict([
(i.id, get_values(i, field_names, arg, context=context))
for i in self.browse(cr, uid, ids, context=context)
])
_columns = {
'type_selection_id': osv.fields.function(
_get_computed_fields,
multi="computed_fields",
fnct_inv=lambda *args: None,
type='many2one', obj='res.partner.relation.type.selection',
string='Type',
),
'partner_id_display': osv.fields.function(
_get_computed_fields,
multi="computed_fields",
fnct_inv=lambda *args: None,
type='many2one', obj='res.partner',
string='Partner'
),
}
partner_id_display = fields.Many2one(
'res.partner',
compute='_compute_fields',
fnct_inv=lambda *args: None,
string='Partner',
)
allow_self = fields.Boolean(related='type_id.allow_self')
left_contact_type = fields.Selection(
lambda s: s.env['res.partner.relation.type']._get_partner_types(),
'Left Partner Type',
compute='_get_partner_type_any',
compute='_compute_any_partner_id',
store=True,
)
right_contact_type = fields.Selection(
lambda s: s.env['res.partner.relation.type']._get_partner_types(),
'Right Partner Type',
compute='_get_partner_type_any',
compute='_compute_any_partner_id',
store=True,
)
any_partner_id = fields.Many2many(
'res.partner',
string='Partner',
compute='_get_partner_type_any',
compute='_compute_any_partner_id',
search='_search_any_partner_id'
)
@ -138,55 +86,111 @@ class ResPartnerRelation(models.Model):
date_end = fields.Date('Ending date')
active = fields.Boolean('Active', default=True)
@api.multi
def _compute_fields(self):
for this in self:
on_right_partner = this._on_right_partner()
this.type_selection_id = self\
.env['res.partner.relation.type.selection']\
.browse(this.type_id.id * PADDING +
(on_right_partner and 1 or 0))
this.partner_id_display = (
this.left_partner_id
if on_right_partner
else this.right_partner_id
)
@api.onchange('type_selection_id')
def _onchange_type_selection_id(self):
'''Set domain on partner_id_display, when selection a relation type'''
result = {
'domain': {'partner_id_display': [FALSE_LEAF]},
}
if not self.type_selection_id:
return result
type_id, is_reverse = self.type_selection_id\
.get_type_from_selection_id()
self.type_id = self.env['res.partner.relation.type'].browse(type_id)
partner_domain = []
check_contact_type = self.type_id.contact_type_right
check_partner_category = self.type_id.partner_category_right
if is_reverse:
# partner_id_display is left partner
check_contact_type = self.type_id.contact_type_left
check_partner_category = self.type_id.partner_category_left
if check_contact_type == 'c':
partner_domain.append(('is_company', '=', True))
if check_contact_type == 'p':
partner_domain.append(('is_company', '=', False))
if check_partner_category:
partner_domain.append(
('category_id', 'child_of', check_partner_category.ids))
result['domain']['partner_id_display'] = partner_domain
return result
@api.one
@api.depends('left_partner_id', 'right_partner_id')
def _get_partner_type_any(self):
self.left_contact_type = get_partner_type(self.left_partner_id)
self.right_contact_type = get_partner_type(self.right_partner_id)
def _compute_any_partner_id(self):
self.left_contact_type = self.left_partner_id.get_partner_type()
self.right_contact_type = self.right_partner_id.get_partner_type()
self.any_partner_id = self.left_partner_id + self.right_partner_id
def _on_right_partner(self, cr, uid, right_partner_id, context=None):
@api.model
def _search_any_partner_id(self, operator, value):
return [
'|',
('left_partner_id', operator, value),
('right_partner_id', operator, value),
]
@api.multi
def _on_right_partner(self):
'''Determine wether functions are called in a situation where the
active partner is the right partner. Default False!
'''
if (context and 'active_ids' in context and
right_partner_id in context.get('active_ids', [])):
return True
return False
return set(self.mapped('right_partner_id').ids) &\
set(self.env.context.get('active_ids', []))
@api.model
def _correct_vals(self, vals):
"""Fill type and left and right partner id, according to whether
we have a normal relation type or an inverse relation type
"""
vals = vals.copy()
# If type_selection_id ends in 1, it is a reverse relation type
if 'type_selection_id' in vals:
prts_model = self.env['res.partner.relation.type.selection']
type_selection_id = vals['type_selection_id']
(type_id, is_reverse) = (
prts_model.browse(type_selection_id).
get_type_from_selection_id()
)
vals['type_id'] = type_id
if self._context.get('active_id'):
if is_reverse:
vals['right_partner_id'] = self._context['active_id']
else:
vals['left_partner_id'] = self._context['active_id']
if vals.get('partner_id_display'):
if is_reverse:
vals['left_partner_id'] = vals['partner_id_display']
else:
vals['right_partner_id'] = vals['partner_id_display']
if vals.get('other_partner_id'):
if is_reverse:
vals['left_partner_id'] = vals['other_partner_id']
else:
vals['right_partner_id'] = vals['other_partner_id']
del vals['other_partner_id']
if vals.get('contact_type'):
del vals['contact_type']
if 'type_selection_id' not in vals:
return vals
type_id, is_reverse = self\
.env['res.partner.relation.type.selection']\
.browse(vals['type_selection_id'])\
.get_type_from_selection_id()
vals['type_id'] = type_id
if self._context.get('active_id'):
if is_reverse:
vals['right_partner_id'] = self._context['active_id']
else:
vals['left_partner_id'] = self._context['active_id']
if vals.get('partner_id_display'):
if is_reverse:
vals['left_partner_id'] = vals['partner_id_display']
else:
vals['right_partner_id'] = vals['partner_id_display']
if vals.get('other_partner_id'):
if is_reverse:
vals['left_partner_id'] = vals['other_partner_id']
else:
vals['right_partner_id'] = vals['other_partner_id']
del vals['other_partner_id']
if vals.get('this_partner_id'):
if is_reverse:
vals['right_partner_id'] = vals['this_partner_id']
else:
vals['left_partner_id'] = vals['this_partner_id']
del vals['this_partner_id']
if vals.get('contact_type'):
del vals['contact_type']
return vals
@api.multi
@ -201,46 +205,6 @@ class ResPartnerRelation(models.Model):
vals = self._correct_vals(vals)
return super(ResPartnerRelation, self).create(vals)
def on_change_type_selection_id(
self, cr, uid, dummy_ids, type_selection_id, context=None):
'''Set domain on partner_id_display, when selection a relation type'''
result = {
'domain': {'partner_id_display': []},
'value': {'type_id': False}
}
if not type_selection_id:
return result
prts_model = self.pool['res.partner.relation.type.selection']
type_model = self.pool['res.partner.relation.type']
(type_id, is_reverse) = (
prts_model.get_type_from_selection_id(
cr, uid, type_selection_id)
)
result['value']['type_id'] = type_id
type_obj = type_model.browse(cr, uid, type_id, context=context)
partner_domain = []
check_contact_type = type_obj.contact_type_right
check_partner_category = (
type_obj.partner_category_right and
type_obj.partner_category_right.id
)
if is_reverse:
# partner_id_display is left partner
check_contact_type = type_obj.contact_type_left
check_partner_category = (
type_obj.partner_category_left and
type_obj.partner_category_left.id
)
if check_contact_type == 'c':
partner_domain.append(('is_company', '=', True))
if check_contact_type == 'p':
partner_domain.append(('is_company', '=', False))
if check_partner_category:
partner_domain.append(
('category_id', 'child_of', check_partner_category))
result['domain']['partner_id_display'] = partner_domain
return result
@api.one
@api.constrains('date_start', 'date_end')
def _check_dates(self):
@ -329,25 +293,24 @@ class ResPartnerRelation(models.Model):
_('There is already a similar relation with overlapping dates')
)
def get_action_related_partners(self, cr, uid, ids, context=None):
@api.multi
def get_action_related_partners(self):
'''return a window action showing a list of partners taking part in the
relations names by ids. Context key 'partner_relations_show_side'
determines if we show 'left' side, 'right' side or 'all' (default)
partners.
If active_model is res.partner.relation.all, left=this and
right=other'''
if context is None:
context = {}
field_names = {}
if context.get('active_model', self._name) == self._name:
if self.env.context.get('active_model', self._name) == self._name:
field_names = {
'left': ['left'],
'right': ['right'],
'all': ['left', 'right']
}
elif context.get('active_model') == 'res.partner.relation.all':
elif self.env.context.get('active_model') ==\
'res.partner.relation.all':
field_names = {
'left': ['this'],
'right': ['other'],
@ -356,21 +319,22 @@ class ResPartnerRelation(models.Model):
else:
assert False, 'Unknown active_model!'
partner_ids = []
partners = self.env['res.partner'].browse([])
field_names = field_names[
context.get('partner_relations_show_side', 'all')]
self.env.context.get('partner_relations_show_side', 'all')
]
field_names = ['%s_partner_id' % n for n in field_names]
for relation in self.pool[context.get('active_model')].read(
cr, uid, ids, context=context, load='_classic_write'):
for relation in self.env[self.env.context.get('active_model')].browse(
self.ids):
for name in field_names:
partner_ids.append(relation[name])
partners += relation[name]
return {
'name': _('Related partners'),
'type': 'ir.actions.act_window',
'res_model': 'res.partner',
'domain': [('id', 'in', partner_ids)],
'domain': [('id', 'in', partners.ids)],
'views': [(False, 'tree'), (False, 'form')],
'view_type': 'form'
}

71
partner_relations/model/res_partner_relation_all.py → partner_relations/models/res_partner_relation_all.py

@ -1,29 +1,12 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# © 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from psycopg2.extensions import AsIs
from openerp import models, fields, api
from openerp.tools import drop_view_if_exists
from .res_partner_relation_type_selection import \
from .res_partner_relation_type_selection import\
ResPartnerRelationTypeSelection
from .res_partner import get_partner_type, PADDING
from .res_partner import PADDING
class ResPartnerRelationAll(models.AbstractModel):
@ -101,7 +84,7 @@ class ResPartnerRelationAll(models.AbstractModel):
cr.execute(
'''create or replace view %(table)s as
select
id * %(padding)d as id,
id * %(padding)s as id,
id as relation_id,
type_id,
cast('a' as char(1)) as record_type,
@ -111,11 +94,11 @@ class ResPartnerRelationAll(models.AbstractModel):
date_start,
date_end,
active,
type_id * %(padding)d as type_selection_id
type_id * %(padding)s as type_selection_id
%(additional_view_fields)s
from %(underlying_table)s
union select
id * %(padding)d + 1,
id * %(padding)s + 1,
id,
type_id,
cast('b' as char(1)),
@ -125,28 +108,32 @@ class ResPartnerRelationAll(models.AbstractModel):
date_start,
date_end,
active,
type_id * %(padding)d + 1
type_id * %(padding)s + 1
%(additional_view_fields)s
from %(underlying_table)s''' % {
'table': self._table,
from %(underlying_table)s''',
{
'table': AsIs(self._table),
'padding': PADDING,
'additional_view_fields': additional_view_fields,
'underlying_table': 'res_partner_relation',
'additional_view_fields': AsIs(additional_view_fields),
'underlying_table': AsIs('res_partner_relation'),
}
)
return super(ResPartnerRelationAll, self)._auto_init(
cr, context=context)
@api.multi
def _get_underlying_object(self):
"""Get the record on which this record is overlaid"""
return self.env[self._overlays].browse(self.id / PADDING)
return self.env[self._overlays].browse(
i / PADDING for i in self.ids)
@api.multi
def _get_default_contact_type(self):
partner_id = self._context.get('default_this_partner_id')
if partner_id:
partner = self.env['res.partner'].browse(partner_id)
return get_partner_type(partner)
return partner.get_partner_type()
return False
@api.multi
@ -154,7 +141,7 @@ class ResPartnerRelationAll(models.AbstractModel):
return {
this.id: '%s %s %s' % (
this.this_partner_id.name,
this.type_selection_id.name_get()[0][1],
this.type_selection_id.display_name,
this.other_partner_id.name,
)
for this in self
@ -189,7 +176,7 @@ class ResPartnerRelationAll(models.AbstractModel):
'|',
('contact_type_this', '=', False),
('contact_type_this', '=',
'c' if self.this_partner_id else 'p'),
self.this_partner_id.get_partner_type()),
'|',
('partner_category_this', '=', False),
('partner_category_this', 'in',
@ -198,19 +185,15 @@ class ResPartnerRelationAll(models.AbstractModel):
},
}
@api.one
@api.multi
def write(self, vals):
"""divert non-problematic writes to underlying table"""
underlying_objs = self._get_underlying_object()
vals = {
key: val
for key, val in vals.iteritems()
if not self._columns[key].readonly
if not self._fields[key].readonly
}
vals['type_selection_id'] = vals.get(
'type_selection_id',
underlying_objs.type_selection_id.id
)
return underlying_objs.write(vals)
@api.model
@ -222,16 +205,12 @@ class ResPartnerRelationAll(models.AbstractModel):
vals = {
key: val
for key, val in vals.iteritems()
if not self._columns[key].readonly
if not self._fields[key].readonly
}
vals['type_selection_id'] = vals.get(
'type_selection_id',
False,
)
res = self.env[self._overlays].create(vals)
return self.browse(res.id * PADDING)
@api.one
@api.multi
def unlink(self):
"""divert non-problematic creates to underlying table"""
return self._get_underlying_object().unlink()

43
partner_relations/model/res_partner_relation_type.py → partner_relations/models/res_partner_relation_type.py

@ -1,25 +1,6 @@
# -*- coding: utf-8 -*-
'''Define model res.partner.relation.type'''
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# © 2013-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, _
@ -31,13 +12,11 @@ class ResPartnerRelationType(models.Model):
name = fields.Char(
'Name',
size=128,
required=True,
translate=True,
)
name_inverse = fields.Char(
'Inverse name',
size=128,
required=True,
translate=True,
)
@ -58,7 +37,15 @@ class ResPartnerRelationType(models.Model):
'Right partner category',
)
allow_self = fields.Boolean(
'Allow both sides to be the same',
'Reflexive',
help='This relation can be set up with the same partner left and '
'right',
default=False,
)
symmetric = fields.Boolean(
'Symmetric',
help='This relation is the same from right to left as from left to '
'right',
default=False,
)
@ -68,3 +55,11 @@ class ResPartnerRelationType(models.Model):
('c', _('Company')),
('p', _('Person')),
]
@api.onchange('symmetric')
def _onchange_symmetric(self):
self.update({
'name_inverse': self.name,
'contact_type_right': self.contact_type_left,
'partner_category_right': self.partner_category_left,
})

149
partner_relations/models/res_partner_relation_type_selection.py

@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
# © 2014-2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
'''
Created on 23 may 2014
@author: Ronald Portier, Therp
rportier@therp.nl
http://www.therp.nl
For the model defined here _auto is set to False to prevent creating a
database file. All i/o operations are overridden to use a sql SELECT that
takes data from res_partner_connection_type where each type is included in the
result set twice, so it appears that the connection type and the inverse
type are separate records..
The original function _auto_init is still called because this function
normally (if _auto == True) not only creates the db tables, but it also takes
care of registering all fields in ir_model_fields. This is needed to make
the field labels translatable.
example content for last lines of _statement:
select id, record_type,
customer_id, customer_name, customer_city, customer_zip, customer_street,
caller_id, caller_name, caller_phone, caller_fax, caller_email
from FULL_LIST as ResPartnerRelationTypeSelection where record_type = 'c'
ORDER BY ResPartnerRelationTypeSelection.customer_name asc,
ResPartnerRelationTypeSelection.caller_name asc;
'''
from psycopg2.extensions import AsIs
from openerp import api, fields, models
from openerp.tools import drop_view_if_exists
from .res_partner_relation_type import ResPartnerRelationType
from .res_partner import PADDING
class ResPartnerRelationTypeSelection(models.Model):
'''Virtual relation types'''
_name = 'res.partner.relation.type.selection'
_description = 'All relation types'
_auto = False # Do not try to create table in _auto_init(..)
_foreign_keys = []
_log_access = False
_order = 'name asc'
_RECORD_TYPES = [
('a', 'Type'),
('b', 'Inverse type'),
]
record_type = fields.Selection(_RECORD_TYPES, 'Record type')
type_id = fields.Many2one('res.partner.relation.type', 'Type')
name = fields.Char('Name')
contact_type_this = fields.Selection(
ResPartnerRelationType._get_partner_types.im_func,
'Current record\'s partner type')
contact_type_other = fields.Selection(
ResPartnerRelationType._get_partner_types.im_func,
'Other record\'s partner type')
partner_category_this = fields.Many2one(
'res.partner.category', 'Current record\'s category')
partner_category_other = fields.Many2one(
'res.partner.category', 'Other record\'s category')
def _auto_init(self, cr, context=None):
drop_view_if_exists(cr, self._table)
cr.execute(
'''create or replace view %(table)s as
select
id * %(padding)s as id,
id as type_id,
cast('a' as char(1)) as record_type,
name as name,
contact_type_left as contact_type_this,
contact_type_right as contact_type_other,
partner_category_left as partner_category_this,
partner_category_right as partner_category_other
from %(underlying_table)s
union select
id * %(padding)s + 1,
id,
cast('b' as char(1)),
name_inverse,
contact_type_right,
contact_type_left,
partner_category_right,
partner_category_left
from %(underlying_table)s''',
{
'table': AsIs(self._table),
'padding': PADDING,
'underlying_table': AsIs('res_partner_relation_type'),
})
return super(ResPartnerRelationTypeSelection, self)._auto_init(
cr, context=context)
@api.multi
def name_get(self):
"""translate name using translations from res.partner.relation.type"""
ir_translation = self.env['ir.translation']
return [
(
this.id,
ir_translation._get_source(
'res.partner.relation.type,name_inverse'
if this.get_type_from_selection_id()[1]
else 'res.partner.relation.type,name',
('model',),
self.env.context.get('lang'),
this.name,
this.type_id.id
)
)
for this in self
]
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
"""search for translated names in res.partner.relation.type"""
res_partner_relation_type = self.env['res.partner.relation.type']
relations = res_partner_relation_type.search([
('name', operator, name)
])
inverse_relations = res_partner_relation_type.search([
('name_inverse', operator, name),
('symmetric', '=', False),
])
return self.search(
[
(
'id', 'in',
map(lambda x: x * PADDING, relations.ids) +
map(lambda x: x * PADDING + 1, inverse_relations.ids)
),
] + (args or []),
limit=limit
).name_get()
@api.multi
def get_type_from_selection_id(self):
"""Selection id ic computed from id of underlying type and the
kind of record. This function does the inverse computation to give
back the original type id, and about the record type."""
type_id = self.id / PADDING
is_reverse = (self.id % PADDING) > 0
return type_id, is_reverse

22
partner_relations/test/test_allow.yml

@ -1,22 +0,0 @@
-
I create a relation allowing the same partner at both ends.
-
!record {model: res.partner.relation.type, id: partner_relations.allow_self}:
name: 'Relation Allow'
name_inverse: 'Inverse Relation Allow'
contact_type_right: 'p'
contact_type_left: 'p'
allow_self: True
-
I create a partner U for testing purposes
-
!record {model: res.partner, id: partner_relations.test_U}:
name: 'unittests.U'
image: ''
-
I create relation instance U -- (allow) --> U
-
!record {model: res.partner.relation, id: partner_relations.test_allow}:
left_partner_id: partner_relations.test_U
right_partner_id: partner_relations.test_U
type_id: partner_relations.allow_self

178
partner_relations/tests/test_partner_relations.py

@ -1,20 +1,8 @@
# -*- coding: utf-8 -*-
# Author: Charbel Jacquin
# Copyright 2015 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Copyright 2015 Camptocamp SA
# Copyright 2016 Therp BV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import fields
from openerp.tests import common
from openerp.exceptions import ValidationError
@ -30,13 +18,13 @@ class TestPartnerRelation(common.TransactionCase):
self.relation_model = self.env['res.partner.relation']
self.partner_1 = self.partner_model.create({
'name': 'Test User 1',
'is_company': False
'name': 'Test User 1',
'is_company': False,
})
self.partner_2 = self.partner_model.create({
'name': 'Test User 2',
'is_company': False
'name': 'Test Company',
'is_company': True,
})
self.relation_allow = self.relation_type_model.create({
@ -62,20 +50,150 @@ class TestPartnerRelation(common.TransactionCase):
'contact_type_right': 'p',
})
def test_self_allowed(self):
self.relation_mixed = self.relation_type_model.create({
'name': 'mixed',
'name_inverse': 'mixed_inverse',
'contact_type_left': 'c',
'contact_type_right': 'p',
})
self.relation_model.create({'type_id': self.relation_allow.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_1.id})
self.relation_symmetric = self.relation_type_model.create({
'name': 'sym',
'name_inverse': 'sym',
'symmetric': True,
})
def test_self_allowed(self):
self.relation_model.create({
'type_id': self.relation_allow.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_1.id,
})
def test_self_disallowed(self):
with self.assertRaises(ValidationError):
self.relation_model.create({'type_id': self.relation_disallow.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_1.id})
self.relation_model.create({
'type_id': self.relation_disallow.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_1.id,
})
def test_self_default(self):
with self.assertRaises(ValidationError):
self.relation_model.create({'type_id': self.relation_default.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_1.id})
self.relation_model.create({
'type_id': self.relation_default.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_1.id,
})
def test_self_mixed(self):
with self.assertRaises(ValidationError):
self.relation_model.create({
'type_id': self.relation_mixed.id,
'left_partner_id': self.partner_1.id,
'right_partner_id': self.partner_2.id,
})
def test_searching(self):
relation = self.relation_model.create({
'type_id': self.relation_mixed.id,
'left_partner_id': self.partner_2.id,
'right_partner_id': self.partner_1.id,
})
partners = self.env['res.partner'].search([
('search_relation_id', '=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_2 in partners)
partners = self.env['res.partner'].search([
('search_relation_id', '!=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_1 in partners)
partners = self.env['res.partner'].search([
('search_relation_id', '=', self.relation_mixed.name)
])
self.assertTrue(self.partner_1 in partners)
self.assertTrue(self.partner_2 in partners)
partners = self.env['res.partner'].search([
('search_relation_id', '=', 'unknown relation')
])
self.assertFalse(partners)
partners = self.env['res.partner'].search([
('search_relation_partner_id', '=', self.partner_2.id),
])
self.assertTrue(self.partner_1 in partners)
partners = self.env['res.partner'].search([
('search_relation_date', '=', fields.Date.today()),
])
self.assertTrue(self.partner_1 in partners)
self.assertTrue(self.partner_2 in partners)
def test_ui_functions(self):
relation = self.relation_model.create({
'type_id': self.relation_mixed.id,
'left_partner_id': self.partner_2.id,
'right_partner_id': self.partner_1.id,
})
self.assertEqual(relation.type_selection_id.type_id, relation.type_id)
relation = relation.with_context(
active_id=self.partner_1.id,
active_ids=self.partner_1.ids,
active_model='res.partner.relation',
)
relation.read()
domain = relation._onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', True) in domain['partner_id_display']
)
relation.write({
'type_selection_id': relation.type_selection_id.id,
})
action = relation.get_action_related_partners()
self.assertTrue(self.partner_1.id in action['domain'][0][2])
def test_relation_all(self):
relation_all_record = self.env['res.partner.relation.all']\
.with_context(
active_id=self.partner_2.id,
active_ids=self.partner_2.ids,
).create({
'other_partner_id': self.partner_1.id,
'type_selection_id': self.relation_mixed.id * 10,
})
self.assertEqual(
relation_all_record.display_name, '%s %s %s' % (
self.partner_2.name,
'mixed',
self.partner_1.name,
)
)
domain = relation_all_record.onchange_type_selection_id()['domain']
self.assertTrue(
('is_company', '=', False) in domain['other_partner_id'])
domain = relation_all_record.onchange_this_partner_id()['domain']
self.assertTrue(
('contact_type_this', '=', 'c') in domain['type_selection_id'])
relation_all_record.write({
'type_id': self.relation_mixed.id,
})
relation = relation_all_record.relation_id
relation_all_record.unlink()
self.assertFalse(relation.exists())
def test_symmetric(self):
relation = self.relation_model.create({
'type_id': self.relation_symmetric.id,
'left_partner_id': self.partner_2.id,
'right_partner_id': self.partner_1.id,
})
partners = self.env['res.partner'].search([
('search_relation_id', '=', relation.type_selection_id.id)
])
self.assertTrue(self.partner_1 in partners)
self.assertTrue(self.partner_2 in partners)

0
partner_relations/view/menu.xml → partner_relations/views/menu.xml

0
partner_relations/view/res_partner.xml → partner_relations/views/res_partner.xml

0
partner_relations/view/res_partner_relation.xml → partner_relations/views/res_partner_relation.xml

8
partner_relations/view/res_partner_relation_all.xml → partner_relations/views/res_partner_relation_all.xml

@ -12,16 +12,11 @@
>
<field
name="this_partner_id"
readonly="1"
invisible="1"
/>
<field
name="type_selection_id"
required="True"
domain="[
'|',
('contact_type_this', '=', False),
('contact_type_this', '=', contact_type),
]"
options="{'create': false, 'create_edit': false}"
/>
<field
@ -34,7 +29,6 @@
<field name="date_start" />
<field name="date_end" />
<field name="active" />
<field name="contact_type" invisible="1"/>
</tree>
</field>
</record>

17
partner_relations/view/res_partner_relation_type.xml → partner_relations/views/res_partner_relation_type.xml

@ -2,42 +2,45 @@
<data>
<record id="tree_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field name="type">tree</field>
<field type="xml" name="arch">
<tree version="7.0" string="Partner relation">
<tree>
<field name="name" />
<field name="name_inverse" />
<field name="contact_type_left" />
<field name="contact_type_right" />
<field name="allow_self" />
<field name="symmetric" />
</tree>
</field>
</record>
<record id="form_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field name="type">form</field>
<field type="xml" name="arch">
<form version="7.0" string="Partner relation">
<form>
<sheet>
<group>
<field name="allow_self" />
<group
colspan="2" col="2"
string="Left side of relation"
name="left"
>
<field name="name" />
<field name="contact_type_left" />
<field name="partner_category_left" />
</group>
<group
colspan="2" col="2"
string="Right side of relation"
name="right"
attrs="{'invisible': [('symmetric', '=', True)]}"
>
<field name="name_inverse" />
<field name="contact_type_right" />
<field name="partner_category_right" />
</group>
</group>
<group name="properties" string="Properties">
<field name="allow_self" />
<field name="symmetric" />
</group>
</sheet>
</form>
</field>
Loading…
Cancel
Save