From f32f2b9012e7dff6b8910a75ba8a27ee96f8a6b2 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Sat, 17 Nov 2018 00:10:15 +0100 Subject: [PATCH] [IMP] New module partner_multi_relation_tabs. --- partner_multi_relation_tabs/README.rst | 63 +++++ partner_multi_relation_tabs/__init__.py | 4 + partner_multi_relation_tabs/__manifest__.py | 25 ++ partner_multi_relation_tabs/i18n/nl.po | 182 +++++++++++++++ .../i18n/partner_multi_relation_tabs.pot | 181 +++++++++++++++ .../models/__init__.py | 8 + .../models/res_partner.py | 158 +++++++++++++ .../models/res_partner_relation_all.py | 55 +++++ .../models/res_partner_relation_type.py | 58 +++++ .../res_partner_relation_type_selection.py | 36 +++ .../models/res_partner_tab.py | 102 ++++++++ .../security/ir.model.access.csv | 3 + .../static/description/icon.png | Bin 0 -> 18407 bytes partner_multi_relation_tabs/tests/__init__.py | 4 + .../tests/test_partner_tabs.py | 217 ++++++++++++++++++ partner_multi_relation_tabs/views/menu.xml | 18 ++ .../views/res_partner_relation_all.xml | 17 ++ .../views/res_partner_relation_type.xml | 20 ++ .../views/res_partner_tab.xml | 29 +++ 19 files changed, 1180 insertions(+) create mode 100644 partner_multi_relation_tabs/README.rst create mode 100644 partner_multi_relation_tabs/__init__.py create mode 100644 partner_multi_relation_tabs/__manifest__.py create mode 100644 partner_multi_relation_tabs/i18n/nl.po create mode 100644 partner_multi_relation_tabs/i18n/partner_multi_relation_tabs.pot create mode 100644 partner_multi_relation_tabs/models/__init__.py create mode 100644 partner_multi_relation_tabs/models/res_partner.py create mode 100644 partner_multi_relation_tabs/models/res_partner_relation_all.py create mode 100644 partner_multi_relation_tabs/models/res_partner_relation_type.py create mode 100644 partner_multi_relation_tabs/models/res_partner_relation_type_selection.py create mode 100644 partner_multi_relation_tabs/models/res_partner_tab.py create mode 100644 partner_multi_relation_tabs/security/ir.model.access.csv create mode 100644 partner_multi_relation_tabs/static/description/icon.png create mode 100644 partner_multi_relation_tabs/tests/__init__.py create mode 100644 partner_multi_relation_tabs/tests/test_partner_tabs.py create mode 100644 partner_multi_relation_tabs/views/menu.xml create mode 100644 partner_multi_relation_tabs/views/res_partner_relation_all.xml create mode 100644 partner_multi_relation_tabs/views/res_partner_relation_type.xml create mode 100644 partner_multi_relation_tabs/views/res_partner_tab.xml diff --git a/partner_multi_relation_tabs/README.rst b/partner_multi_relation_tabs/README.rst new file mode 100644 index 000000000..b74ef9700 --- /dev/null +++ b/partner_multi_relation_tabs/README.rst @@ -0,0 +1,63 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +======================== +Partner Relations in tab +======================== + +This module adds the possibility to show certain partner relations in its own +tab instead of the list of all relations. This can be useful if certain +relation types are regularly used and should be overseeable at a glace. + + +Usage +===== + +To use this module, you need to: + +#. Go to ... + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/partner-contact/10.0 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + + +Contributors +------------ + +* Holger Brunn +* Alexandre Fayolle +* Stéphane Bidoul +* Ronald Portier +* George Daramouskas + +Do not contact contributors directly about support or help with technical issues. + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/partner_multi_relation_tabs/__init__.py b/partner_multi_relation_tabs/__init__.py new file mode 100644 index 000000000..39faa769c --- /dev/null +++ b/partner_multi_relation_tabs/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/partner_multi_relation_tabs/__manifest__.py b/partner_multi_relation_tabs/__manifest__.py new file mode 100644 index 000000000..101dca590 --- /dev/null +++ b/partner_multi_relation_tabs/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2018 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Show partner relations in own tab", + "version": "10.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "complexity": "normal", + "category": "Customer Relationship Management", + "depends": [ + 'web_tree_many2one_clickable', + 'partner_multi_relation', + ], + "data": [ + "views/res_partner_tab.xml", + "views/res_partner_relation_type.xml", + "views/res_partner_relation_all.xml", + 'views/menu.xml', + 'security/ir.model.access.csv', + ], + "auto_install": False, + "installable": True, + "application": False, +} diff --git a/partner_multi_relation_tabs/i18n/nl.po b/partner_multi_relation_tabs/i18n/nl.po new file mode 100644 index 000000000..2eaebbe17 --- /dev/null +++ b/partner_multi_relation_tabs/i18n/nl.po @@ -0,0 +1,182 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_multi_relation_tabs +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-11-01 09:32+0000\n" +"PO-Revision-Date: 2017-11-01 09:32+0000\n" +"Last-Translator: Ronald Portier , 2017\n" +"Language-Team: Dutch (https://www.transifex.com/oca/teams/23907/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner.py:26 +#, python-format +msgid "Adding field %s to res.partner nodel." +msgstr "Veld %s wordt toegevoegd aan res.partner model." + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_relation_all +msgid "All (non-inverse + inverse) relations between partners" +msgstr "Alle connecties (van beide kanten) tussen relaties" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_relation_type_selection +msgid "All relation types" +msgstr "Alle connectie types" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_code +msgid "Code" +msgstr "Code" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:32 +#, python-format +msgid "Contact type left not compatible with left tab" +msgstr "Linker type contact is niet verenigbaar met linker tab" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:52 +#, python-format +msgid "Contact type right not compatible with right tab" +msgstr "Rechter type contact is niet verenigbaar met rechter tab" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_create_uid +msgid "Created by" +msgstr "Aangemaakt door" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_create_date +msgid "Created on" +msgstr "Aangemaakt op" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_display_name +msgid "Display Name" +msgstr "Naam weergave" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_id +msgid "ID" +msgstr "ID" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_tab_code +msgid "Language independent code for tab" +msgstr "Taal onafhankelijke code voor tab" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab___last_update +msgid "Last Modified on" +msgstr "Laatst gewijzigd op" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_write_uid +msgid "Last Updated by" +msgstr "Laatst gewijzigd door" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_write_date +msgid "Last Updated on" +msgstr "Laatst gewijzigd op" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_name +msgid "Name" +msgstr "Naam" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner +msgid "Partner" +msgstr "Relatie" + +#. module: partner_multi_relation_tabs +#: model:ir.actions.act_window,name:partner_multi_relation_tabs.action_res_partner_tab +#: model:ir.ui.menu,name:partner_multi_relation_tabs.menu_res_partner_tab +msgid "Relation Tabs" +msgstr "Connectie tabs" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_relation_type +msgid "Partner Relation Type" +msgstr "Type connectie" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:37 +#, python-format +msgid "Partner category left not compatible with left tab" +msgstr "Categorie van linker partner is niet verenigbaar met linker tab" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:57 +#, python-format +msgid "Partner category right not compatible with right tab" +msgstr "Categorie van rechter partner is niet verenigbaar met rechter tab" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_all_tab_id +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_type_selection_tab_id +msgid "Show relation on tab" +msgstr "Toon connectie op tab" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_type_tab_right_id +msgid "Tab for inverse relation" +msgstr "Tab voor de omgekeerde relatie" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_type_tab_left_id +msgid "Tab for this relation" +msgstr "Tab voor deze relatie" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_relation_type_tab_right_id +msgid "Tab in which inverse relations will be visible on partner." +msgstr "Tab waar de omgekeerde connecties zichtbaar zullen zijn op de relatie." + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_relation_type_tab_left_id +msgid "Tab in which these relations will be visible on partner." +msgstr "Tab waarin deze connecties zichtbaar zullen zijn op de relaties." + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_tab +msgid "Tabs to add to partner" +msgstr "Tab die aan de relatie wordt toegevoegd" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner.py:39 +#, python-format +msgid "Updating field %s in res.partner nodel." +msgstr "Veld %s op res.partner model wordt bijgewerkt." + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_partner_category_id +msgid "Valid for partner category" +msgstr "Geldig voor relatiecategorie" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_contact_type +msgid "Valid for partner type" +msgstr "Geldig voor type relatie" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_tab_name +msgid "Will provide title for tab in user language" +msgstr "Wordt gebruikt voor titel van tabblad in de taal van de gebruiker" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner.py:45 +#, python-format +msgid "deleting field %s from res.partner nodel." +msgstr "verwijderen veld % van res.partner model." + diff --git a/partner_multi_relation_tabs/i18n/partner_multi_relation_tabs.pot b/partner_multi_relation_tabs/i18n/partner_multi_relation_tabs.pot new file mode 100644 index 000000000..00d44a960 --- /dev/null +++ b/partner_multi_relation_tabs/i18n/partner_multi_relation_tabs.pot @@ -0,0 +1,181 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_multi_relation_tabs +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-11-01 09:32+0000\n" +"PO-Revision-Date: 2017-11-01 09:32+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner.py:26 +#, python-format +msgid "Adding field %s to res.partner nodel." +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_relation_all +msgid "All (non-inverse + inverse) relations between partners" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_relation_type_selection +msgid "All relation types" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_code +msgid "Code" +msgstr "" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:32 +#, python-format +msgid "Contact type left not compatible with left tab" +msgstr "" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:52 +#, python-format +msgid "Contact type right not compatible with right tab" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_create_uid +msgid "Created by" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_create_date +msgid "Created on" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_display_name +msgid "Display Name" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_id +msgid "ID" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_tab_code +msgid "Language independent code for tab" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab___last_update +msgid "Last Modified on" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_write_date +msgid "Last Updated on" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_name +msgid "Name" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner +msgid "Partner" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.actions.act_window,name:partner_multi_relation_tabs.action_res_partner_tab +#: model:ir.ui.menu,name:partner_multi_relation_tabs.menu_res_partner_tab +msgid "Relation Tabs" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_relation_type +msgid "Partner Relation Type" +msgstr "" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:37 +#, python-format +msgid "Partner category left not compatible with left tab" +msgstr "" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner_relation_type.py:57 +#, python-format +msgid "Partner category right not compatible with right tab" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_all_tab_id +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_type_selection_tab_id +msgid "Show relation on tab" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_type_tab_right_id +msgid "Tab for inverse relation" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_relation_type_tab_left_id +msgid "Tab for this relation" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_relation_type_tab_right_id +msgid "Tab in which inverse relations will be visible on partner." +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_relation_type_tab_left_id +msgid "Tab in which these relations will be visible on partner." +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model,name:partner_multi_relation_tabs.model_res_partner_tab +msgid "Tabs to add to partner" +msgstr "" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner.py:39 +#, python-format +msgid "Updating field %s in res.partner nodel." +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_partner_category_id +msgid "Valid for partner category" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,field_description:partner_multi_relation_tabs.field_res_partner_tab_contact_type +msgid "Valid for partner type" +msgstr "" + +#. module: partner_multi_relation_tabs +#: model:ir.model.fields,help:partner_multi_relation_tabs.field_res_partner_tab_name +msgid "Will provide title for tab in user language" +msgstr "" + +#. module: partner_multi_relation_tabs +#: code:addons/partner_multi_relation_tabs/models/res_partner.py:45 +#, python-format +msgid "deleting field %s from res.partner nodel." +msgstr "" + diff --git a/partner_multi_relation_tabs/models/__init__.py b/partner_multi_relation_tabs/models/__init__.py new file mode 100644 index 000000000..7c93bed9a --- /dev/null +++ b/partner_multi_relation_tabs/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import res_partner_tab +from . import res_partner_relation_type +from . import res_partner_relation_type_selection +from . import res_partner_relation_all +from . import res_partner diff --git a/partner_multi_relation_tabs/models/res_partner.py b/partner_multi_relation_tabs/models/res_partner.py new file mode 100644 index 000000000..f174b8ea8 --- /dev/null +++ b/partner_multi_relation_tabs/models/res_partner.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2018 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +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 + + +_logger = logging.getLogger(__name__) +NAME_PREFIX = 'relation_ids_tab' + + +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.model + def fields_view_get( + self, view_id=None, view_type='form', toolbar=False, + submenu=False): + """Override to add relation tabs to form.""" + result = super(ResPartner, self).fields_view_get( + view_id=view_id, view_type=view_type, toolbar=toolbar, + 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 + extra_fields = self._add_tab_pages(view) + view_model = self.env['ir.ui.view'] + result['arch'], original_fields = view_model.postprocess_and_fields( + self._name, view, result['view_id']) + for fieldname in extra_fields: + result['fields'][fieldname] = original_fields[fieldname] + return result diff --git a/partner_multi_relation_tabs/models/res_partner_relation_all.py b/partner_multi_relation_tabs/models/res_partner_relation_all.py new file mode 100644 index 000000000..5430b633c --- /dev/null +++ b/partner_multi_relation_tabs/models/res_partner_relation_all.py @@ -0,0 +1,55 @@ +# -*- 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 + + +class ResPartnerRelationAll(models.AbstractModel): + """Abstract model to show each relation from two sides.""" + _inherit = 'res.partner.relation.all' + + tab_id = fields.Many2one( + comodel_name='res.partner.tab', + string='Show relation on tab', + readonly=True, + ) + + def _get_additional_view_fields(self): + """Add tab_id to view fields.""" + return ','.join([ + super(ResPartnerRelationAll, self)._get_additional_view_fields(), + "CASE" + " WHEN NOT bas.is_inverse" + " THEN lefttab.id" + " ELSE righttab.id" + " END as tab_id"]) + + def _get_additional_tables(self): + """Add res_partner_tab table to view.""" + return ' '.join([ + super(ResPartnerRelationAll, self)._get_additional_tables(), + "LEFT OUTER JOIN res_partner_tab lefttab" + " ON typ.tab_left_id = lefttab.id", + "LEFT OUTER JOIN res_partner_tab righttab" + " ON typ.tab_right_id = righttab.id"]) + + @api.onchange( + 'this_partner_id', + 'other_partner_id', + ) + 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, + it is with a relation type meant to be placed on that tab. + """ + result = super(ResPartnerRelationAll, self).onchange_partner_id() + if 'default_tab_id' in self.env.context: + if 'domain' not in result: + result['domain'] = {} + if 'type_selection_id' not in result['domain']: + result['domain']['type_selection_id'] = [] + selection_domain = result['domain']['type_selection_id'] + selection_domain.append( + ('tab_id', '=', self.env.context['default_tab_id'])) + return 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 new file mode 100644 index 000000000..59a95cc47 --- /dev/null +++ b/partner_multi_relation_tabs/models/res_partner_relation_type.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ResPartnerRelationType(models.Model): + _inherit = 'res.partner.relation.type' + + tab_left_id = fields.Many2one( + comodel_name='res.partner.tab', + string='Tab for this relation', + help="Tab in which these relations will be visible on partner.") + tab_right_id = fields.Many2one( + comodel_name='res.partner.tab', + string='Tab for inverse relation', + help="Tab in which inverse relations will be visible on partner.") + + @api.multi + @api.constrains( + 'contact_type_left', + 'partner_category_left', + 'tab_left_id') + def _check_tab_left(self): + """Conditions for left partner should be consistent with tab.""" + for rec in self: + if not rec.tab_left_id: + continue + tab_contact_type = rec.tab_left_id.contact_type + if tab_contact_type and tab_contact_type != rec.contact_type_left: + raise ValidationError(_( + "Contact type left not compatible with left tab")) + tab_partner_category_id = rec.tab_left_id.partner_category_id + if tab_partner_category_id and \ + tab_partner_category_id != rec.partner_category_left: + raise ValidationError(_( + "Partner category left not compatible with left tab")) + + @api.multi + @api.constrains( + 'contact_type_right', + 'partner_category_right', + 'tab_right_id') + def _check_tab_right(self): + """Conditions for right partner should be consistent with tab.""" + for rec in self: + if not rec.tab_right_id: + continue + tab_contact_type = rec.tab_right_id.contact_type + if tab_contact_type and tab_contact_type != rec.contact_type_right: + raise ValidationError(_( + "Contact type right not compatible with right tab")) + tab_partner_category_id = rec.tab_right_id.partner_category_id + if tab_partner_category_id and \ + tab_partner_category_id != rec.partner_category_right: + raise ValidationError(_( + "Partner category right not compatible with right tab")) diff --git a/partner_multi_relation_tabs/models/res_partner_relation_type_selection.py b/partner_multi_relation_tabs/models/res_partner_relation_type_selection.py new file mode 100644 index 000000000..c05b4f928 --- /dev/null +++ b/partner_multi_relation_tabs/models/res_partner_relation_type_selection.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright 2013-2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from openerp import fields, models + + +class ResPartnerRelationTypeSelection(models.Model): + """Virtual relation types""" + _inherit = 'res.partner.relation.type.selection' + + tab_id = fields.Many2one( + comodel_name='res.partner.tab', + string='Show relation on tab', + readonly=True, + ) + + def _get_additional_view_fields(self): + """Add tab_id to fields in view.""" + return ','.join([ + super(ResPartnerRelationTypeSelection, self) + ._get_additional_view_fields(), + "CASE" + " WHEN NOT bas.is_inverse" + " THEN lefttab.id" + " ELSE righttab.id" + " END as tab_id"]) + + def _get_additional_tables(self): + """Add two links to res_partner_tab.""" + return ' '.join([ + super(ResPartnerRelationTypeSelection, self) + ._get_additional_tables(), + "LEFT OUTER JOIN res_partner_tab lefttab" + " ON typ.tab_left_id = lefttab.id", + "LEFT OUTER JOIN res_partner_tab righttab" + " ON typ.tab_right_id = righttab.id"]) diff --git a/partner_multi_relation_tabs/models/res_partner_tab.py b/partner_multi_relation_tabs/models/res_partner_tab.py new file mode 100644 index 000000000..dae1d9ba2 --- /dev/null +++ b/partner_multi_relation_tabs/models/res_partner_tab.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright 2017-2018 Therp BV . +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class ResPartnerTab(models.Model): + """Model that defines relation types that might exist between partners""" + _name = 'res.partner.tab' + _description = 'Tabs to add to partner' + _order = 'name' + + @api.model + def get_partner_types(self): + """Partner types are defined by model res.partner.relation.type.""" + # pylint: disable=no-self-use + rprt_model = self.env['res.partner.relation.type'] + return rprt_model.get_partner_types() + + code = fields.Char( + string='Code', + required=True, + help="Language independent code for tab") + name = fields.Char( + string='Name', + required=True, + translate=True, + help="Will provide title for tab in user language") + contact_type = fields.Selection( + selection='get_partner_types', + string='Valid for partner type') + partner_category_id = fields.Many2one( + comodel_name='res.partner.category', + string='Valid for partner category') + partner_ids = fields.Many2many( + comodel_name='res.partner', + string="Partners with this tab", + help="This tab will only show for certain partners.\n" + "Do not combine this with selection for contact type or" + " category.") + + @api.constrains('contact_type', 'partner_category_id', 'partner_ids') + def _check_partner_ids(self): + """If partner_ids filled, other domain fields should be empty.""" + if self.partner_ids and \ + (self.contact_type or self.partner_category_id): + 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. + + If we have no vals, assume unlink. + """ + if vals: + contact_type = vals.get('contact_type', False) + partner_category_id = vals.get('partner_category_id', False) + type_model = self.env['res.partner.relation.type'] + for this in self: + for tab_side in ('left', 'right'): + side_tab = 'tab_%s_id' % tab_side + tab_using = type_model.search([(side_tab, '=', this.id)]) + for relation_type in tab_using: + type_value = relation_type['contact_type_%s' % tab_side] + category_value = \ + relation_type['partner_category_%s' % tab_side] + if (not vals or + (contact_type and contact_type != type_value) or + (partner_category_id and + partner_category_id != category_value.id)): + relation_type.write({side_tab: False}) + + @api.multi + def write(self, vals): + """Remove tab from types no longer satifying criteria.""" + if vals.get('contact_type', False) or \ + 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() diff --git a/partner_multi_relation_tabs/security/ir.model.access.csv b/partner_multi_relation_tabs/security/ir.model.access.csv new file mode 100644 index 000000000..7875667aa --- /dev/null +++ b/partner_multi_relation_tabs/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +read_res_partner_tab,read_res_partner_tab,model_res_partner_tab,,1,0,0,0 +crud_res_partner_tab,crud_res_partner_tab,model_res_partner_tab,base.group_partner_manager,1,1,1,1 diff --git a/partner_multi_relation_tabs/static/description/icon.png b/partner_multi_relation_tabs/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b219b220e874c919f20b361209fc886696027165 GIT binary patch literal 18407 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 literal 0 HcmV?d00001 diff --git a/partner_multi_relation_tabs/tests/__init__.py b/partner_multi_relation_tabs/tests/__init__.py new file mode 100644 index 000000000..c2550f63a --- /dev/null +++ b/partner_multi_relation_tabs/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import test_partner_tabs diff --git a/partner_multi_relation_tabs/tests/test_partner_tabs.py b/partner_multi_relation_tabs/tests/test_partner_tabs.py new file mode 100644 index 000000000..269596e7f --- /dev/null +++ b/partner_multi_relation_tabs/tests/test_partner_tabs.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +# Copyright 2014-2017 Therp BV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from lxml import etree + +from odoo.exceptions import ValidationError +from odoo.tests import common + + +class FakeTab(): + + def __init__(self, id, name): + self.id = id + self.name = name + + +class TestPartnerTabs(common.SingleTransactionCase): + + 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. + view_partner_form = self.env.ref('base.view_partner_form') + view = partner_model.with_context().fields_view_get( + view_id=view_partner_form.id, 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.with_context().fields_view_get(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_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({ + '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)) + # 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) + # 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, + 'contact_type_left': 'c', + 'tab_left_id': executive_tab.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() + self.assertEqual( + type_has_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)) + # Trying to clear either category should raise ValidationError: + with self.assertRaises(ValidationError): + type_has_chairperson.write({'partner_category_left': False}) + with self.assertRaises(ValidationError): + type_has_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}) + with self.assertRaises(ValidationError): + type_has_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)) + # 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) + # 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) + self.assertEqual( + executive_partners.other_partner_id.id, + 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_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) diff --git a/partner_multi_relation_tabs/views/menu.xml b/partner_multi_relation_tabs/views/menu.xml new file mode 100644 index 000000000..0859c9159 --- /dev/null +++ b/partner_multi_relation_tabs/views/menu.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/partner_multi_relation_tabs/views/res_partner_relation_all.xml b/partner_multi_relation_tabs/views/res_partner_relation_all.xml new file mode 100644 index 000000000..adce7fcaa --- /dev/null +++ b/partner_multi_relation_tabs/views/res_partner_relation_all.xml @@ -0,0 +1,17 @@ + + + + + res.partner.relation.all + + + + + + + + + diff --git a/partner_multi_relation_tabs/views/res_partner_relation_type.xml b/partner_multi_relation_tabs/views/res_partner_relation_type.xml new file mode 100644 index 000000000..ce8e2c6d1 --- /dev/null +++ b/partner_multi_relation_tabs/views/res_partner_relation_type.xml @@ -0,0 +1,20 @@ + + + + + res.partner.relation.type + + + + + + + + + + + + diff --git a/partner_multi_relation_tabs/views/res_partner_tab.xml b/partner_multi_relation_tabs/views/res_partner_tab.xml new file mode 100644 index 000000000..db1d1170e --- /dev/null +++ b/partner_multi_relation_tabs/views/res_partner_tab.xml @@ -0,0 +1,29 @@ + + + + + res.partner.tab + + + + + + + + + + res.partner.tab + +
+ + + + + + + +
+
+
+ +