diff --git a/partner_multi_relation_tabs/__init__.py b/partner_multi_relation_tabs/__init__.py index 39faa769c..82d5bfd41 100644 --- a/partner_multi_relation_tabs/__init__.py +++ b/partner_multi_relation_tabs/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2014-2017 Therp BV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import tablib from . import models diff --git a/partner_multi_relation_tabs/__manifest__.py b/partner_multi_relation_tabs/__manifest__.py index 101dca590..9dbe76e90 100644 --- a/partner_multi_relation_tabs/__manifest__.py +++ b/partner_multi_relation_tabs/__manifest__.py @@ -12,6 +12,13 @@ 'web_tree_many2one_clickable', 'partner_multi_relation', ], + "demo": [ + "demo/res_partner_category_demo.xml", + "demo/res_partner_tab_demo.xml", + "demo/res_partner_demo.xml", + "demo/res_partner_relation_type_demo.xml", + "demo/res_partner_relation_demo.xml", + ], "data": [ "views/res_partner_tab.xml", "views/res_partner_relation_type.xml", diff --git a/partner_multi_relation_tabs/demo/res_partner_category_demo.xml b/partner_multi_relation_tabs/demo/res_partner_category_demo.xml new file mode 100644 index 000000000..ad02d7fb7 --- /dev/null +++ b/partner_multi_relation_tabs/demo/res_partner_category_demo.xml @@ -0,0 +1,13 @@ + + + + + + Government + + + + Functionary + + + diff --git a/partner_multi_relation_tabs/demo/res_partner_demo.xml b/partner_multi_relation_tabs/demo/res_partner_demo.xml new file mode 100644 index 000000000..e59740e7e --- /dev/null +++ b/partner_multi_relation_tabs/demo/res_partner_demo.xml @@ -0,0 +1,28 @@ + + + + + + Big company + 1 + + BIG + + + + + Bart Simpson + 0 + BS + + + + Homer Simpson + 0 + HS + + + diff --git a/partner_multi_relation_tabs/demo/res_partner_relation_demo.xml b/partner_multi_relation_tabs/demo/res_partner_relation_demo.xml new file mode 100644 index 000000000..92d8531ab --- /dev/null +++ b/partner_multi_relation_tabs/demo/res_partner_relation_demo.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/partner_multi_relation_tabs/demo/res_partner_relation_type_demo.xml b/partner_multi_relation_tabs/demo/res_partner_relation_type_demo.xml new file mode 100644 index 000000000..80e96a6a1 --- /dev/null +++ b/partner_multi_relation_tabs/demo/res_partner_relation_type_demo.xml @@ -0,0 +1,30 @@ + + + + + + has ceo + is ceo of + c + + p + + + + + + has chairperson + is chairperson of + c + + p + + + + + + diff --git a/partner_multi_relation_tabs/demo/res_partner_tab_demo.xml b/partner_multi_relation_tabs/demo/res_partner_tab_demo.xml new file mode 100644 index 000000000..9c6b1da7b --- /dev/null +++ b/partner_multi_relation_tabs/demo/res_partner_tab_demo.xml @@ -0,0 +1,24 @@ + + + + + committee + Government committee + c + + + + + board + Company executive board + c + + + + positions + Positions held + p + + + + diff --git a/partner_multi_relation_tabs/models/res_partner.py b/partner_multi_relation_tabs/models/res_partner.py index f174b8ea8..ef1db30b7 100644 --- a/partner_multi_relation_tabs/models/res_partner.py +++ b/partner_multi_relation_tabs/models/res_partner.py @@ -1,142 +1,27 @@ # -*- coding: utf-8 -*- # Copyright 2014-2018 Therp BV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# pylint: disable=no-member import logging from lxml import etree -from odoo.osv.orm import transfer_modifiers_to_node -from odoo.osv import expression -from odoo import _, api, fields, models +from odoo import api, fields, models -_logger = logging.getLogger(__name__) -NAME_PREFIX = 'relation_ids_tab' +_logger = logging.getLogger(__name__) # pylint: disable=invalid-name class ResPartner(models.Model): _inherit = 'res.partner' - def _get_tab_fieldname(self, tab): - """Create fieldname for tab.""" - return '%s_%s' % (NAME_PREFIX, tab.id) - - @api.model - def _add_tab_field(self, tab): - fieldname = self._get_tab_fieldname(tab) - _logger.info(_( - "Adding field %s to res.partner model." % fieldname)) - field = fields.One2many( - comodel_name='res.partner.relation.all', - inverse_name='this_partner_id', - domain=[('tab_id', '=', tab.id)], - string=tab.name) - self._add_field(fieldname, field) - - @api.model - def _update_tab_field(self, tab): - fieldname = self._get_tab_fieldname(tab) - if fieldname not in self._fields: - return self._add_tab_field(tab) - _logger.info(_( - "Updating field %s in res.partner model." % fieldname)) - self._fields[fieldname].string = tab.name - - @api.model - def _delete_tab_field(self, fieldname): - _logger.info(_( - "deleting field %s from res.partner model." % fieldname)) - self._pop_field(fieldname) - - @api.model - def _update_tab_fields(self): - """Create a field for each tab that might be shown for a partner.""" - deprecated_tab_fields = [ - name for name in self._fields - if name.startswith(NAME_PREFIX)] - tab_model = self.env['res.partner.tab'] - for tab in tab_model.search([]): # get all tabs - fieldname = self._get_tab_fieldname(tab) - 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 _register_hook(self): - self._update_tab_fields() - - def _create_tab_page(self, fieldname, tab): - """Create an xml node containing the tab page to be added view.""" - # pylint: disable=no-member - tab_page = etree.Element('page') - invisible = [('id', '=', False)] # Partner not created yet - if tab.partner_ids: - invisible = expression.OR([ - invisible, - [('id', 'not in', tab.partner_ids.ids)]]) - else: - if tab.contact_type: - invisible = expression.OR([ - invisible, - [('is_company', '=', tab.contact_type != 'c')]]) - if tab.partner_category_id: - invisible = expression.OR([ - invisible, - [('category_id', '!=', tab.partner_category_id.id)]]) - attrs = {'invisible': invisible} - tab_page.set('string', tab.name) - tab_page.set('attrs', repr(attrs)) - transfer_modifiers_to_node(attrs, tab_page) - field = etree.Element( - 'field', - name=fieldname, - context='{' - '"default_this_partner_id": id,' - '"default_tab_id": %d,' - '"active_test": False}' % tab.id) - tree = etree.Element('tree', editable='bottom') - # Now add fields for the editable tree view in the tab: - type_field = etree.Element( - 'field', - name='type_selection_id', - widget='many2one_clickable') - type_field.set('domain', repr([('tab_id', '=', tab.id)])) - type_field.set('options', repr({'no_create': True})) - tree.append(type_field) - other_partner_field = etree.Element( - 'field', - name='other_partner_id', - widget='many2one_clickable') - other_partner_field.set('options', repr({'no_create': True})) - tree.append(other_partner_field) - tree.append(etree.Element('field', name='date_start')) - tree.append(etree.Element('field', name='date_end')) - field.append(tree) - tab_page.append(field) - return tab_page - - def _add_tab_pages(self, view): - """Adds the relevant tabs to the partner's formview.""" - # pylint: disable=no-member - last_page_nodes = view.xpath('//page[last()]') - if not last_page_nodes: - # Nothing to do if form contains no pages/tabs. - return [] - extra_fields = [] - if not view.xpath('//field[@name="id"]'): - view.append( - etree.Element('field', name='id', invisible='True')) - extra_fields.append('id') - last_page = last_page_nodes[0] - tab_model = self.env['res.partner.tab'] - for tab in tab_model.search([]): # get all tabs - fieldname = self._get_tab_fieldname(tab) - self._update_tab_field(tab) - extra_fields.append(fieldname) - tab_page = self._create_tab_page(fieldname, tab) - last_page.addnext(tab_page) - last_page = tab_page # Keep ordering of tabs - return extra_fields + @api.multi + def browse(self, arg=None, prefetch=None): + for tab in self._get_tabs(): + fieldname = tab.get_fieldname() + if fieldname not in self._fields: + # Check this for performance reasons. + self.add_field(tab) + return super(ResPartner, self).browse(arg=arg, prefetch=prefetch) @api.model def fields_view_get( @@ -148,7 +33,7 @@ class ResPartner(models.Model): submenu=submenu) if view_type != 'form' or self.env.context.get('check_view_ids'): return result - view = etree.fromstring(result['arch']) # pylint: disable=no-member + view = etree.fromstring(result['arch']) extra_fields = self._add_tab_pages(view) view_model = self.env['ir.ui.view'] result['arch'], original_fields = view_model.postprocess_and_fields( @@ -156,3 +41,61 @@ class ResPartner(models.Model): for fieldname in extra_fields: result['fields'][fieldname] = original_fields[fieldname] return result + + def _add_tab_pages(self, view): + """Adds the relevant tabs to the partner's formview.""" + # pylint: disable=no-member + def add_invisible_extra_field(view, extra_fields, fieldname): + """Add invisible field to view.""" + view.append( + etree.Element('field', name=fieldname, invisible='True')) + extra_fields.append(fieldname) + + last_page_nodes = view.xpath('//page[last()]') + if not last_page_nodes: + # Nothing to do if form contains no pages/tabs. + return [] + extra_fields = [] + if not view.xpath('//field[@name="id"]'): + add_invisible_extra_field(view, extra_fields, 'id') + last_page = last_page_nodes[0] + for tab in self._get_tabs(): # get all tabs + self.add_field(tab) + add_invisible_extra_field( + view, extra_fields, tab.get_visible_fieldname()) + extra_fields.append(tab.get_fieldname()) + tab_page = tab.create_page() + last_page.addnext(tab_page) + last_page = tab_page # Keep ordering of tabs + return extra_fields + + @api.depends('is_company', 'category_id') + def _compute_tabs_visibility(self): + """Compute for all tabs wether they should be visible.""" + for tab in self._get_tabs(): # get all tabs + for this in self: + this[tab.get_visible_fieldname()] = \ + tab.compute_visibility(this) + + def _get_tabs(self): + tab_model = self.env['res.partner.tab'] + return tab_model.get_tabs() + + def add_field(self, tab): + """Add tab field to model. + + Will replace existing field if already present. + """ + # Visible field determines wether first field will be visible. + # This is because domains on many2many no longer work in 9.0 + # and above. + visible_field = fields.Boolean(compute='_compute_tabs_visibility') + self._add_field(tab.get_visible_fieldname(), visible_field) + if visible_field not in self._field_computed: + self._field_computed[visible_field] = [visible_field] + tab_field = fields.One2many( + comodel_name='res.partner.relation.all', + inverse_name='this_partner_id', + domain=[('tab_id', '=', tab.tab_record.id)], + string=tab.tab_record.name) + self._add_field(tab.get_fieldname(), tab_field) diff --git a/partner_multi_relation_tabs/models/res_partner_relation_all.py b/partner_multi_relation_tabs/models/res_partner_relation_all.py index 5430b633c..6cd2bf4c9 100644 --- a/partner_multi_relation_tabs/models/res_partner_relation_all.py +++ b/partner_multi_relation_tabs/models/res_partner_relation_all.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -# Copyright 2014-2017 Therp BV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import api, fields, models +# Copyright 2014-2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models class ResPartnerRelationAll(models.AbstractModel): @@ -16,6 +16,7 @@ class ResPartnerRelationAll(models.AbstractModel): def _get_additional_view_fields(self): """Add tab_id to view fields.""" + # pylint: disable=no-member return ','.join([ super(ResPartnerRelationAll, self)._get_additional_view_fields(), "CASE" @@ -26,6 +27,7 @@ class ResPartnerRelationAll(models.AbstractModel): def _get_additional_tables(self): """Add res_partner_tab table to view.""" + # pylint: disable=no-member return ' '.join([ super(ResPartnerRelationAll, self)._get_additional_tables(), "LEFT OUTER JOIN res_partner_tab lefttab" @@ -40,9 +42,10 @@ class ResPartnerRelationAll(models.AbstractModel): def onchange_partner_id(self): """Add tab if needed to type_selection_id domain. - This method makes sure then when a relation is added to a tab, + This method makes sure that when a relation is added to a tab, it is with a relation type meant to be placed on that tab. """ + # pylint: disable=no-member result = super(ResPartnerRelationAll, self).onchange_partner_id() if 'default_tab_id' in self.env.context: if 'domain' not in result: diff --git a/partner_multi_relation_tabs/models/res_partner_relation_type.py b/partner_multi_relation_tabs/models/res_partner_relation_type.py index 59a95cc47..27d83c159 100644 --- a/partner_multi_relation_tabs/models/res_partner_relation_type.py +++ b/partner_multi_relation_tabs/models/res_partner_relation_type.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- # Copyright 2014-2017 Therp BV # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# pylint: disable=no-self-use from odoo import _, api, fields, models from odoo.exceptions import ValidationError class ResPartnerRelationType(models.Model): + # pylint: disable=too-few-public-methods _inherit = 'res.partner.relation.type' tab_left_id = fields.Many2one( diff --git a/partner_multi_relation_tabs/models/res_partner_tab.py b/partner_multi_relation_tabs/models/res_partner_tab.py index dae1d9ba2..84d21bf6e 100644 --- a/partner_multi_relation_tabs/models/res_partner_tab.py +++ b/partner_multi_relation_tabs/models/res_partner_tab.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- # Copyright 2017-2018 Therp BV . -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from ..tablib import Tab + class ResPartnerTab(models.Model): """Model that defines relation types that might exist between partners""" @@ -48,13 +50,6 @@ class ResPartnerTab(models.Model): raise ValidationError(_( "You can not both specify partner_ids and other criteria.")) - @api.model - def create(self, vals): - new_tab = super(ResPartnerTab, self).create(vals) - partner_model = self.env['res.partner'] - partner_model._add_tab_field(new_tab) - return new_tab - @api.multi def update_types(self, vals=None): """Update types on write or unlink. @@ -86,17 +81,16 @@ class ResPartnerTab(models.Model): vals.get('partner_category_id', False): self.update_types(vals) result = super(ResPartnerTab, self).write(vals) - partner_model = self.env['res.partner'] - for this in self: - partner_model._update_tab_field(this) return result @api.multi def unlink(self): """Unlink should first remove references.""" self.update_types() - partner_model = self.env['res.partner'] - for this in self: - fieldname = partner_model._get_tab_fieldname(this) - partner_model._delete_tab_field(fieldname) return super(ResPartnerTab, self).unlink() + + @api.model + def get_tabs(self): + """Convert information on tabs in database to array of objects.""" + tabs = [Tab(tab_record) for tab_record in self.search([])] + return tabs diff --git a/partner_multi_relation_tabs/tablib/__init__.py b/partner_multi_relation_tabs/tablib/__init__.py new file mode 100644 index 000000000..68740b22c --- /dev/null +++ b/partner_multi_relation_tabs/tablib/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from .tab import Tab diff --git a/partner_multi_relation_tabs/tablib/tab.py b/partner_multi_relation_tabs/tablib/tab.py new file mode 100644 index 000000000..3f4657d16 --- /dev/null +++ b/partner_multi_relation_tabs/tablib/tab.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging +from lxml import etree + +from odoo import _ +from odoo.osv.orm import transfer_modifiers_to_node + + +_logger = logging.getLogger(__name__) # pylint: disable=invalid-name + + +NAME_PREFIX = 'relation_ids_tab' + + +class Tab(object): + """Encapsulate the information on a tab in the database.""" + + def __init__(self, tab_record): + """Create tab from tab_record. + + In this version tab_record can be assumed to be a partner.relation.tab. + """ + self.tab_record = tab_record + self.name = tab_record.code + + def get_fieldname(self): + return '%s_%s' % (NAME_PREFIX, self.tab_record.id) + + def get_visible_fieldname(self): + return '%s_visible' % self.get_fieldname() + + def create_page(self): + tab_page = etree.Element('page') + self._set_page_attrs(tab_page) + field = etree.Element( + 'field', + name=self.get_fieldname(), + context='{' + '"default_this_partner_id": id,' + '"default_tab_id": %d,' + '"active_test": False}' % self.tab_record.id) + tab_page.append(field) + tree = etree.Element('tree', editable='bottom') + field.append(tree) + # Now add fields for the editable tree view in the tab. + type_field = etree.Element( + 'field', + name='type_selection_id', + widget='many2one_clickable') + type_field.set('domain', repr([('tab_id', '=', self.tab_record.id)])) + type_field.set('options', repr({'no_create': True})) + tree.append(type_field) + other_partner_field = etree.Element( + 'field', + string=_('Partner'), + name='other_partner_id', + widget='many2one_clickable') + other_partner_field.set('options', repr({'no_create': True})) + tree.append(other_partner_field) + tree.append(etree.Element('field', name='date_start')) + tree.append(etree.Element('field', name='date_end')) + return tab_page + + def _set_page_attrs(self, tab_page): + tab_page.set('string', self.tab_record.name) + attrs = {'invisible': [(self.get_visible_fieldname(), '=', False)]} + tab_page.set('attrs', repr(attrs)) + transfer_modifiers_to_node(attrs, tab_page) + + def compute_visibility(self, partner): + """Compute visibility, dependent on partner and conditions.""" + tab = self.tab_record + if tab.partner_ids: + return partner in tab.partner_ids + if tab.contact_type: + is_company_tab = tab.contact_type == 'c' + if partner.is_company != is_company_tab: + return False + if tab.partner_category_id: + if tab.partner_category_id not in partner.category_id: + return False + return True diff --git a/partner_multi_relation_tabs/tests/__init__.py b/partner_multi_relation_tabs/tests/__init__.py index c2550f63a..1c8fd5840 100644 --- a/partner_multi_relation_tabs/tests/__init__.py +++ b/partner_multi_relation_tabs/tests/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2014-2017 Therp BV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import common from . import test_partner_tabs +from . import test_tab diff --git a/partner_multi_relation_tabs/tests/common.py b/partner_multi_relation_tabs/tests/common.py new file mode 100644 index 000000000..d9f876c39 --- /dev/null +++ b/partner_multi_relation_tabs/tests/common.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo.tests import common + + +class TestCommon(common.SingleTransactionCase): + # pylint: disable=too-many-instance-attributes + post_install = True + + def setUp(self): + """Create common objects for tab tests.""" + # pylint: disable=invalid-name + super(TestCommon, self).setUp() + self.tab_model = self.env['res.partner.tab'] + self.type_model = self.env['res.partner.relation.type'] + self.partner_model = self.env['res.partner'] + self.relation_model = self.env['res.partner.relation'] + # Categories. + self.category_government = self.env.ref( + 'partner_multi_relation_tabs.category_government') + self.category_functionary = self.env.ref( + 'partner_multi_relation_tabs.category_functionary') + # Tabs. + self.tab_committee = self.env.ref( + 'partner_multi_relation_tabs.tab_committee') + self.tab_board = self.env.ref( + 'partner_multi_relation_tabs.tab_board') + self.tab_positions = self.env.ref( + 'partner_multi_relation_tabs.tab_positions') + # Types. + self.type_chairperson = self.env.ref( + 'partner_multi_relation_tabs' + '.relation_type_committee_has_chairperson') + self.type_ceo = self.env.ref( + 'partner_multi_relation_tabs' + '.relation_type_company_has_ceo') + # Partners. + self.partner_big_company = self.env.ref( + 'partner_multi_relation_tabs.partner_big_company') + self.partner_important_person = self.env.ref( + 'partner_multi_relation_tabs.partner_important_person') + self.partner_common_person = self.env.ref( + 'partner_multi_relation_tabs.partner_common_person') + # Relations. + self.relation_company_ceo = self.env.ref( + 'partner_multi_relation_tabs.relation_company_ceo') diff --git a/partner_multi_relation_tabs/tests/test_partner_tabs.py b/partner_multi_relation_tabs/tests/test_partner_tabs.py index 269596e7f..49391bb93 100644 --- a/partner_multi_relation_tabs/tests/test_partner_tabs.py +++ b/partner_multi_relation_tabs/tests/test_partner_tabs.py @@ -1,42 +1,31 @@ # -*- coding: utf-8 -*- -# Copyright 2014-2017 Therp BV -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +# Copyright 2014-2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from lxml import etree from odoo.exceptions import ValidationError -from odoo.tests import common +from . import common +from ..tablib import Tab -class FakeTab(): - - def __init__(self, id, name): - self.id = id - self.name = name - - -class TestPartnerTabs(common.SingleTransactionCase): +class TestPartnerTabs(common.TestCommon): post_install = True def test_create_tab(self): - tab_model = self.env['res.partner.tab'] - partner_model = self.env['res.partner'] - new_tab = tab_model.create({ - 'code': 'executive', - 'name': 'Executive members', - 'contact_type': 'c'}) - self.assertTrue(bool(new_tab)) - # There should now be a field in res_partner for the new tab. - fieldname = partner_model._get_tab_fieldname(new_tab) - self.assertTrue(fieldname in partner_model._fields) - # The form view for partner should now also contain the tab, - # if the view contains tabs in the first place. + self.assertTrue(bool(self.tab_board)) + tab_obj = Tab(self.tab_board) + # fields_view_get should force the creation of the new tabs. view_partner_form = self.env.ref('base.view_partner_form') - view = partner_model.with_context().fields_view_get( + view = self.partner_model.with_context().fields_view_get( view_id=view_partner_form.id, view_type='form') + # The form view for partner should now also contain field 'id'. tree = etree.fromstring(view['arch']) field = tree.xpath('//field[@name="id"]') self.assertTrue(field, 'Id field does not exist.') + # There should now be a field in res_partner for the new tab. + fieldname = tab_obj.get_fieldname() + self.assertTrue(fieldname in self.partner_model._fields) # And we should have a field for the tab: field = tree.xpath('//field[@name="%s"]' % fieldname) self.assertTrue( @@ -44,7 +33,8 @@ class TestPartnerTabs(common.SingleTransactionCase): 'Tab field %s does not exist in %s.' % (fieldname, etree.tostring(tree))) # There should be no effect on the tree view: - view = partner_model.with_context().fields_view_get(view_type='tree') + view = self. partner_model.with_context().fields_view_get( + view_type='tree') tree = etree.fromstring(view['arch']) field = tree.xpath('//field[@name="%s"]' % fieldname) self.assertFalse( @@ -53,165 +43,83 @@ class TestPartnerTabs(common.SingleTransactionCase): (fieldname, etree.tostring(tree))) def test_tab_modifications(self): - category_model = self.env['res.partner.category'] - tab_model = self.env['res.partner.tab'] - type_model = self.env['res.partner.relation.type'] - category_government = category_model.create({'name': 'Government'}) - executive_tab = tab_model.create({ + tab_executive = self.tab_model.create({ 'code': 'executive', 'name': 'Executive members'}) - self.assertTrue(bool(executive_tab)) - type_has_chairperson = type_model.create({ + self.assertTrue(bool(tab_executive)) + type_chairperson = self.type_model.create({ 'name': 'has chairperson', 'name_inverse': 'is chairperson for', + 'contact_type_left': 'p', # This emulates a user mistake. 'contact_type_right': 'p', - 'tab_left_id': executive_tab.id}) - self.assertTrue(bool(type_has_chairperson)) + 'tab_left_id': tab_executive.id}) + self.assertTrue(bool(type_chairperson)) # If we change tab now to be only valid on company partners # the tab_left_id field should be cleared from the type: - executive_tab.write({ - 'contact_type': 'c', - 'partner_category_id': category_government.id}) - self.assertFalse(type_has_chairperson.tab_left_id.id) + tab_executive.write({'contact_type': 'c'}) + self.assertFalse(type_chairperson.tab_left_id.id) # Trying to set the tab back on type should be impossible: with self.assertRaises(ValidationError): - type_has_chairperson.write({'tab_left_id': executive_tab.id}) - # We should be able to change tab, if also changing contact type - # and category: - type_has_chairperson.write({ - 'partner_category_left': category_government.id, + type_chairperson.write({'tab_left_id': tab_executive.id}) + # We should be able to change tab, if also changing contact type. + type_chairperson.write({ 'contact_type_left': 'c', - 'tab_left_id': executive_tab.id}) + 'tab_left_id': tab_executive.id}) self.assertEqual( - type_has_chairperson.tab_left_id.id, - executive_tab.id) - # Unlinking the tab should reset the tab name on relations: - executive_tab.unlink() + type_chairperson.tab_left_id.id, + tab_executive.id) + # Unlinking the tab should reset the tab_left_id on relation type. + tab_executive.unlink() self.assertEqual( - type_has_chairperson.tab_left_id.id, + type_chairperson.tab_left_id.id, False) - def test_relation_type_modifications(self): - category_model = self.env['res.partner.category'] - tab_model = self.env['res.partner.tab'] - type_model = self.env['res.partner.relation.type'] - category_government = category_model.create({'name': 'Government'}) - category_positions = category_model.create({'name': 'Positions'}) - executive_tab = tab_model.create({ - 'code': 'executive', - 'name': 'Executive members', - 'contact_type': 'c', - 'partner_category_id': category_government.id}) - self.assertTrue(bool(executive_tab)) - positions_tab = tab_model.create({ - 'code': 'positions', - 'name': 'Positions held', - 'contact_type': 'p', - 'partner_category_id': category_positions.id}) - self.assertTrue(bool(executive_tab)) - type_has_chairperson = type_model.create({ - 'name': 'has chairperson', - 'name_inverse': 'is chairperson for', - 'partner_category_left': category_government.id, - 'contact_type_left': 'c', - 'tab_left_id': executive_tab.id, - 'partner_category_right': category_positions.id, - 'contact_type_right': 'p', - 'tab_right_id': positions_tab.id}) - self.assertTrue(bool(type_has_chairperson)) + def test_type_modifications(self): + self.assertTrue(bool(self.tab_board)) + self.assertTrue(bool(self.tab_positions)) + self.assertTrue(bool(self.type_chairperson)) # Trying to clear either category should raise ValidationError: with self.assertRaises(ValidationError): - type_has_chairperson.write({'partner_category_left': False}) + self.type_chairperson.write({'partner_category_left': False}) with self.assertRaises(ValidationError): - type_has_chairperson.write({'partner_category_right': False}) + self.type_chairperson.write({'partner_category_right': False}) # Trying to clear either contact type should raise ValidationError: with self.assertRaises(ValidationError): - type_has_chairperson.write({'contact_type_left': False}) + self.type_chairperson.write({'contact_type_left': False}) with self.assertRaises(ValidationError): - type_has_chairperson.write({'contact_type_right': False}) + self.type_chairperson.write({'contact_type_right': False}) def test_relations(self): """Test relations shown on tab.""" - tab_model = self.env['res.partner.tab'] - type_model = self.env['res.partner.relation.type'] - partner_model = self.env['res.partner'] - relation_model = self.env['res.partner.relation'] relation_all_model = self.env['res.partner.relation.all'] - executive_tab = tab_model.create({ - 'code': 'executive', - 'name': 'Executive members'}) - self.assertTrue(bool(executive_tab)) - type_has_chairperson = type_model.create({ - 'name': 'has chairperson', - 'name_inverse': 'is chairperson for', - 'contact_type_right': 'p', - 'tab_left_id': executive_tab.id}) - self.assertTrue(bool(type_has_chairperson)) - big_company = partner_model.create({ - 'name': 'Big company', - 'is_company': True, - 'ref': 'BIG'}) - self.assertTrue(bool(big_company)) - important_person = partner_model.create({ - 'name': 'Bart Simpson', - 'is_company': False, - 'ref': 'BS'}) - self.assertTrue(bool(important_person)) - relation_company_chair = relation_model.create({ - 'left_partner_id': big_company.id, - 'type_id': type_has_chairperson.id, - 'right_partner_id': important_person.id}) - self.assertTrue(bool(relation_company_chair)) + self.assertTrue(bool(self.tab_board)) + self.assertTrue(bool(self.type_ceo)) + self.assertTrue(bool(self.partner_big_company)) + self.assertTrue(bool(self.partner_important_person)) + self.assertTrue(bool(self.relation_company_ceo)) # Now we should be able to find the relation with the tab_id: - relation_all_company_chair = relation_all_model.search([ - ('tab_id', '=', executive_tab.id)], limit=1) - self.assertTrue(bool(relation_all_company_chair)) - self.assertEqual( - relation_company_chair.left_partner_id.id, - relation_all_company_chair.this_partner_id.id) + board_partners = relation_all_model.search([ + ('tab_id', '=', self.tab_board.id)]) + self.assertTrue(bool(board_partners)) + self.assertIn( + self.partner_big_company, + [relation.this_partner_id for relation in board_partners]) # We should find the company on the partner through tab field: - fieldname = partner_model._get_tab_fieldname(executive_tab) - self.assertTrue(fieldname in partner_model._fields) - executive_partners = big_company[fieldname] - self.assertEqual(len(executive_partners), 1) + tab_obj = Tab(self.tab_board) + fieldname = tab_obj.get_fieldname() + self.assertTrue(fieldname in self.partner_model._fields) + board_partners = self.partner_big_company[fieldname] + self.assertEqual(len(board_partners), 1) self.assertEqual( - executive_partners.other_partner_id.id, - important_person.id) + board_partners.other_partner_id.id, + self.partner_important_person.id) # When adding a new relation on a tab, type must be for tab. - onchange_result = executive_partners.with_context( - default_tab_id=executive_tab.id + onchange_result = board_partners.with_context( + default_tab_id=self.tab_board.id ).onchange_partner_id() self.assertTrue(onchange_result) self.assertIn('domain', onchange_result) self.assertIn('type_selection_id', onchange_result['domain']) self.assertEqual( onchange_result['domain']['type_selection_id'][-1], - ('tab_id', '=', executive_tab.id)) - - def test_update_tabs(self): - """Test the function that will create tabs during module loading.""" - tab_model = self.env['res.partner.tab'] - partner_model = self.env['res.partner'] - executive_tab = tab_model.create({ - 'code': 'executive', - 'name': 'Executive members'}) - self.assertTrue(bool(executive_tab)) - tabfield_executive_name = partner_model._get_tab_fieldname( - executive_tab) - # Create some fake tab fields (should be removed). - tab_123 = FakeTab(123, 'First tab') - tab_456 = FakeTab(456, 'Second tab') - # Add "tab fields" - partner_model._add_tab_field(tab_123) - tabfield_123_name = partner_model._get_tab_fieldname(tab_123) - self.assertEqual( - partner_model._fields[tabfield_123_name].string, tab_123.name) - partner_model._add_tab_field(tab_456) - tabfield_456_name = partner_model._get_tab_fieldname(tab_456) - self.assertEqual( - partner_model._fields[tabfield_456_name].string, tab_456.name) - # Now call hook method - partner_model._register_hook() - self.assertFalse(tabfield_123_name in partner_model._fields) - self.assertFalse(tabfield_456_name in partner_model._fields) - self.assertTrue(tabfield_executive_name in partner_model._fields) + ('tab_id', '=', self.tab_board.id)) diff --git a/partner_multi_relation_tabs/tests/test_tab.py b/partner_multi_relation_tabs/tests/test_tab.py new file mode 100644 index 000000000..e5b746d5d --- /dev/null +++ b/partner_multi_relation_tabs/tests/test_tab.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Therp BV . +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +# pylint: disable=protected-access +from . import common + +from ..tablib import Tab + + +class TestTab(common.TestCommon): + + def test_create_page(self): + self.assertTrue(bool(self.tab_board)) + tab_obj = Tab(self.tab_board) + page = tab_obj.create_page() + # And we should have a field for (amongst others) selection_type_id. + field = page.xpath('//field[@name="type_selection_id"]') + self.assertTrue(field, 'Field selection_type_id not in page.') + + def test_visibility(self): + """Tab positions should be shown for functionaries, but not others.""" + self.assertTrue(bool(self.tab_positions)) + self.assertTrue(bool(self.partner_important_person)) + self.assertTrue(bool(self.partner_common_person)) + tab_obj = Tab(self.tab_positions) + self.assertTrue( + tab_obj.compute_visibility(self.partner_important_person), + 'Board tab should be visible for functionary.') + self.assertFalse( + tab_obj.compute_visibility(self.partner_common_person), + 'Board tab should not be visible for non-functionary.')