Holger Brunn
8 years ago
No known key found for this signature in database
GPG Key ID: 1C9760FECA3AE18
19 changed files with 707 additions and 865 deletions
-
23partner_relations/__init__.py
-
13partner_relations/__openerp__.py
-
1partner_relations/data/demo.xml
-
26partner_relations/model/__init__.py
-
339partner_relations/model/res_partner.py
-
175partner_relations/model/res_partner_relation_type_selection.py
-
8partner_relations/models/__init__.py
-
213partner_relations/models/res_partner.py
-
286partner_relations/models/res_partner_relation.py
-
71partner_relations/models/res_partner_relation_all.py
-
43partner_relations/models/res_partner_relation_type.py
-
149partner_relations/models/res_partner_relation_type_selection.py
-
22partner_relations/test/test_allow.yml
-
178partner_relations/tests/test_partner_relations.py
-
0partner_relations/views/menu.xml
-
0partner_relations/views/res_partner.xml
-
0partner_relations/views/res_partner_relation.xml
-
8partner_relations/views/res_partner_relation_all.xml
-
17partner_relations/views/res_partner_relation_type.xml
@ -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 |
@ -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 |
@ -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) |
@ -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) |
@ -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 |
@ -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' |
@ -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 |
@ -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 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue