6 changed files with 356 additions and 249 deletions
-
21partner_relations_in_tab/__init__.py
-
4partner_relations_in_tab/__openerp__.py
-
362partner_relations_in_tab/model/res_partner.py
-
86partner_relations_in_tab/model/res_partner_relation_type.py
-
8partner_relations_in_tab/tests/__init__.py
-
124partner_relations_in_tab/tests/test_partner_tabs.py
@ -1,21 +1,4 @@ |
|||
# -*- 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/>. |
|||
# |
|||
############################################################################## |
|||
# Copyright 2014-2018 Therp BV <https://therp.nl>. |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import model |
@ -1,179 +1,215 @@ |
|||
# -*- 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/>. |
|||
# |
|||
############################################################################## |
|||
# Copyright 2014-2018 Therp BV <https://therp.nl>. |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
import logging |
|||
from lxml import etree |
|||
|
|||
from openerp.osv.orm import Model, transfer_modifiers_to_node |
|||
from openerp.osv import expression |
|||
from openerp.osv import expression, fields |
|||
from openerp.tools.translate import _ |
|||
|
|||
|
|||
class ResPartner(Model): |
|||
_inherit = 'res.partner' |
|||
|
|||
def _get_relation_ids_select(self, cr, uid, ids, field_name, arg, |
|||
context=None): |
|||
cr.execute( |
|||
'''select r.id, left_partner_id, right_partner_id |
|||
from res_partner_relation r |
|||
join res_partner_relation_type t |
|||
on r.type_id = t.id |
|||
where ((left_partner_id in %s and own_tab_left=False) |
|||
or (right_partner_id in %s and own_tab_right=False))''' + |
|||
' order by ' + self.pool['res.partner.relation']._order, |
|||
(tuple(ids), tuple(ids)) |
|||
) |
|||
return cr.fetchall() |
|||
|
|||
def _create_relation_type_tab( |
|||
self, cr, uid, rel_type, inverse, field_names, context=None): |
|||
'''Create an xml node containing the relation's tab to be added to the |
|||
view. Add the field(s) created on the form to field_names.''' |
|||
name = rel_type.name if not inverse else rel_type.name_inverse |
|||
contact_type = rel_type['contact_type_' + |
|||
('left' if not inverse else 'right')] |
|||
partner_category = rel_type['partner_category_' + |
|||
('left' if not inverse |
|||
else 'right')] |
|||
tab = etree.Element('page') |
|||
tab.set('string', name) |
|||
|
|||
invisible = [('id', '=', False)] |
|||
if contact_type: |
|||
invisible = expression.OR([ |
|||
invisible, |
|||
[('is_company', '=', contact_type != 'c')]]) |
|||
if partner_category: |
|||
invisible = expression.OR([ |
|||
invisible, |
|||
[('category_id', '!=', partner_category.id)]]) |
|||
attrs = { |
|||
'invisible': invisible, |
|||
} |
|||
tab.set('attrs', repr(attrs)) |
|||
transfer_modifiers_to_node(attrs, tab) |
|||
|
|||
field_name = 'relation_ids_own_tab_%s_%s' % ( |
|||
rel_type.id, |
|||
'left' if not inverse else 'right') |
|||
field_names.append(field_name) |
|||
this_partner_name = '%s_partner_id' % ( |
|||
'left' if not inverse else 'right') |
|||
other_partner_name = '%s_partner_id' % ( |
|||
'left' if inverse else 'right') |
|||
|
|||
from openerp import SUPERUSER_ID |
|||
|
|||
|
|||
_logger = logging.getLogger(__name__) # pylint: disable=invalid-name |
|||
NAME_PREFIX = 'relation_ids_tab' |
|||
|
|||
|
|||
class Tab(object): |
|||
|
|||
def __init__(self, source, side): |
|||
"""Create tab from source. |
|||
|
|||
In this version source can be assumed to be a partner.relation.type. |
|||
""" |
|||
self.id = source.id |
|||
self.side = side |
|||
if side == 'left': |
|||
self.name = source.name |
|||
self.contact_type = source.contact_type_left |
|||
self.category_id = source.partner_category_left |
|||
self.other_contact_type = source.contact_type_right |
|||
self.other_category_id = source.partner_category_right |
|||
self.other_side = 'right' |
|||
else: |
|||
self.name = source.name_inverse |
|||
self.contact_type = source.contact_type_right |
|||
self.category_id = source.partner_category_right |
|||
self.other_contact_type = source.contact_type_left |
|||
self.other_category_id = source.partner_category_left |
|||
self.other_side = 'left' |
|||
|
|||
def get_fieldname(self): |
|||
return '%s_%s_%s' % (NAME_PREFIX, self.id, self.side) |
|||
|
|||
def get_domain(self): |
|||
return [('type_id', '=', self.id)] |
|||
|
|||
def create_page(self): |
|||
tab_page = etree.Element('page') |
|||
self._set_page_attrs(tab_page) |
|||
field = etree.Element( |
|||
'field', |
|||
name=field_name, |
|||
context=('{"default_type_id": %s, "default_%s": id, ' |
|||
'"active_test": False}') % ( |
|||
rel_type.id, |
|||
this_partner_name)) |
|||
tab.append(field) |
|||
name=self.get_fieldname(), |
|||
context=( |
|||
'{"default_type_id": %s, "default_%s_partner_id": id, ' |
|||
'"active_test": False}') % (self.id, self.side)) |
|||
tab_page.append(field) |
|||
tree = etree.Element('tree', editable='bottom') |
|||
field.append(tree) |
|||
|
|||
onchange_type_values = self.pool['res.partner.relation']\ |
|||
.on_change_type_selection_id(cr, uid, None, |
|||
rel_type.id * 10 + |
|||
(1 if inverse else 0), |
|||
context=context) |
|||
tree.append(etree.Element( |
|||
'field', name='%s_partner_id' % self.side, invisible='True')) |
|||
tree.append(etree.Element( |
|||
'field', |
|||
string=_('Partner'), |
|||
domain=repr( |
|||
onchange_type_values['domain']['partner_id_display']), |
|||
domain=repr(self._get_other_partner_domain()), |
|||
widget='many2one_clickable', |
|||
name=other_partner_name)) |
|||
tree.append(etree.Element( |
|||
'field', |
|||
name='date_start')) |
|||
tree.append(etree.Element( |
|||
'field', |
|||
name='date_end')) |
|||
tree.append(etree.Element( |
|||
'field', |
|||
name='active')) |
|||
tree.append(etree.Element('field', name='type_id', |
|||
invisible='True')) |
|||
tree.append(etree.Element('field', name=this_partner_name, |
|||
invisible='True')) |
|||
return tab |
|||
|
|||
def _add_relation_type_tab( |
|||
self, cr, uid, rel_type, inverse, field_names, relation_tab, |
|||
context=None): |
|||
'''add the xml node to the view''' |
|||
tab = self._create_relation_type_tab( |
|||
cr, uid, rel_type, inverse, field_names, context=context) |
|||
relation_tab.addnext(tab) |
|||
|
|||
def fields_view_get(self, cr, uid, view_id=None, view_type='form', |
|||
context=None, toolbar=False, submenu=False): |
|||
if context is None: |
|||
context = {} |
|||
name='%s_partner_id' % self.other_side)) |
|||
tree.append(etree.Element('field', name='date_start')) |
|||
tree.append(etree.Element('field', name='date_end')) |
|||
tree.append(etree.Element('field', name='active')) |
|||
tree.append(etree.Element('field', name='type_id', invisible='True')) |
|||
return tab_page |
|||
|
|||
def _get_other_partner_domain(self): |
|||
partner_domain = [] |
|||
if self.other_contact_type == 'c': |
|||
partner_domain.append(('is_company', '=', True)) |
|||
if self.other_contact_type == 'p': |
|||
partner_domain.append(('is_company', '=', False)) |
|||
if self.other_category_id: |
|||
partner_domain.append( |
|||
('category_id', 'child_of', self.other_category_id)) |
|||
return partner_domain |
|||
|
|||
def _set_page_attrs(self, tab_page): |
|||
tab_page.set('string', self.name) |
|||
invisible = [('id', '=', False)] |
|||
if self.contact_type: |
|||
invisible = expression.OR([ |
|||
invisible, |
|||
[('is_company', '=', self.contact_type != 'c')]]) |
|||
if self.category_id: |
|||
invisible = expression.OR([ |
|||
invisible, |
|||
[('category_id', '!=', self.category_id)]]) |
|||
attrs = {'invisible': invisible} |
|||
tab_page.set('attrs', repr(attrs)) |
|||
transfer_modifiers_to_node(attrs, tab_page) |
|||
|
|||
|
|||
class ResPartner(Model): |
|||
_inherit = 'res.partner' |
|||
|
|||
def _make_tab(self, source, side): |
|||
return Tab(source, side) |
|||
|
|||
def _register_hook(self, cr): |
|||
"""This function is automatically called by Odoo on all models.""" |
|||
self._update_tab_fields(cr) |
|||
|
|||
def _update_tab_fields(self, cr): |
|||
"""Create a field for each tab that might be shown for a partner.""" |
|||
deprecated_tab_fields = [ |
|||
name for name in self._columns.copy() |
|||
if name.startswith(NAME_PREFIX)] |
|||
tabs = self._get_tabs(cr) |
|||
for tab in tabs: |
|||
fieldname = tab.get_fieldname() |
|||
if fieldname in self._columns: |
|||
self._update_tab_field(tab) |
|||
else: |
|||
self._add_tab_field(tab) |
|||
if fieldname in deprecated_tab_fields: |
|||
deprecated_tab_fields.remove(fieldname) # not deprecated |
|||
for fieldname in deprecated_tab_fields: |
|||
self._delete_tab_field(fieldname) |
|||
|
|||
def _get_tabs(self, cr): |
|||
tabs = [] |
|||
relation_type_model = self.pool['res.partner.relation.type'] |
|||
relation_type_domain = [ |
|||
'|', |
|||
('own_tab_left', '=', True), |
|||
('own_tab_right', '=', True)] |
|||
relation_type_ids = relation_type_model.search( |
|||
cr, SUPERUSER_ID, relation_type_domain) |
|||
for relation_type in relation_type_model.browse( |
|||
cr, SUPERUSER_ID, relation_type_ids): |
|||
if relation_type.own_tab_left: |
|||
new_tab = Tab(relation_type, 'left') |
|||
tabs.append(new_tab) |
|||
if relation_type.own_tab_right: |
|||
new_tab = Tab(relation_type, 'right') |
|||
tabs.append(new_tab) |
|||
return tabs |
|||
|
|||
def _add_tab_field(self, tab): |
|||
field = fields.one2many( |
|||
'res.partner.relation', |
|||
'%s_partner_id' % tab.side, |
|||
string=tab.name, |
|||
domain=tab.get_domain()) |
|||
fieldname = tab.get_fieldname() |
|||
_logger.info(_( |
|||
"Adding field %s to res.partner model.") % fieldname) |
|||
self._columns[fieldname] = field |
|||
|
|||
def _update_tab_field(self, tab): |
|||
fieldname = tab.get_fieldname() |
|||
_logger.info(_( |
|||
"Updating field %s in res.partner model.") % fieldname) |
|||
self._columns[fieldname].string = tab.name |
|||
|
|||
def _delete_tab_field(self, fieldname): |
|||
_logger.info(_( |
|||
"Deleting field %s from res.partner model.") % fieldname) |
|||
del self._columns[fieldname] |
|||
|
|||
def fields_view_get( |
|||
self, cr, uid, view_id=None, view_type='form', context=None, |
|||
toolbar=False, submenu=False): |
|||
context = context or {} |
|||
result = super(ResPartner, self).fields_view_get( |
|||
cr, uid, view_id=view_id, view_type=view_type, context=context, |
|||
toolbar=toolbar, submenu=submenu) |
|||
if view_type == 'form' and not context.get('check_view_ids'): |
|||
res_partner_relation_type = self.pool['res.partner.relation.type'] |
|||
own_tab_types = res_partner_relation_type.browse( |
|||
cr, uid, |
|||
res_partner_relation_type.search( |
|||
cr, uid, |
|||
[ |
|||
'|', |
|||
('own_tab_left', '=', True), |
|||
('own_tab_right', '=', True) |
|||
], |
|||
context=context), |
|||
context=context) |
|||
view = etree.fromstring(result['arch']) |
|||
|
|||
relation_tab = view.xpath( |
|||
'//field[@name="relation_ids"]/ancestor::page') |
|||
if not relation_tab: |
|||
return result |
|||
relation_tab = relation_tab[0] |
|||
|
|||
field_names = [] |
|||
|
|||
if not view.xpath('//field[@name="id"]'): |
|||
view.append(etree.Element('field', name='id', |
|||
invisible='True')) |
|||
field_names.append('id') |
|||
|
|||
for rel_type in own_tab_types: |
|||
if rel_type.own_tab_left: |
|||
self._add_relation_type_tab( |
|||
cr, uid, rel_type, False, field_names, relation_tab, |
|||
context=context) |
|||
if rel_type.own_tab_right: |
|||
self._add_relation_type_tab( |
|||
cr, uid, rel_type, True, field_names, relation_tab, |
|||
context=context) |
|||
|
|||
result['arch'], fields = self\ |
|||
._BaseModel__view_look_dom_arch( |
|||
cr, uid, view, result['view_id'], context=context) |
|||
|
|||
for field_name in field_names: |
|||
result['fields'][field_name] = fields[field_name] |
|||
|
|||
if view_type != 'form' or context.get('check_view_ids'): |
|||
return result |
|||
view = etree.fromstring(result['arch']) |
|||
extra_fields = self._add_tab_pages(cr, view) |
|||
result['arch'], view_fields = self._BaseModel__view_look_dom_arch( |
|||
cr, uid, view, result['view_id'], context=context) |
|||
for fieldname in extra_fields: |
|||
result['fields'][fieldname] = view_fields[fieldname] |
|||
return result |
|||
|
|||
def _add_tab_pages(self, cr, view): |
|||
"""Adds the relevant tabs to the partner's formview.""" |
|||
extra_fields = [] |
|||
if not view.xpath('//field[@name="id"]'): |
|||
view.append( |
|||
etree.Element('field', name='id', invisible='True')) |
|||
extra_fields.append('id') |
|||
element_last_page_hook = view.xpath('//page[last()]')[0] |
|||
for tab in self._get_tabs(cr): |
|||
fieldname = tab.get_fieldname() |
|||
extra_fields.append(fieldname) |
|||
tab_page = tab.create_page() |
|||
_logger.debug( |
|||
_("Adding %s tab %s with arch: %s"), |
|||
tab.side, tab.name, etree.tostring(tab_page)) |
|||
element_last_page_hook.addnext(tab_page) |
|||
return extra_fields |
|||
|
|||
def _get_relation_ids_select( |
|||
self, cr, uid, ids, fieldname, arg, context=None): |
|||
"""Overide domain for other partner on default relations tab.""" |
|||
cr.execute( |
|||
"""select r.id, left_partner_id, right_partner_id |
|||
from res_partner_relation r |
|||
join res_partner_relation_type t |
|||
on r.type_id = t.id |
|||
where ((left_partner_id in %s and own_tab_left=False) |
|||
or (right_partner_id in %s and own_tab_right=False))""" + |
|||
' order by ' + self.pool['res.partner.relation']._order, |
|||
(tuple(ids), tuple(ids))) |
|||
return cr.fetchall() |
@ -0,0 +1,8 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2017-2018 Therp BV <https://therp.nl>. |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import test_partner_tabs |
|||
|
|||
checks = [ |
|||
test_partner_tabs, |
|||
] |
@ -0,0 +1,124 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright 2014-2018 Therp BV <https://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from lxml import etree |
|||
|
|||
from openerp.tests import common |
|||
|
|||
|
|||
class TestPartnerTabs(common.SingleTransactionCase): |
|||
|
|||
post_install = True |
|||
|
|||
def _get_tab_fieldname(self, relation_type, side): |
|||
tab = self._get_tab(relation_type, side) |
|||
return tab.get_fieldname() |
|||
|
|||
def _get_tab(self, relation_type, side): |
|||
partner_model = self.registry('res.partner') |
|||
return partner_model._make_tab(relation_type, side) |
|||
|
|||
def test_create_tab(self): |
|||
cr, uid = self.cr, self.uid |
|||
type_model = self.registry('res.partner.relation.type') |
|||
partner_model = self.registry('res.partner') |
|||
type_has_chairperson_id = type_model.create( |
|||
cr, uid, { |
|||
'name': 'has chairperson', |
|||
'name_inverse': 'is chairperson for', |
|||
'contact_type_left': 'c', |
|||
'own_tab_left': True, |
|||
'contact_type_right': 'p'}) |
|||
type_has_chairperson = type_model.browse( |
|||
cr, uid, type_has_chairperson_id) |
|||
self.assertTrue(bool(type_has_chairperson)) |
|||
# There should now be a field in res_partner for the new tab: |
|||
fieldname = self._get_tab_fieldname(type_has_chairperson, 'left') |
|||
self.assertTrue(fieldname in partner_model._columns) |
|||
# The form view for partner should now also contain the tab: |
|||
view = partner_model.fields_view_get(cr, uid, view_type='form') |
|||
tree = etree.fromstring(view['arch']) |
|||
field = tree.xpath('//field[@name="id"]') |
|||
self.assertTrue(field, 'Id field does not exist.') |
|||
# And we should have a field for the tab: |
|||
field = tree.xpath('//field[@name="%s"]' % fieldname) |
|||
self.assertTrue( |
|||
field, |
|||
'Tab field %s does not exist in %s.' % |
|||
(fieldname, etree.tostring(tree))) |
|||
# There should be no effect on the tree view: |
|||
view = partner_model.fields_view_get(cr, uid, view_type='tree') |
|||
tree = etree.fromstring(view['arch']) |
|||
field = tree.xpath('//field[@name="%s"]' % fieldname) |
|||
self.assertFalse( |
|||
field, |
|||
'Tab field %s should not exist in %s.' % |
|||
(fieldname, etree.tostring(tree))) |
|||
|
|||
def test_relations(self): |
|||
"""Test relations shown on tab.""" |
|||
cr, uid = self.cr, self.uid |
|||
type_model = self.registry('res.partner.relation.type') |
|||
relation_model = self.registry('res.partner.relation') |
|||
partner_model = self.registry('res.partner') |
|||
type_has_chairperson_id = type_model.create( |
|||
cr, uid, { |
|||
'name': 'has chairperson', |
|||
'name_inverse': 'is chairperson for', |
|||
'contact_type_left': 'c', |
|||
'own_tab_left': True, |
|||
'contact_type_right': 'p'}) |
|||
type_has_chairperson = type_model.browse( |
|||
cr, uid, type_has_chairperson_id) |
|||
self.assertTrue(bool(type_has_chairperson)) |
|||
big_company_id = partner_model.create( |
|||
cr, uid, { |
|||
'name': 'Big company', |
|||
'is_company': True, |
|||
'ref': 'BIG'}) |
|||
big_company = partner_model.browse(cr, uid, big_company_id) |
|||
self.assertTrue(bool(big_company)) |
|||
important_person_id = partner_model.create( |
|||
cr, uid, { |
|||
'name': 'Bart Simpson', |
|||
'is_company': False, |
|||
'ref': 'BS'}) |
|||
important_person = partner_model.browse(cr, uid, important_person_id) |
|||
self.assertTrue(bool(important_person)) |
|||
relation_company_chair_id = relation_model.create( |
|||
cr, uid, { |
|||
'left_partner_id': big_company.id, |
|||
'type_id': type_has_chairperson.id, |
|||
'right_partner_id': important_person.id}) |
|||
relation_company_chair = relation_model.browse( |
|||
cr, uid, relation_company_chair_id) |
|||
self.assertTrue(bool(relation_company_chair)) |
|||
# There should now be a field in res_partner for the new tab: |
|||
fieldname = self._get_tab_fieldname(type_has_chairperson, 'left') |
|||
self.assertTrue(fieldname in partner_model._columns) |
|||
# We should find the chairperson of the company through the tab: |
|||
executive_partners = big_company[fieldname] |
|||
self.assertEqual(len(executive_partners), 1) |
|||
self.assertEqual( |
|||
executive_partners[0].right_partner_id.id, |
|||
important_person.id) |
|||
|
|||
def test_update_tabs(self): |
|||
"""Test the function that will create tabs during module loading.""" |
|||
cr, uid = self.cr, self.uid |
|||
type_model = self.registry('res.partner.relation.type') |
|||
partner_model = self.registry('res.partner') |
|||
type_has_chairperson_id = type_model.create( |
|||
cr, uid, { |
|||
'name': 'has chairperson', |
|||
'name_inverse': 'is chairperson for', |
|||
'contact_type_left': 'c', |
|||
'own_tab_left': True, |
|||
'contact_type_right': 'p'}) |
|||
type_has_chairperson = type_model.browse( |
|||
cr, uid, type_has_chairperson_id) |
|||
# Now call hook method |
|||
partner_model._register_hook(cr) |
|||
# There should now be a field in res_partner for the new tab: |
|||
fieldname = self._get_tab_fieldname(type_has_chairperson, 'left') |
|||
self.assertTrue(fieldname in partner_model._columns) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue