← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~akretion-team/partner-contact-management/add-partner_relation into lp:partner-contact-management

 

Alexis de Lattre has proposed merging lp:~akretion-team/partner-contact-management/add-partner_relation into lp:partner-contact-management.

Requested reviews:
  Partner and Contact Core Editors (partner-contact-core-editors)

For more details, see:
https://code.launchpad.net/~akretion-team/partner-contact-management/add-partner_relation/+merge/220726

This merge proposal adds a new module partner_relation. Here is the description :

===========
This module adds relations between partners. The type of relation is configurable ; it supports symetric and asymetric relations.

For example, you will be able to define on the form view of partner A that :

* Partner A is a competitor of Partner B (symetric relation : B is a competitor of A),

* Partner A has been recommended by Partner C (asymetric relation : C recommands A),

* Partner A is the editor of Partner D (asymetric relation : D is the integrator of A).

The relations that you define on Partner A towards Partner B will automatically be visible on the form view of Partner B.
============

Technically, it is not easy to implement in OpenERP. This implementation inherit the create() of the relation object to automatically generate a reverse relation (src_partner_id and dest_partner_id are swapped and the type of relation is the reverse if the relation is asymetric).

I have tried other implementations that would avoid to create a double entry for each relation, but all the other implementations were not good enough because I wanted relations to be simple and easy-to-use inside the partner form view.

This module is flake8 compliant, has demo data, ACLs, POT file and even an icon ! :)
-- 
https://code.launchpad.net/~akretion-team/partner-contact-management/add-partner_relation/+merge/220726
Your team Partner and Contact Core Editors is requested to review the proposed merge of lp:~akretion-team/partner-contact-management/add-partner_relation into lp:partner-contact-management.
=== added directory 'partner_relation'
=== added file 'partner_relation/__init__.py'
--- partner_relation/__init__.py	1970-01-01 00:00:00 +0000
+++ partner_relation/__init__.py	2014-05-22 21:44:09 +0000
@@ -0,0 +1,23 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Partner Relation module for OpenERP
+#    Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
+#    @author: Alexis de Lattre <alexis.delattre@xxxxxxxxxxxx>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import partner_relation

=== added file 'partner_relation/__openerp__.py'
--- partner_relation/__openerp__.py	1970-01-01 00:00:00 +0000
+++ partner_relation/__openerp__.py	2014-05-22 21:44:09 +0000
@@ -0,0 +1,57 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Partner Relation module for OpenERP
+#    Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
+#    @author: Alexis de Lattre <alexis.delattre@xxxxxxxxxxxx>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+{
+    'name': 'Partner Relation',
+    'version': '0.1',
+    'category': 'Partner',
+    'license': 'AGPL-3',
+    'summary': 'Adds relations between partners',
+    'description': """
+Partner Relation
+================
+
+This module adds relations between partners. The type of relation is configurable ; it supports symetric and asymetric relations.
+
+For example, you will be able to define on the form view of partner A that :
+
+* Partner A is a competitor of Partner B (symetric relation : B is a competitor of A),
+
+* Partner A has been recommended by Partner C (asymetric relation : C recommands A),
+
+* Partner A is the editor of Partner D (asymetric relation : D is the integrator of A).
+
+The relations that you define on Partner A towards Partner B will automatically be visible on the form view of Partner B.
+    """,
+    'author': 'Barroux Abbey, Akretion',
+    'website': 'http://www.barroux.org',
+    'depends': ['base'],
+    'data': [
+        'partner_relation_view.xml',
+        'security/ir.model.access.csv',
+        ],
+    'demo': [
+        'partner_relation_demo.xml',
+        ],
+    'active': False,
+}

=== added directory 'partner_relation/i18n'
=== added file 'partner_relation/i18n/fr.po'
--- partner_relation/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ partner_relation/i18n/fr.po	2014-05-22 21:44:09 +0000
@@ -0,0 +1,171 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* partner_relation
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-05-22 19:57+0000\n"
+"PO-Revision-Date: 2014-05-22 19:57+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_relation
+#: field:res.partner.relation.type,active:0
+msgid "Active"
+msgstr "Actif"
+
+#. module: partner_relation
+#: view:res.partner:0
+msgid "Contacts"
+msgstr "Contacts"
+
+#. module: partner_relation
+#: field:res.partner.relation,dest_partner_id:0
+msgid "Destination Partner"
+msgstr "Partenaire destination"
+
+#. module: partner_relation
+#: code:addons/partner_relation/partner_relation.py:90
+#: code:addons/partner_relation/partner_relation.py:167
+#, python-format
+msgid "Error:"
+msgstr "Erreur :"
+
+#. module: partner_relation
+#: view:res.partner:0
+msgid "Go to Relation Partner"
+msgstr "Aller au partenaire lié"
+
+#. module: partner_relation
+#: view:res.partner.relation:0
+msgid "Group By..."
+msgstr "Grouper par..."
+
+#. module: partner_relation
+#: help:res.partner.relation.type,reverse_id:0
+msgid "If the relation type is asymetric, select the corresponding reverse relation type. For exemple, 'A recommends B' is an asymetric relation ; it's reverse relation is 'B is recommended by A'. If the relation type is symetric, leave the field empty. For example, 'A is a competitor of B' is a symetric relation because we also have 'B is the competitor of A'."
+msgstr "Si le type de relation est asymetrique, sélectionnez le type de relation inverse correspondant. Par exemple, 'A recommande B' est une relation asymetrique ; sa relation inverse est 'B recommande A'. Si le type de relation est symétrique, laissez le champ vide. Par exemple, 'A est un concurrent de B' est une relation symétrique car on a également 'B est un concurrent de A'."
+
+#. module: partner_relation
+#: code:addons/partner_relation/partner_relation.py:91
+#, python-format
+msgid "It is not possible to modify the reverse of a relation type. You should desactivate or delete this relation type and create a new one."
+msgstr "Il n'est pas possible de modifier l'inverse d'un type de relation. Vous devriez désactiver ou supprimer ce type de relation et en créer un nouveau."
+
+#. module: partner_relation
+#: model:ir.model,name:partner_relation.model_res_partner
+#: view:res.partner:0
+#: view:res.partner.relation:0
+msgid "Partner"
+msgstr "Partenaire"
+
+#. module: partner_relation
+#: model:ir.model,name:partner_relation.model_res_partner_relation_type
+msgid "Partner Relation Type"
+msgstr "Type de relation partenaire"
+
+#. module: partner_relation
+#: model:ir.actions.act_window,name:partner_relation.partner_relation_type_action
+#: model:ir.ui.menu,name:partner_relation.partner_relation_type_menu
+#: view:res.partner.relation.type:0
+msgid "Partner Relation Types"
+msgstr "Types de relation partenaire"
+
+#. module: partner_relation
+#: model:ir.actions.act_window,name:partner_relation.partner_relation_action
+#: model:ir.ui.menu,name:partner_relation.partner_relation_menu
+#: view:res.partner:0
+#: field:res.partner,relation_ids:0
+#: view:res.partner.relation:0
+msgid "Partner Relations"
+msgstr "Relations partenaires"
+
+#. module: partner_relation
+#: field:res.partner.relation.type,name:0
+msgid "Relation Name"
+msgstr "Nom de la relation"
+
+#. module: partner_relation
+#: view:res.partner.relation:0
+#: field:res.partner.relation,relation_type_id:0
+msgid "Relation Type"
+msgstr "Type de relation"
+
+#. module: partner_relation
+#: model:ir.ui.menu,name:partner_relation.partner_relation_config_menu
+#: view:res.partner:0
+msgid "Relations"
+msgstr "Relations"
+
+#. module: partner_relation
+#: field:res.partner.relation.type,reverse_id:0
+msgid "Reverse Relation Type"
+msgstr "Type de relation inverse"
+
+#. module: partner_relation
+#: view:res.partner.relation:0
+msgid "Search Partner Relations"
+msgstr "Recherche dans les relations partenaires"
+
+#. module: partner_relation
+#: field:res.partner.relation,src_partner_id:0
+msgid "Source Partner"
+msgstr "Partenaire source"
+
+#. module: partner_relation
+#: sql_constraint:res.partner.relation:0
+msgid "This relation already exists!"
+msgstr "Cette relation existe déjà !"
+
+#. module: partner_relation
+#: code:addons/partner_relation/partner_relation.py:168
+#, python-format
+msgid "You cannot write the same values on the relation and it's reverse relation."
+msgstr "Vous ne pouvez pas écrire les mêmes valeurs sur une relation et sa relation inverse."
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_competitor_of
+msgid "is a competitor of"
+msgstr "est un concurrent de"
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_customer_of
+msgid "is a customer of"
+msgstr "est un client de"
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_supplier_of
+msgid "is a supplier of"
+msgstr "est un fournisseur de"
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_integrator_of
+msgid "is an integrator of"
+msgstr "est un intégrateur de"
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_recommended_by
+msgid "is recommended by"
+msgstr "est recommandé par"
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_editor_of
+msgid "is the editor of"
+msgstr "est l'éditeur de"
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.recommends
+msgid "recommends"
+msgstr "recommande"
+
+#. module: partner_relation
+#: model:ir.model,name:partner_relation.model_res_partner_relation
+msgid "res.partner.relation"
+msgstr "res.partner.relation"
+

=== added file 'partner_relation/i18n/partner_relation.pot'
--- partner_relation/i18n/partner_relation.pot	1970-01-01 00:00:00 +0000
+++ partner_relation/i18n/partner_relation.pot	2014-05-22 21:44:09 +0000
@@ -0,0 +1,171 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* partner_relation
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-05-22 19:57+0000\n"
+"PO-Revision-Date: 2014-05-22 19:57+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_relation
+#: field:res.partner.relation.type,active:0
+msgid "Active"
+msgstr ""
+
+#. module: partner_relation
+#: view:res.partner:0
+msgid "Contacts"
+msgstr ""
+
+#. module: partner_relation
+#: field:res.partner.relation,dest_partner_id:0
+msgid "Destination Partner"
+msgstr ""
+
+#. module: partner_relation
+#: code:addons/partner_relation/partner_relation.py:90
+#: code:addons/partner_relation/partner_relation.py:167
+#, python-format
+msgid "Error:"
+msgstr ""
+
+#. module: partner_relation
+#: view:res.partner:0
+msgid "Go to Relation Partner"
+msgstr ""
+
+#. module: partner_relation
+#: view:res.partner.relation:0
+msgid "Group By..."
+msgstr ""
+
+#. module: partner_relation
+#: help:res.partner.relation.type,reverse_id:0
+msgid "If the relation type is asymetric, select the corresponding reverse relation type. For exemple, 'A recommends B' is an asymetric relation ; it's reverse relation is 'B is recommended by A'. If the relation type is symetric, leave the field empty. For example, 'A is a competitor of B' is a symetric relation because we also have 'B is the competitor of A'."
+msgstr ""
+
+#. module: partner_relation
+#: code:addons/partner_relation/partner_relation.py:91
+#, python-format
+msgid "It is not possible to modify the reverse of a relation type. You should desactivate or delete this relation type and create a new one."
+msgstr ""
+
+#. module: partner_relation
+#: model:ir.model,name:partner_relation.model_res_partner
+#: view:res.partner:0
+#: view:res.partner.relation:0
+msgid "Partner"
+msgstr ""
+
+#. module: partner_relation
+#: model:ir.model,name:partner_relation.model_res_partner_relation_type
+msgid "Partner Relation Type"
+msgstr ""
+
+#. module: partner_relation
+#: model:ir.actions.act_window,name:partner_relation.partner_relation_type_action
+#: model:ir.ui.menu,name:partner_relation.partner_relation_type_menu
+#: view:res.partner.relation.type:0
+msgid "Partner Relation Types"
+msgstr ""
+
+#. module: partner_relation
+#: model:ir.actions.act_window,name:partner_relation.partner_relation_action
+#: model:ir.ui.menu,name:partner_relation.partner_relation_menu
+#: view:res.partner:0
+#: field:res.partner,relation_ids:0
+#: view:res.partner.relation:0
+msgid "Partner Relations"
+msgstr ""
+
+#. module: partner_relation
+#: field:res.partner.relation.type,name:0
+msgid "Relation Name"
+msgstr ""
+
+#. module: partner_relation
+#: view:res.partner.relation:0
+#: field:res.partner.relation,relation_type_id:0
+msgid "Relation Type"
+msgstr ""
+
+#. module: partner_relation
+#: model:ir.ui.menu,name:partner_relation.partner_relation_config_menu
+#: view:res.partner:0
+msgid "Relations"
+msgstr ""
+
+#. module: partner_relation
+#: field:res.partner.relation.type,reverse_id:0
+msgid "Reverse Relation Type"
+msgstr ""
+
+#. module: partner_relation
+#: view:res.partner.relation:0
+msgid "Search Partner Relations"
+msgstr ""
+
+#. module: partner_relation
+#: field:res.partner.relation,src_partner_id:0
+msgid "Source Partner"
+msgstr ""
+
+#. module: partner_relation
+#: sql_constraint:res.partner.relation:0
+msgid "This relation already exists!"
+msgstr ""
+
+#. module: partner_relation
+#: code:addons/partner_relation/partner_relation.py:168
+#, python-format
+msgid "You cannot write the same values on the relation and it's reverse relation."
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_competitor_of
+msgid "is a competitor of"
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_customer_of
+msgid "is a customer of"
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_supplier_of
+msgid "is a supplier of"
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_integrator_of
+msgid "is an integrator of"
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_recommended_by
+msgid "is recommended by"
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.is_editor_of
+msgid "is the editor of"
+msgstr ""
+
+#. module: partner_relation
+#: model:res.partner.relation.type,name:partner_relation.recommends
+msgid "recommends"
+msgstr ""
+
+#. module: partner_relation
+#: model:ir.model,name:partner_relation.model_res_partner_relation
+msgid "res.partner.relation"
+msgstr ""
+

=== added file 'partner_relation/partner_relation.py'
--- partner_relation/partner_relation.py	1970-01-01 00:00:00 +0000
+++ partner_relation/partner_relation.py	2014-05-22 21:44:09 +0000
@@ -0,0 +1,216 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Partner Relation module for OpenERP
+#    Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
+#    @author: Alexis de Lattre <alexis.delattre@xxxxxxxxxxxx>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import orm, fields
+from openerp.tools.translate import _
+
+
+class res_partner_relation_type(orm.Model):
+    _name = 'res.partner.relation.type'
+    _description = "Partner Relation Type"
+
+    _columns = {
+        'name': fields.char(
+            'Relation Name', size=32, required=True, translate=True),
+        'reverse_id': fields.many2one(
+            'res.partner.relation.type', 'Reverse Relation Type',
+            help="If the relation type is asymetric, select the corresponding "
+            "reverse relation type. For exemple, 'A recommends B' is an "
+            "asymetric relation ; it's reverse relation is 'B is recommended "
+            "by A'. If the relation type is symetric, leave the field empty. "
+            "For example, 'A is a competitor of B' is a symetric relation "
+            "because we also have 'B is the competitor of A'."),
+        'active': fields.boolean('Active'),
+        }
+
+    _defaults = {
+        'active': True,
+        }
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        if default is None:
+            default = {}
+        current = self.browse(cr, uid, id, context=context)
+        default.update({
+            'name': u'%s (copy)' % current.name,
+            'reverse_id': False,
+            })
+        return super(res_partner_relation_type, self).copy(
+            cr, uid, id, default=default, context=context)
+
+    def _get_reverse_relation_type_id(
+            self, cr, uid, relation_type_id, context=None):
+        relation_type = self.browse(
+            cr, uid, relation_type_id, context=context)
+        if relation_type.reverse_id:
+            reverse_relation_type_id = relation_type.reverse_id.id
+        else:
+            reverse_relation_type_id = relation_type_id
+        return reverse_relation_type_id
+
+    def create(self, cr, uid, vals, context=None):
+        if context is None:
+            context = {}
+        new_id = super(res_partner_relation_type, self).create(
+            cr, uid, vals, context=context)
+        if vals.get('reverse_id'):
+            ctx_write = context.copy()
+            ctx_write['allow_write_reverse_id'] = True
+            self.write(
+                cr, uid, vals['reverse_id'],
+                {'reverse_id': new_id}, context=ctx_write)
+        return new_id
+
+    def write(self, cr, uid, ids, vals, context=None):
+        if context is None:
+            context = {}
+        if (
+                'reverse_id' in vals
+                and not context.get('allow_write_reverse_id')):
+            raise orm.except_orm(
+                _('Error:'),
+                _('It is not possible to modify the reverse of a relation '
+                    'type. You should desactivate or delete this relation '
+                    'type and create a new one.'))
+        return super(res_partner_relation_type, self).write(
+            cr, uid, ids, vals, context=context)
+
+
+class res_partner_relation(orm.Model):
+    _name = 'res.partner.relation'
+    _description = 'Partner Relation'
+
+    _columns = {
+        'src_partner_id': fields.many2one(
+            'res.partner', 'Source Partner', required=True),
+        'relation_type_id': fields.many2one(
+            'res.partner.relation.type', 'Relation Type', required=True),
+        'dest_partner_id': fields.many2one(
+            'res.partner', 'Destination Partner', required=True),
+        }
+
+    _sql_constraints = [(
+        'src_dest_partner_relation_uniq',
+        'unique(src_partner_id, dest_partner_id, relation_type_id)',
+        'This relation already exists!'
+        )]
+
+    def create(self, cr, uid, vals, context=None):
+        '''When a user creates a relation, OpenERP creates the reverse
+        relation automatically'''
+        reverse_rel_type_id = self.pool['res.partner.relation.type'].\
+            _get_reverse_relation_type_id(
+                cr, uid, vals['relation_type_id'], context=context)
+        # Create reverse relation
+        super(res_partner_relation, self).create(
+            cr, uid, {
+                'relation_type_id': reverse_rel_type_id,
+                'src_partner_id': vals['dest_partner_id'],
+                'dest_partner_id': vals['src_partner_id'],
+                }, context=context)
+        return super(res_partner_relation, self).create(
+            cr, uid, vals, context=context)
+
+    def _get_reverse_relation_id(self, cr, uid, relation, context=None):
+        reverse_rel_type_id = self.pool['res.partner.relation.type'].\
+            _get_reverse_relation_type_id(
+                cr, uid, relation.relation_type_id.id, context=context)
+        reverse_rel_ids = self.search(
+            cr, uid, [
+                ('src_partner_id', '=', relation.dest_partner_id.id),
+                ('dest_partner_id', '=', relation.src_partner_id.id),
+                ('relation_type_id', '=', reverse_rel_type_id)
+                ], context=context)
+        assert len(reverse_rel_ids) == 1, \
+            'A relation always has one reverse relation'
+        return reverse_rel_ids[0]
+
+    def unlink(self, cr, uid, ids, context=None):
+        '''When a user deletes a relation, OpenERP deletes the reverse
+        relation automatically'''
+        for relation in self.browse(cr, uid, ids, context=None):
+            reverse_rel_id = self._get_reverse_relation_id(
+                cr, uid, relation, context=context)
+            if reverse_rel_id not in ids:
+                ids.append(reverse_rel_id)
+        return super(res_partner_relation, self).unlink(
+            cr, uid, ids, context=context)
+
+    def write(self, cr, uid, ids, vals, context=None):
+        '''When a user writes on a relation, we also have to update
+        the reverse relation'''
+        reverse_relation_ids = []
+        for relation in self.browse(cr, uid, ids, context=None):
+            reverse_rel_id = self._get_reverse_relation_id(
+                cr, uid, relation, context=context)
+            if reverse_rel_id in ids:
+                raise orm.except_orm(
+                    _('Error:'),
+                    _("You cannot write the same values on the relation "
+                        "and it's reverse relation."))
+            assert reverse_rel_id not in reverse_relation_ids, \
+                "Impossible: it's relation has it's own reverse relation."
+            reverse_relation_ids.append(reverse_rel_id)
+        reverse_vals = {}
+        if 'src_partner_id' in vals:
+            reverse_vals['dest_partner_id'] = vals['src_partner_id']
+        if 'dest_partner_id' in vals:
+            reverse_vals['src_partner_id'] = vals['dest_partner_id']
+        if 'relation_type_id' in vals:
+            reverse_vals['relation_type_id'] = \
+                self.pool['res.partner.relation.type'].\
+                _get_reverse_relation_type_id(
+                    cr, uid, vals['relation_type_id'], context=context)
+        super(res_partner_relation, self).write(
+            cr, uid, reverse_relation_ids, reverse_vals, context=context)
+        return super(res_partner_relation, self).write(
+            cr, uid, ids, vals, context=context)
+
+    def go_to_dest_partner(self, cr, uid, ids, context=None):
+        assert len(ids) == 1, 'Only 1 ID'
+        relation = self.browse(cr, uid, ids[0], context=context)
+        action = {
+            'name': self.pool['res.partner']._description,
+            'type': 'ir.actions.act_window',
+            'res_model': 'res.partner',
+            'view_type': 'form',
+            'view_mode': 'form,tree,kanban',
+            'target': 'current',
+            'res_id': relation.dest_partner_id.id,
+            }
+        return action
+
+
+class res_partner(orm.Model):
+    _inherit = 'res.partner'
+
+    _columns = {
+        'relation_ids': fields.one2many(
+            'res.partner.relation', 'src_partner_id', 'Partner Relations'),
+        }
+
+    def copy(self, cr, uid, id, default=None, context=None):
+        if default is None:
+            default = {}
+        default['relation_ids'] = False
+        return super(res_partner, self).copy(
+            cr, uid, id, default=default, context=context)

=== added file 'partner_relation/partner_relation_demo.xml'
--- partner_relation/partner_relation_demo.xml	1970-01-01 00:00:00 +0000
+++ partner_relation/partner_relation_demo.xml	2014-05-22 21:44:09 +0000
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
+    @author: Alexis de Lattre <alexis.delattre@xxxxxxxxxxxx>
+    The licence is in the file __openerp__.py
+-->
+
+<openerp>
+<data noupdate="1">
+
+<!-- RELATION TYPES -->
+<record id="is_editor_of" model="res.partner.relation.type">
+    <field name="name">is the editor of</field>
+</record>
+
+<record id="is_integrator_of" model="res.partner.relation.type">
+    <field name="name">is an integrator of</field>
+    <field name="reverse_id" ref="is_editor_of"/>
+</record>
+
+<record id="is_recommended_by" model="res.partner.relation.type">
+    <field name="name">is recommended by</field>
+</record>
+
+<record id="recommends" model="res.partner.relation.type">
+    <field name="name">recommends</field>
+    <field name="reverse_id" ref="is_recommended_by"/>
+</record>
+
+<record id="is_competitor_of" model="res.partner.relation.type">
+    <field name="name">is a competitor of</field>
+    <!-- This is a symetric relation -->
+</record>
+
+<record id="is_supplier_of" model="res.partner.relation.type">
+    <field name="name">is a supplier of</field>
+</record>
+
+<record id="is_customer_of" model="res.partner.relation.type">
+    <field name="name">is a customer of</field>
+    <field name="reverse_id" ref="is_supplier_of"/>
+</record>
+
+
+<!-- PARTNER RELATIONS -->
+<!-- Elec Import is a customer of China Export -->
+<record id="relation_6_3_customer" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_6"/>
+    <field name="relation_type_id" ref="is_customer_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_3"/>
+</record>
+
+<!-- Delta PC is a customer of Asustek -->
+<record id="relation_4_1_customer" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_4"/>
+    <field name="relation_type_id" ref="is_customer_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_1"/>
+</record>
+
+<!-- Delta PC is a customer of Seagate -->
+<record id="relation_4_19_customer" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_4"/>
+    <field name="relation_type_id" ref="is_customer_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_19"/>
+</record>
+
+<!-- Maxtor is a competitor of Seagate -->
+<record id="relation_20_19_competitor" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_20"/>
+    <field name="relation_type_id" ref="is_competitor_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_19"/>
+</record>
+
+<!-- Medialpole recommends Agrolait -->
+<record id="relation_8_2_recommends" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_8"/>
+    <field name="relation_type_id" ref="recommends"/>
+    <field name="dest_partner_id" ref="base.res_partner_2"/>
+</record>
+
+<!-- Agrolait is a customer of Bank Wealthy -->
+<record id="relation_2_7_customer" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_2"/>
+    <field name="relation_type_id" ref="is_customer_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_7"/>
+</record>
+
+<!-- Vicking Direct is a customer of Bank Wealthy -->
+<record id="relation_22_7_customer" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_22"/>
+    <field name="relation_type_id" ref="is_customer_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_7"/>
+</record>
+
+<!-- Camptocamp is a competitor of Axelor -->
+<record id="relation_12_13_competitor" model="res.partner.relation">
+    <field name="src_partner_id" ref="base.res_partner_12"/>
+    <field name="relation_type_id" ref="is_competitor_of"/>
+    <field name="dest_partner_id" ref="base.res_partner_13"/>
+</record>
+
+
+</data>
+</openerp>

=== added file 'partner_relation/partner_relation_view.xml'
--- partner_relation/partner_relation_view.xml	1970-01-01 00:00:00 +0000
+++ partner_relation/partner_relation_view.xml	2014-05-22 21:44:09 +0000
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2014 Artisanat Monastique de Provence (www.barroux.org)
+    @author: Alexis de Lattre <alexis.delattre@xxxxxxxxxxxx>
+    The licence is in the file __openerp__.py
+-->
+
+<openerp>
+<data>
+
+<!-- Partner Relation Type -->
+<record id="partner_relation_type_form" model="ir.ui.view">
+    <field name="name">partner_relation_type_form</field>
+    <field name="model">res.partner.relation.type</field>
+    <field name="arch" type="xml">
+        <form string="Partner Relation Types" version="7.0">
+            <group name="main">
+                <field name="name"/>
+                <field name="reverse_id"/>
+                <field name="active"/>
+            </group>
+        </form>
+    </field>
+</record>
+
+<record id="partner_relation_type_tree" model="ir.ui.view">
+    <field name="name">partner_relation_type_tree</field>
+    <field name="model">res.partner.relation.type</field>
+    <field name="arch" type="xml">
+        <tree string="Partner Relation Types">
+            <field name="name"/>
+            <field name="reverse_id"/>
+        </tree>
+    </field>
+</record>
+
+<record id="partner_relation_type_action" model="ir.actions.act_window">
+    <field name="name">Partner Relation Types</field>
+    <field name="res_model">res.partner.relation.type</field>
+    <field name="view_mode">tree,form</field>
+</record>
+
+<menuitem id="partner_relation_config_menu" name="Relations"
+    parent="base.menu_config_address_book" sequence="90"/>
+
+<menuitem id="partner_relation_type_menu" action="partner_relation_type_action"
+    parent="partner_relation_config_menu" sequence="20"/>
+
+
+<!-- Partner Relation -->
+<record id="partner_relation_tree" model="ir.ui.view">
+    <field name="name">partner_relation_tree</field>
+    <field name="model">res.partner.relation</field>
+    <field name="arch" type="xml">
+        <tree string="Partner Relations" editable="bottom">
+            <field name="src_partner_id"/>
+            <field name="relation_type_id" widget="selection"/>
+            <field name="dest_partner_id"/>
+        </tree>
+    </field>
+</record>
+
+<record id="partner_relation_search" model="ir.ui.view">
+    <field name="name">partner_relation_search</field>
+    <field name="model">res.partner.relation</field>
+    <field name="arch" type="xml">
+        <search string="Search Partner Relations">
+            <field name="src_partner_id" string="Partner"
+                filter_domain="['|', ('src_partner_id', 'ilike', self), ('dest_partner_id', 'ilike', self)]"/>
+             <group string="Group By..." name="groupby">
+                 <filter name="relation_type_groupby" string="Relation Type"
+                    context="{'group_by': 'relation_type_id'}"/>
+            </group>
+        </search>
+    </field>
+</record>
+
+<record id="partner_relation_action" model="ir.actions.act_window">
+    <field name="name">Partner Relations</field>
+    <field name="res_model">res.partner.relation</field>
+    <field name="view_mode">tree</field>
+</record>
+
+<menuitem id="partner_relation_menu" action="partner_relation_action"
+    parent="partner_relation_config_menu" sequence="10"/>
+
+
+<!-- Partner -->
+<record id="view_partner_form" model="ir.ui.view">
+    <field name="name">add.relations.on.res.partner.form</field>
+    <field name="model">res.partner</field>
+    <field name="inherit_id" ref="base.view_partner_form"/>
+    <field name="arch"  type="xml">
+        <page string="Contacts" position="after">
+            <page name="relations" string="Relations">
+                <group string="Partner Relations" name="relations">
+                    <field name="relation_ids" nolabel="1">
+                    <!-- I can't call the tree view of
+                    res.partner.relation because 'src_partner_id' is a
+                    required field and it blocks... and I really want
+                    this field to be required for data integrity reasons
+                    -->
+                        <tree editable="bottom">
+                            <field name="relation_type_id" widget="selection"/>
+                            <field name="dest_partner_id" string="Partner"/>
+                            <button name="go_to_dest_partner" type="object"
+                                icon="terp-gtk-jump-to-ltr"
+                                string="Go to Relation Partner"/>
+                        </tree>
+                    </field>
+                </group>
+            </page>
+        </page>
+    </field>
+</record>
+
+</data>
+</openerp>

=== added directory 'partner_relation/security'
=== added file 'partner_relation/security/ir.model.access.csv'
--- partner_relation/security/ir.model.access.csv	1970-01-01 00:00:00 +0000
+++ partner_relation/security/ir.model.access.csv	2014-05-22 21:44:09 +0000
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_partner_relation_type_full,Full access on res.partner.relation.type to Settings grp,model_res_partner_relation_type,base.group_system,1,1,1,1
+access_partner_relation_type_read,Read access on res.partner.relation.type to everybody,model_res_partner_relation_type,,1,0,0,0
+access_partner_relation_full,Full access on res.partner.relation to Contact Manager grp,model_res_partner_relation,base.group_partner_manager,1,1,1,1
+access_partner_relation_read,Read access on res.partner.relation to Employees grp,model_res_partner_relation,base.group_user,1,0,0,0

=== added directory 'partner_relation/static'
=== added directory 'partner_relation/static/src'
=== added directory 'partner_relation/static/src/img'
=== added file 'partner_relation/static/src/img/icon.png'
Binary files partner_relation/static/src/img/icon.png	1970-01-01 00:00:00 +0000 and partner_relation/static/src/img/icon.png	2014-05-22 21:44:09 +0000 differ

Follow ups