Browse Source

[IMP] New module partner_multi_relation_tabs.

pull/702/head
Ronald Portier 6 years ago
committed by Holger Brunn
parent
commit
7cacf62f13
  1. 63
      partner_multi_relation_tabs/README.rst
  2. 4
      partner_multi_relation_tabs/__init__.py
  3. 25
      partner_multi_relation_tabs/__manifest__.py
  4. 182
      partner_multi_relation_tabs/i18n/nl.po
  5. 181
      partner_multi_relation_tabs/i18n/partner_multi_relation_tabs.pot
  6. 8
      partner_multi_relation_tabs/models/__init__.py
  7. 158
      partner_multi_relation_tabs/models/res_partner.py
  8. 55
      partner_multi_relation_tabs/models/res_partner_relation_all.py
  9. 58
      partner_multi_relation_tabs/models/res_partner_relation_type.py
  10. 36
      partner_multi_relation_tabs/models/res_partner_relation_type_selection.py
  11. 102
      partner_multi_relation_tabs/models/res_partner_tab.py
  12. 3
      partner_multi_relation_tabs/security/ir.model.access.csv
  13. BIN
      partner_multi_relation_tabs/static/description/icon.png
  14. 4
      partner_multi_relation_tabs/tests/__init__.py
  15. 217
      partner_multi_relation_tabs/tests/test_partner_tabs.py
  16. 18
      partner_multi_relation_tabs/views/menu.xml
  17. 17
      partner_multi_relation_tabs/views/res_partner_relation_all.xml
  18. 20
      partner_multi_relation_tabs/views/res_partner_relation_type.xml
  19. 29
      partner_multi_relation_tabs/views/res_partner_tab.xml

63
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
<https://github.com/OCA/partner-contact/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 <hbrunn@therp.nl>
* Alexandre Fayolle <alexandre.fayolle@camptocamp.com>
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
* Ronald Portier <ronald@therp.nl>
* George Daramouskas <gdaramouskas@therp.nl>
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.

4
partner_multi_relation_tabs/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2017 Therp BV <https://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models

25
partner_multi_relation_tabs/__manifest__.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2018 Therp BV <https://therp.nl>
# 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,
}

182
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 <ronald@therp.nl>, 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."

181
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 ""

8
partner_multi_relation_tabs/models/__init__.py

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2017 Therp BV <https://therp.nl>
# 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

158
partner_multi_relation_tabs/models/res_partner.py

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
# 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 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

55
partner_multi_relation_tabs/models/res_partner_relation_all.py

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2017 Therp BV <http://therp.nl>
# 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

58
partner_multi_relation_tabs/models/res_partner_relation_type.py

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2017 Therp BV <https://therp.nl>
# 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"))

36
partner_multi_relation_tabs/models/res_partner_relation_type_selection.py

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Copyright 2013-2017 Therp BV <http://therp.nl>
# 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"])

102
partner_multi_relation_tabs/models/res_partner_tab.py

@ -0,0 +1,102 @@
# -*- 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 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()

3
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

BIN
partner_multi_relation_tabs/static/description/icon.png

After

Width: 90  |  Height: 90  |  Size: 18 KiB

4
partner_multi_relation_tabs/tests/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2017 Therp BV <https://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_partner_tabs

217
partner_multi_relation_tabs/tests/test_partner_tabs.py

@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2017 Therp BV <https://therp.nl>
# 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)

18
partner_multi_relation_tabs/views/menu.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<act_window
id="action_res_partner_tab"
res_model="res.partner.tab"
view_mode="tree,form"
name="Relation Tabs"
/>
<menuitem
id="menu_res_partner_tab"
parent="sales_team.menu_config_address_book"
action="action_res_partner_tab"
sequence="80"
/>
</odoo>

17
partner_multi_relation_tabs/views/res_partner_relation_all.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="search_res_partner_relation_all" model="ir.ui.view">
<field name="model">res.partner.relation.all</field>
<field
name="inherit_id"
ref="partner_multi_relation.search_res_partner_relation_all"
/>
<field name="arch" type="xml">
<field name="type_selection_id" position="after">
<field name="tab_id"/>
</field>
</field>
</record>
</odoo>

20
partner_multi_relation_tabs/views/res_partner_relation_type.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="form_res_partner_relation_type" model="ir.ui.view">
<field name="model">res.partner.relation.type</field>
<field
name="inherit_id"
ref="partner_multi_relation.form_res_partner_relation_type"
/>
<field name="arch" type="xml">
<field name="partner_category_left" position="after">
<field name="tab_left_id" />
</field>
<field name="partner_category_right" position="after">
<field name="tab_right_id" />
</field>
</field>
</record>
</odoo>

29
partner_multi_relation_tabs/views/res_partner_tab.xml

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="tree_res_partner_tab" model="ir.ui.view">
<field name="model">res.partner.tab</field>
<field type="xml" name="arch">
<tree>
<field name="code" />
<field name="name" />
</tree>
</field>
</record>
<record id="form_res_partner_tab" model="ir.ui.view">
<field name="model">res.partner.tab</field>
<field type="xml" name="arch">
<form>
<group>
<field name="code" />
<field name="name" />
<field name="contact_type" />
<field name="partner_category_id" />
<field name="partner_ids" />
</group>
</form>
</field>
</record>
</odoo>
Loading…
Cancel
Save