diff --git a/setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix b/setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix
new file mode 120000
index 00000000..4d06f547
--- /dev/null
+++ b/setup/web_widget_x2many_2d_matrix/odoo/addons/web_widget_x2many_2d_matrix
@@ -0,0 +1 @@
+../../../../web_widget_x2many_2d_matrix
\ No newline at end of file
diff --git a/setup/web_widget_x2many_2d_matrix/setup.cfg b/setup/web_widget_x2many_2d_matrix/setup.cfg
new file mode 100644
index 00000000..3c6e79cf
--- /dev/null
+++ b/setup/web_widget_x2many_2d_matrix/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal=1
diff --git a/setup/web_widget_x2many_2d_matrix/setup.py b/setup/web_widget_x2many_2d_matrix/setup.py
new file mode 100644
index 00000000..28c57bb6
--- /dev/null
+++ b/setup/web_widget_x2many_2d_matrix/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+ setup_requires=['setuptools-odoo'],
+ odoo_addon=True,
+)
diff --git a/web_widget_x2many_2d_matrix/README.rst b/web_widget_x2many_2d_matrix/README.rst
new file mode 100644
index 00000000..52eb81b1
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/README.rst
@@ -0,0 +1,172 @@
+.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+
+===========================
+2D matrix for x2many fields
+===========================
+
+This module allows to show an x2many field with 3-tuples
+($x_value, $y_value, $value) in a table
+
++-----------+-------------+-------------+
+| | $x_value1 | $x_value2 |
++===========+=============+=============+
+| $y_value1 | $value(1/1) | $value(2/1) |
++-----------+-------------+-------------+
+| $y_value2 | $value(1/2) | $value(2/2) |
++-----------+-------------+-------------+
+
+where `value(n/n)` is editable.
+
+An example use case would be: Select some projects and some employees so that
+a manager can easily fill in the planned_hours for one task per employee. The
+result could look like this:
+
+.. image:: /web_widget_x2many_2d_matrix/static/description/screenshot.png
+ :alt: Screenshot
+
+The beauty of this is that you have an arbitrary amount of columns with this
+widget, trying to get this in standard x2many lists involves some quite ugly
+hacks.
+
+Usage
+=====
+
+Use this widget by saying::
+
+
+
+This assumes that my_field refers to a model with the fields `x`, `y` and
+`value`. If your fields are named differently, pass the correct names as
+attributes::
+
+
+
+
+
+
+
+
+
+
+You can pass the following parameters:
+
+field_x_axis
+ The field that indicates the x value of a point
+field_y_axis
+ The field that indicates the y value of a point
+field_label_x_axis
+ Use another field to display in the table header
+field_label_y_axis
+ Use another field to display in the table header
+field_value
+ Show this field as value
+show_row_totals
+ If field_value is a numeric field, it indicates if you want to calculate
+ row totals. True by default
+show_column_totals
+ If field_value is a numeric field, it indicates if you want to calculate
+ column totals. True by default
+
+.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
+ :alt: Try me on Runbot
+ :target: https://runbot.odoo-community.org/runbot/162/11.0
+
+Example
+=======
+
+You need a data structure already filled with values. Let's assume we want to
+use this widget in a wizard that lets the user fill in planned hours for one
+task per project per user. In this case, we can use ``project.task`` as our
+data model and point to it from our wizard. The crucial part is that we fill
+the field in the default function::
+
+ from odoo import fields, models
+
+ class MyWizard(models.TransientModel):
+ _name = 'my.wizard'
+
+ def _default_task_ids(self):
+ # your list of project should come from the context, some selection
+ # in a previous wizard or wherever else
+ projects = self.env['project.project'].browse([1, 2, 3])
+ # same with users
+ users = self.env['res.users'].browse([1, 2, 3])
+ return [
+ (0, 0, {
+ 'name': 'Sample task name',
+ 'project_id': p.id,
+ 'user_id': u.id,
+ 'planned_hours': 0,
+ 'message_needaction': False,
+ 'date_deadline': fields.Date.today(),
+ })
+ # if the project doesn't have a task for the user, create a new one
+ if not p.task_ids.filtered(lambda x: x.user_id == u) else
+ # otherwise, return the task
+ (4, p.task_ids.filtered(lambda x: x.user_id == u)[0].id)
+ for p in projects
+ for u in users
+ ]
+
+ task_ids = fields.Many2many('project.task', default=_default_task_ids)
+
+Now in our wizard, we can use::
+
+
+
+
+
+
+
+
+
+
+
+Known issues / Roadmap
+======================
+
+* Support extra attributes on each field cell via `field_extra_attrs` param.
+ We could set a cell as not editable, required or readonly for instance.
+ The `readonly` case will also give the ability
+ to click on m2o to open related records.
+
+* Support limit total records in the matrix. Ref: https://github.com/OCA/web/issues/901
+
+
+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 a detailed and welcomed feedback.
+
+Credits
+=======
+
+Contributors
+------------
+
+* Holger Brunn
+* Pedro M. Baeza
+* Artem Kostyuk
+* Simone Orsi
+* Timon Tschanz
+
+
+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/web_widget_x2many_2d_matrix/__init__.py b/web_widget_x2many_2d_matrix/__init__.py
new file mode 100644
index 00000000..919541c6
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/__init__.py
@@ -0,0 +1,19 @@
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# This module copyright (C) 2015 Therp BV .
+#
+# 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 .
+#
+##############################################################################
diff --git a/web_widget_x2many_2d_matrix/__manifest__.py b/web_widget_x2many_2d_matrix/__manifest__.py
new file mode 100644
index 00000000..31fa2d5a
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/__manifest__.py
@@ -0,0 +1,23 @@
+# Copyright 2015 Holger Brunn
+# Copyright 2016 Pedro M. Baeza
+# Copyright 2018 Simone Orsi
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+{
+ "name": "2D matrix for x2many fields",
+ "version": "11.0.1.0.0",
+ "author": "Therp BV, "
+ "Tecnativa, "
+ "Camptocamp, "
+ "Odoo Community Association (OCA)",
+ "website": "https://github.com/OCA/web",
+ "license": "AGPL-3",
+ "category": "Hidden/Dependency",
+ "summary": "Show list fields as a matrix",
+ "depends": [
+ 'web',
+ ],
+ "data": [
+ 'views/assets.xml',
+ ],
+ "installable": True,
+}
diff --git a/web_widget_x2many_2d_matrix/i18n/ar.po b/web_widget_x2many_2d_matrix/i18n/ar.po
new file mode 100644
index 00000000..7a85d2bd
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/ar.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# SaFi J. , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-12-16 07:41+0000\n"
+"PO-Revision-Date: 2015-12-16 17:24+0000\n"
+"Last-Translator: SaFi J. \n"
+"Language-Team: Arabic (http://www.transifex.com/oca/OCA-web-8-0/language/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "المجموع الاجمالي"
diff --git a/web_widget_x2many_2d_matrix/i18n/de.po b/web_widget_x2many_2d_matrix/i18n/de.po
new file mode 100644
index 00000000..337d2b94
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/de.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# Rudolf Schnapka , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-10 07:31+0000\n"
+"PO-Revision-Date: 2016-01-18 20:15+0000\n"
+"Last-Translator: Rudolf Schnapka \n"
+"Language-Team: German (http://www.transifex.com/oca/OCA-web-8-0/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Gesamt"
diff --git a/web_widget_x2many_2d_matrix/i18n/es.po b/web_widget_x2many_2d_matrix/i18n/es.po
new file mode 100644
index 00000000..10ba2f9f
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/es.po
@@ -0,0 +1,26 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-11-23 13:46+0000\n"
+"PO-Revision-Date: 2015-11-07 11:29+0000\n"
+"Last-Translator: Pedro M. Baeza \n"
+"Language-Team: Spanish (http://www.transifex.com/oca/OCA-web-8-0/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Total"
diff --git a/web_widget_x2many_2d_matrix/i18n/fi.po b/web_widget_x2many_2d_matrix/i18n/fi.po
new file mode 100644
index 00000000..df37d34a
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/fi.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# Jarmo Kortetjärvi , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-10 07:31+0000\n"
+"PO-Revision-Date: 2016-02-01 09:54+0000\n"
+"Last-Translator: Jarmo Kortetjärvi \n"
+"Language-Team: Finnish (http://www.transifex.com/oca/OCA-web-8-0/language/fi/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: fi\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Yhteensä"
diff --git a/web_widget_x2many_2d_matrix/i18n/fr.po b/web_widget_x2many_2d_matrix/i18n/fr.po
new file mode 100644
index 00000000..7ed8bc35
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/fr.po
@@ -0,0 +1,26 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-05-06 15:50+0000\n"
+"PO-Revision-Date: 2015-11-07 11:22+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: French (http://www.transifex.com/oca/OCA-web-8-0/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Total"
diff --git a/web_widget_x2many_2d_matrix/i18n/hr.po b/web_widget_x2many_2d_matrix/i18n/hr.po
new file mode 100644
index 00000000..f209e294
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/hr.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# Ana-Maria Olujić , 2016
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-08-25 00:51+0000\n"
+"PO-Revision-Date: 2016-08-19 11:47+0000\n"
+"Last-Translator: Ana-Maria Olujić \n"
+"Language-Team: Croatian (http://www.transifex.com/oca/OCA-web-8-0/language/hr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: hr\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Ukupno"
diff --git a/web_widget_x2many_2d_matrix/i18n/it.po b/web_widget_x2many_2d_matrix/i18n/it.po
new file mode 100644
index 00000000..5b5d0bf3
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/it.po
@@ -0,0 +1,26 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-03-17 07:30+0000\n"
+"PO-Revision-Date: 2015-11-07 11:22+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: Italian (http://www.transifex.com/oca/OCA-web-8-0/language/it/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Totale"
diff --git a/web_widget_x2many_2d_matrix/i18n/lt.po b/web_widget_x2many_2d_matrix/i18n/lt.po
new file mode 100644
index 00000000..57a65fc5
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/lt.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# Viktoras Norkus , 2018
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 11.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-01-25 01:58+0000\n"
+"PO-Revision-Date: 2018-02-15 12:40+0200\n"
+"Last-Translator: Viktoras Norkus , 2018\n"
+"Language-Team: Lithuanian (https://www.transifex.com/oca/teams/23907/lt/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: lt\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Suma"
diff --git a/web_widget_x2many_2d_matrix/i18n/nl_NL.po b/web_widget_x2many_2d_matrix/i18n/nl_NL.po
new file mode 100644
index 00000000..e1fde063
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/nl_NL.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# Peter Hageman , 2017
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 11.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-01-03 03:50+0000\n"
+"PO-Revision-Date: 2018-02-15 12:39+0200\n"
+"Last-Translator: Peter Hageman , 2017\n"
+"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/teams/23907/nl_NL/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: nl_NL\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Totaal"
diff --git a/web_widget_x2many_2d_matrix/i18n/pt_BR.po b/web_widget_x2many_2d_matrix/i18n/pt_BR.po
new file mode 100644
index 00000000..c56e07fa
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/pt_BR.po
@@ -0,0 +1,26 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-03-11 02:18+0000\n"
+"PO-Revision-Date: 2016-03-05 16:20+0000\n"
+"Last-Translator: danimaribeiro \n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/oca/OCA-web-8-0/language/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Total"
diff --git a/web_widget_x2many_2d_matrix/i18n/sl.po b/web_widget_x2many_2d_matrix/i18n/sl.po
new file mode 100644
index 00000000..07ae09c5
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/sl.po
@@ -0,0 +1,26 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-11-23 13:46+0000\n"
+"PO-Revision-Date: 2015-11-08 05:48+0000\n"
+"Last-Translator: Matjaž Mozetič \n"
+"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-web-8-0/language/sl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: sl\n"
+"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Skupaj"
diff --git a/web_widget_x2many_2d_matrix/i18n/tr.po b/web_widget_x2many_2d_matrix/i18n/tr.po
new file mode 100644
index 00000000..635773bd
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/i18n/tr.po
@@ -0,0 +1,27 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * web_widget_x2many_2d_matrix
+#
+# Translators:
+# Ahmet Altınışık , 2015
+msgid ""
+msgstr ""
+"Project-Id-Version: web (8.0)\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-01-08 21:34+0000\n"
+"PO-Revision-Date: 2015-12-30 22:00+0000\n"
+"Last-Translator: Ahmet Altınışık \n"
+"Language-Team: Turkish (http://www.transifex.com/oca/OCA-web-8-0/language/tr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: tr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. module: web_widget_x2many_2d_matrix
+#. openerp-web
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:11
+#: code:addons/web_widget_x2many_2d_matrix/static/src/xml/web_widget_x2many_2d_matrix.xml:28
+#, python-format
+msgid "Total"
+msgstr "Toplam"
diff --git a/web_widget_x2many_2d_matrix/static/description/icon.png b/web_widget_x2many_2d_matrix/static/description/icon.png
new file mode 100644
index 00000000..a501fbf8
Binary files /dev/null and b/web_widget_x2many_2d_matrix/static/description/icon.png differ
diff --git a/web_widget_x2many_2d_matrix/static/description/screenshot.png b/web_widget_x2many_2d_matrix/static/description/screenshot.png
new file mode 100644
index 00000000..922e2961
Binary files /dev/null and b/web_widget_x2many_2d_matrix/static/description/screenshot.png differ
diff --git a/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css
new file mode 100644
index 00000000..907f507d
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/static/src/css/web_widget_x2many_2d_matrix.css
@@ -0,0 +1,3 @@
+.o_field_x2many_2d_matrix .row-total {
+ font-weight: bold;
+}
diff --git a/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js b/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js
new file mode 100644
index 00000000..898ac0d5
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/static/src/js/2d_matrix_renderer.js
@@ -0,0 +1,416 @@
+/* Copyright 2018 Simone Orsi
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
+
+odoo.define('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer', function (require) {
+ "use strict";
+
+ // heavily inspired by Odoo's `ListRenderer`
+ var BasicRenderer = require('web.BasicRenderer');
+ var config = require('web.config');
+ var field_utils = require('web.field_utils');
+ var utils = require('web.utils');
+ var FIELD_CLASSES = {
+ // copied from ListRenderer
+ float: 'o_list_number',
+ integer: 'o_list_number',
+ monetary: 'o_list_number',
+ text: 'o_list_text',
+ };
+
+ var X2Many2dMatrixRenderer = BasicRenderer.extend({
+
+ init: function (parent, state, params) {
+ this._super.apply(this, arguments);
+ this.editable = params.editable;
+ this.columns = params.matrix_data.columns;
+ this.rows = params.matrix_data.rows;
+ this.matrix_data = params.matrix_data;
+ },
+ /**
+ * Main render function for the matrix widget. It is rendered as a table. For now,
+ * this method does not wait for the field widgets to be ready.
+ *
+ * @override
+ * @private
+ * returns {Deferred} this deferred is resolved immediately
+ */
+ _renderView: function () {
+ var self = this;
+
+ this.$el
+ .removeClass('table-responsive')
+ .empty();
+
+ var $table = $('
').addClass('o_list_view table table-condensed table-striped');
+ this.$el
+ .addClass('table-responsive')
+ .append($table);
+
+ this._computeColumnAggregates();
+ this._computeRowAggregates();
+
+ $table
+ .append(this._renderHeader())
+ .append(this._renderBody());
+ if (self.matrix_data.show_column_totals) {
+ $table.append(this._renderFooter());
+ }
+ return this._super();
+ },
+ /**
+ * Render the table body. Looks for the table body and renders the rows in it.
+ * Also it sets the tabindex on every input element.
+ *
+ * @private
+ * return {jQueryElement} The table body element that was just filled.
+ */
+ _renderBody: function () {
+ var $body = $('').append(this._renderRows());
+ _.each($body.find('input'), function (td, i) {
+ $(td).attr('tabindex', i);
+ });
+ return $body;
+ },
+ /**
+ * Render the table head of our matrix. Looks for the first table head
+ * and inserts the header into it.
+ *
+ * @private
+ * @return {jQueryElement} The thead element that was inserted into.
+ */
+ _renderHeader: function () {
+ var $tr = $('
', {class: 'total'}));
+ }
+ return $('').append($tr);
+ },
+ /**
+ * Render a single header cell. Creates a th and adds the description as text.
+ *
+ * @private
+ * @param {jQueryElement} node
+ * @returns {jQueryElement} the created
node.
+ */
+ _renderHeaderCell: function (node) {
+ var name = node.attrs.name;
+ var field = this.state.fields[name];
+ var $th = $('
');
+ if (!field) {
+ return $th;
+ }
+ var description;
+ if (node.attrs.widget) {
+ description = this.state.fieldsInfo.list[name].Widget.prototype.description;
+ }
+ if (description === undefined) {
+ description = node.attrs.string || field.string;
+ }
+ $th.text(description).data('name', name);
+
+ if (field.type === 'float' || field.type === 'integer' || field.type === 'monetary') {
+ $th.addClass('text-right');
+ }
+
+ if (config.debug) {
+ var fieldDescr = {
+ field: field,
+ name: name,
+ string: description || name,
+ record: this.state,
+ attrs: node.attrs,
+ };
+ this._addFieldTooltip(fieldDescr, $th);
+ }
+ return $th;
+ },
+ /**
+ * Proxy call to function rendering single row.
+ *
+ * @private
+ * @returns {String} a string with the generated html.
+ *
+ */
+
+ _renderRows: function () {
+ return _.map(this.rows, this._renderRow.bind(this));
+ },
+ /**
+ * Render a single row with all its columns. Renders all the cells and then wraps them with a
.
+ * If aggregate is set on the row it also will generate the aggregate cell.
+ *
+ * @private
+ * @param {Object} row: The row that will be rendered.
+ * @returns {jQueryElement} the
element that has been rendered.
+ */
+ _renderRow: function (row) {
+ var self = this;
+ var $tr = $('
', {class: 'o_data_row'});
+ $tr = $tr.append(self._renderLabelCell(row.data[0]));
+ var $cells = _.map(this.columns, function (node, index) {
+ var record = row.data[index];
+ // make the widget use our field value for each cell
+ node.attrs.name = self.matrix_data.field_value;
+ return self._renderBodyCell(record, node, index, {mode:''});
+ });
+ $tr = $tr.append($cells);
+ if (row.aggregate) {
+ $tr.append(self._renderAggregateRowCell(row));
+ }
+ return $tr;
+ },
+ /**
+ * Renders the label for a specific row.
+ *
+ * @private
+ * @params {Object} record: Contains the information about the record.
+ * @params {jQueryElement} the cell that was rendered.
+ */
+ _renderLabelCell: function(record) {
+ var $td = $('
');
+ var value = record.data[this.matrix_data.field_y_axis];
+ if (value.type == 'record') {
+ // we have a related record
+ value = value.data.display_name;
+ }
+ // get 1st column filled w/ Y label
+ $td.text(value);
+ return $td;
+ },
+ /**
+ * Create a cell and fill it with the aggregate value.
+ *
+ * @private
+ * @param {Object} row: the row object to aggregate.
+ * @returns {jQueryElement} The rendered cell.
+ */
+ _renderAggregateRowCell: function (row) {
+ var $cell = $('
', {class: 'row-total text-right'});
+ this._apply_aggregate_value($cell, row.aggregate);
+ return $cell;
+ },
+ /**
+ * Render a single body Cell.
+ * Gets the field and renders the widget. We force the edit mode, since
+ * we always want the widget to be editable.
+ *
+ * @private
+ * @param {Object} record: Contains the data for this cell
+ * @param {jQueryElement} node: The HTML of the field.
+ * @param {int} colIndex: The index of the current column.
+ * @param {Object} options: The obtions used for the widget
+ * @returns {jQueryElement} the rendered cell.
+ */
+ _renderBodyCell: function (record, node, colIndex, options) {
+ var tdClassName = 'o_data_cell';
+ if (node.tag === 'button') {
+ tdClassName += ' o_list_button';
+ } else if (node.tag === 'field') {
+ var typeClass = FIELD_CLASSES[this.state.fields[node.attrs.name].type];
+ if (typeClass) {
+ tdClassName += (' ' + typeClass);
+ }
+ if (node.attrs.widget) {
+ tdClassName += (' o_' + node.attrs.widget + '_cell');
+ }
+ }
+ // TODO roadmap: here we should collect possible extra params
+ // the user might want to attach to each single cell.
+ var $td = $('
', {
+ 'class': tdClassName,
+ 'data-form-id': record.id,
+ 'data-id': record.data.id,
+ });
+ // We register modifiers on the
element so that it gets the correct
+ // modifiers classes (for styling)
+ var modifiers = this._registerModifiers(node, record, $td, _.pick(options, 'mode'));
+ // If the invisible modifiers is true, the
element is left empty.
+ // Indeed, if the modifiers was to change the whole cell would be
+ // rerendered anyway.
+ if (modifiers.invisible && !(options && options.renderInvisible)) {
+ return $td;
+ }
+ options.mode = 'edit'; // enforce edit mode
+ var widget = this._renderFieldWidget(node, record, _.pick(options, 'mode'));
+ this._handleAttributes(widget.$el, node);
+ return $td.append(widget.$el);
+ },
+ /**
+ * Wraps the column aggregate with a tfoot element
+ *
+ * @private
+ * @returns {jQueryElement} The footer element with the cells in it.
+ */
+ _renderFooter: function () {
+ var $cells = this._renderAggregateColCells();
+ if ($cells) {
+ return $('
').append($('
').append('
').append($cells));
+ }
+ return;
+ },
+ /**
+ * Render the Aggregate cells for the column.
+ *
+ * @private
+ * @returns {List} the rendered cells
+ */
+ _renderAggregateColCells: function () {
+ var self = this;
+ return _.map(this.columns, function (column, index) {
+ var $cell = $('
', {class: 'col-total text-right'});
+ if (column.aggregate) {
+ self._apply_aggregate_value($cell, column.aggregate);
+ }
+ return $cell;
+ });
+ },
+ /**
+ * Compute the column aggregates.
+ * This function is called everytime the value is changed.
+ *
+ * @private
+ */
+ _computeColumnAggregates: function () {
+ if (!this.matrix_data.show_column_totals) {
+ return;
+ }
+ var self = this,
+ fname = this.matrix_data.field_value,
+ field = this.state.fields[fname];
+ if (!field) { return; }
+ var type = field.type;
+ if (type !== 'integer' && type !== 'float' && type !== 'monetary') {
+ return;
+ }
+ _.each(self.columns, function (column, index) {
+ column.aggregate = {
+ fname: fname,
+ ftype: type,
+ // TODO: translate
+ help: 'Sum',
+ value: 0
+ };
+ _.each(self.rows, function (row) {
+ // var record = _.findWhere(self.state.data, {id: col.data.id});
+ column.aggregate.value += row.data[index].data[fname];
+ });
+ });
+ },
+ /**
+ * Compute the row aggregates.
+ * This function is called everytime the value is changed.
+ *
+ * @private
+ */
+ _computeRowAggregates: function () {
+ if (!this.matrix_data.show_row_totals) {
+ return;
+ }
+ var self = this,
+ fname = this.matrix_data.field_value,
+ field = this.state.fields[fname];
+ if (!field) { return; }
+ var type = field.type;
+ if (type !== 'integer' && type !== 'float' && type !== 'monetary') {
+ return;
+ }
+ _.each(self.rows, function (row) {
+ row.aggregate = {
+ fname: fname,
+ ftype: type,
+ // TODO: translate
+ help: 'Sum',
+ value: 0
+ };
+ _.each(row.data, function (col) {
+ row.aggregate.value += col.data[fname];
+ });
+ });
+ },
+ /**
+ * Takes the given Value, formats it and adds it to the given cell.
+ *
+ * @private
+ * @param {jQueryElement} $cell: The Cell where the aggregate should be added.
+ * @param {Object} aggregate: The object which contains the information about the aggregate value
+ */
+ _apply_aggregate_value: function ($cell, aggregate) {
+ var field = this.state.fields[aggregate.fname],
+ formatter = field_utils.format[field.type];
+ var formattedValue = formatter(aggregate.value, field, {escape: true, });
+ $cell.addClass('total').attr('title', aggregate.help).html(formattedValue);
+ },
+ /**
+ * Check if the change was successful and then update the grid.
+ * This function is required on relational fields.
+ *
+ * @params {Object} state: Contains the current state of the field & all the data
+ * @params {String} id: the id of the updated object.
+ * @params {Array} fields: The fields we have in the view.
+ * @params {Object} ev: The event object.
+ * @returns {Deferred} The deferred object thats gonna be resolved when the change is made.
+ */
+ confirmUpdate: function (state, id, fields, ev) {
+ var self = this;
+ this.state = state;
+ return this.confirmChange(state, id, fields, ev).then(function () {
+ self._refresh(id);
+ });
+ },
+ /**
+ * Refresh our grid.
+ *
+ * @private
+ */
+ _refresh: function (id) {
+ this._updateRow(id);
+ this._refreshColTotals();
+ this._refreshRowTotals();
+ },
+ /**
+ *Update row data in our internal rows.
+ *
+ * @params {String} id: The id of the row that needs to be updated.
+ */
+ _updateRow: function (id) {
+ var self = this,
+ record = _.findWhere(self.state.data, {id: id});
+ _.each(self.rows, function(row) {
+ _.each(row.data, function(col, i) {
+ if (col.id == id) {
+ row.data[i] = record;
+ }
+ });
+ });
+ },
+ /**
+ * Update the row total.
+ */
+ _refreshColTotals: function () {
+ this._computeColumnAggregates();
+ this.$('tfoot').replaceWith(this._renderFooter());
+ },
+ /**
+ * Update the column total.
+ */
+ _refreshRowTotals: function () {
+ var self = this;
+ this._computeRowAggregates();
+ var $rows = self.$el.find('tr.o_data_row');
+ _.each(self.rows, function(row, i) {
+ if (row.aggregate) {
+ $($rows[i]).find('.row-total')
+ .replaceWith(self._renderAggregateRowCell(row));
+ }
+ });
+ },
+ /*
+ x2m fields expect this
+ */
+ getEditableRecordID: function (){ return false;}
+
+ });
+
+ return X2Many2dMatrixRenderer;
+});
diff --git a/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js b/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js
new file mode 100644
index 00000000..4b1a73f9
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/static/src/js/widget_x2many_2d_matrix.js
@@ -0,0 +1,172 @@
+/* Copyright 2015 Holger Brunn
+ * Copyright 2016 Pedro M. Baeza
+ * Copyright 2018 Simone Orsi
+ * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
+
+odoo.define('web_widget_x2many_2d_matrix.widget', function (require) {
+ "use strict";
+
+ var core = require('web.core');
+ // var FieldManagerMixin = require('web.FieldManagerMixin');
+ var field_registry = require('web.field_registry');
+ var relational_fields = require('web.relational_fields');
+ var weContext = require('web_editor.context');
+ // var Helpers = require('web_widget_x2many_2d_matrix.helpers');
+ var AbstractField = require('web.AbstractField');
+ var X2Many2dMatrixRenderer = require('web_widget_x2many_2d_matrix.X2Many2dMatrixRenderer');
+
+ var WidgetX2Many2dMatrix = relational_fields.FieldOne2Many.extend({
+ widget_class: 'o_form_field_x2many_2d_matrix',
+ /**
+ * Initialize the widget & parameters.
+ *
+ * @param {Object} parent: contains the form view.
+ * @param {String} name: the name of the field.
+ * @param {Object} record: Contains the information about the database records.
+ * @param {Object} options: Contains the view options.
+ */
+ init: function (parent, name, record, options) {
+ this._super(parent, name, record, options);
+ this.init_params();
+ },
+
+ /**
+ * Initialize the widget specific parameters.
+ * Sets the axis and the values.
+ */
+ init_params: function () {
+ var node = this.attrs;
+ this.by_x_axis = {};
+ this.by_y_axis = {};
+ this.field_x_axis = node.field_x_axis || this.field_x_axis;
+ this.field_y_axis = node.field_y_axis || this.field_y_axis;
+ this.field_label_x_axis = node.field_label_x_axis || this.field_x_axis;
+ this.field_label_y_axis = node.field_label_y_axis || this.field_y_axis;
+ this.x_axis_clickable = this.parse_boolean(node.x_axis_clickable || '1');
+ this.y_axis_clickable = this.parse_boolean(node.y_axis_clickable || '1');
+ this.field_value = node.field_value || this.field_value;
+ // TODO: is this really needed? Holger?
+ for (var property in node) {
+ if (property.startsWith("field_att_")) {
+ this.fields_att[property.substring(10)] = node[property];
+ }
+ }
+ // and this?
+ this.field_editability = node.field_editability || this.field_editability;
+ this.show_row_totals = this.parse_boolean(node.show_row_totals || '1');
+ this.show_column_totals = this.parse_boolean(node.show_column_totals || '1');
+ this.init_matrix();
+ },
+ /**
+ * Initializes the Value matrix.
+ * Puts the values in the grid. If we have related items we use the display name.
+ */
+ init_matrix: function(){
+ var self = this,
+ records = self.recordData[this.name].data;
+ _.each(records, function(record) {
+ var x = record.data[self.field_x_axis],
+ y = record.data[self.field_y_axis];
+ if (x.type == 'record') {
+ // we have a related record
+ x = x.data.display_name;
+ }
+ if (y.type == 'record') {
+ // we have a related record
+ y = y.data.display_name;
+ }
+ self.by_x_axis[x] = self.by_x_axis[x] || {};
+ self.by_y_axis[y] = self.by_y_axis[y] || {};
+ self.by_x_axis[x][y] = record;
+ self.by_y_axis[y][x] = record;
+ });
+ // init columns
+ self.columns = [];
+ $.each(self.by_x_axis, function(x){
+ self.columns.push(self._make_column(x));
+ });
+ self.rows = [];
+ $.each(self.by_y_axis, function(y){
+ self.rows.push(self._make_row(y));
+ });
+ self.matrix_data = {
+ 'field_value': self.field_value,
+ 'field_x_axis': self.field_x_axis,
+ 'field_y_axis': self.field_y_axis,
+ 'columns': self.columns,
+ 'rows': self.rows,
+ 'show_row_totals': self.show_row_totals,
+ 'show_column_totals': self.show_column_totals
+ };
+
+ },
+ /**
+ * Create scaffold for a column.
+ *
+ * @params {String} x: The string used as a column title
+ */
+ _make_column: function(x){
+ return {
+ // simulate node parsed on xml arch
+ 'tag': 'field',
+ 'attrs': {
+ 'name': this.field_x_axis,
+ 'string': x
+ }
+ };
+ },
+ /**
+ * Create scaffold for a row.
+ *
+ * @params {String} x: The string used as a row title
+ */
+ _make_row: function(y){
+ var self = this;
+ // use object so that we can attach more data if needed
+ var row = {'data': []};
+ $.each(self.by_x_axis, function(x) {
+ row.data.push(self.by_y_axis[y][x]);
+ });
+ return row;
+ },
+ /**
+ *Parse a String containing a Python bool or 1 and convert it to a proper bool.
+ *
+ * @params {String} val: the string to be parsed.
+ * @returns {Boolean} The parsed boolean.
+ */
+ parse_boolean: function(val) {
+ if (val.toLowerCase() === 'true' || val === '1') {
+ return true;
+ }
+ return false;
+ },
+ /**
+ *Create the matrix renderer and add its output to our element
+ *
+ * @returns {Deferred} A deferred object to be completed when it finished rendering.
+ */
+ _render: function () {
+ if (!this.view) {
+ return this._super();
+ }
+ var arch = this.view.arch,
+ viewType = 'list';
+ this.renderer = new X2Many2dMatrixRenderer(this, this.value, {
+ arch: arch,
+ editable: true,
+ viewType: viewType,
+ matrix_data: this.matrix_data
+ });
+ this.$el.addClass('o_field_x2many o_field_x2many_2d_matrix');
+ return this.renderer.appendTo(this.$el);
+ }
+
+ });
+
+ field_registry.add('x2many_2d_matrix', WidgetX2Many2dMatrix);
+
+ return {
+ WidgetX2Many2dMatrix: WidgetX2Many2dMatrix
+ };
+});
diff --git a/web_widget_x2many_2d_matrix/views/assets.xml b/web_widget_x2many_2d_matrix/views/assets.xml
new file mode 100644
index 00000000..ba820435
--- /dev/null
+++ b/web_widget_x2many_2d_matrix/views/assets.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_widget_x2many_2d_matrix_example/README.rst b/web_widget_x2many_2d_matrix_example/README.rst
new file mode 100644
index 00000000..4f8000d9
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/README.rst
@@ -0,0 +1,41 @@
+.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+
+===================================
+2D matrix for x2many fields example
+===================================
+
+Install it and click on the menu item `Demo x2m matrix widget`.
+
+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 a detailed and welcomed feedback.
+
+Credits
+=======
+
+Contributors
+------------
+
+* Simone Orsi
+
+
+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/web_widget_x2many_2d_matrix_example/__init__.py b/web_widget_x2many_2d_matrix_example/__init__.py
new file mode 100644
index 00000000..9b429614
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/web_widget_x2many_2d_matrix_example/__manifest__.py b/web_widget_x2many_2d_matrix_example/__manifest__.py
new file mode 100644
index 00000000..75567396
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/__manifest__.py
@@ -0,0 +1,20 @@
+{
+ 'name': 'web_widget_x2many_2d_matrix example',
+ 'summary': "A small example on how to use `web_widget_x2many_2d_matrix`.",
+ "version": "11.0.1.0.0",
+ "author": "Camptocamp, "
+ "Odoo Community Association (OCA)",
+ "website": "https://github.com/OCA/web",
+ "license": "AGPL-3",
+ "category": "Hidden/Dependency",
+ "depends": [
+ 'web_widget_x2many_2d_matrix',
+ ],
+ "data": [
+ 'security/ir.model.access.csv',
+ 'demo/x2m.demo.csv',
+ 'views/x2m_demo.xml',
+ 'wizard/x2m_matrix.xml',
+ ],
+ "installable": True,
+}
diff --git a/web_widget_x2many_2d_matrix_example/demo/x2m.demo.csv b/web_widget_x2many_2d_matrix_example/demo/x2m.demo.csv
new file mode 100644
index 00000000..9a8b5aa1
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/demo/x2m.demo.csv
@@ -0,0 +1,20 @@
+id,name,line_ids/user_id/id,line_ids/name,line_ids/value
+web_widget_x2many_2d_matrix_example.x2m_demo_5,One,,,
+,,base.user_demo,A,1
+,,base.user_demo,B,2
+,,base.user_demo,C,3
+web_widget_x2many_2d_matrix_example.x2m_demo_3,Two,,,
+,,base.user_demo,E,5
+,,base.user_demo,F,6
+web_widget_x2many_2d_matrix_example.x2m_demo_2,Three,,,
+,,base.user_root,G,8
+,,base.user_demo,H,9
+,,base.user_root,I,10
+web_widget_x2many_2d_matrix_example.x2m_demo_1,Four,,,
+,,base.user_root,L,12
+,,base.user_demo,M,13
+,,base.user_demo,N,14
+,,base.user_demo,O,15
+,,base.user_root,P,16
+web_widget_x2many_2d_matrix_example.x2m_demo_4,Five,,,
+,,base.user_demo,Q,18
diff --git a/web_widget_x2many_2d_matrix_example/models/__init__.py b/web_widget_x2many_2d_matrix_example/models/__init__.py
new file mode 100644
index 00000000..a23d8c46
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/models/__init__.py
@@ -0,0 +1 @@
+from . import x2m_demo
diff --git a/web_widget_x2many_2d_matrix_example/models/x2m_demo.py b/web_widget_x2many_2d_matrix_example/models/x2m_demo.py
new file mode 100644
index 00000000..039f5a75
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/models/x2m_demo.py
@@ -0,0 +1,31 @@
+from odoo import models, api, fields
+
+
+class X2MDemo(models.Model):
+ _name = 'x2m.demo'
+
+ name = fields.Char()
+ line_ids = fields.One2many('x2m.demo.line', 'demo_id')
+
+ @api.multi
+ def open_x2m_matrix(self):
+ wiz = self.env['x2m.matrix.demo.wiz'].create({})
+ return {
+ 'name': 'Try x2many 2D matrix widget',
+ 'type': 'ir.actions.act_window',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'x2m.matrix.demo.wiz',
+ 'target': 'new',
+ 'res_id': wiz.id,
+ 'context': self.env.context,
+ }
+
+
+class X2MDemoLine(models.Model):
+ _name = 'x2m.demo.line'
+
+ name = fields.Char()
+ demo_id = fields.Many2one('x2m.demo')
+ user_id = fields.Many2one('res.users')
+ value = fields.Integer()
diff --git a/web_widget_x2many_2d_matrix_example/security/ir.model.access.csv b/web_widget_x2many_2d_matrix_example/security/ir.model.access.csv
new file mode 100644
index 00000000..ff2b0388
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_x2m_demo_line,access_x2m_demo_line,model_x2m_demo_line,base.group_user,1,0,0,0
+access_x2m_demo_line_admin,access_x2m_demo_line_admin,model_x2m_demo_line,base.group_system,1,1,1,1
+access_x2m_demo,access_x2m_demo,model_x2m_demo,base.group_user,1,0,0,0
+access_x2m_demo_admin,access_x2m_demo_admin,model_x2m_demo,base.group_system,1,1,1,1
diff --git a/web_widget_x2many_2d_matrix_example/views/x2m_demo.xml b/web_widget_x2many_2d_matrix_example/views/x2m_demo.xml
new file mode 100644
index 00000000..8f6805b6
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/views/x2m_demo.xml
@@ -0,0 +1,54 @@
+
+
+
+
+ x2m.demo.form
+ x2m.demo
+
+
+
+
+
+
+ Demo - Tree
+ x2m.demo
+
+
+
+
+
+
+
+
+ Demo
+ x2m.demo
+ form
+ tree,form
+
+
+
+
+
+
diff --git a/web_widget_x2many_2d_matrix_example/wizard/__init__.py b/web_widget_x2many_2d_matrix_example/wizard/__init__.py
new file mode 100644
index 00000000..68ebaf69
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/wizard/__init__.py
@@ -0,0 +1 @@
+from . import demo_wizard
diff --git a/web_widget_x2many_2d_matrix_example/wizard/demo_wizard.py b/web_widget_x2many_2d_matrix_example/wizard/demo_wizard.py
new file mode 100644
index 00000000..267bcd6e
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/wizard/demo_wizard.py
@@ -0,0 +1,27 @@
+from odoo import fields, models
+
+
+class DemoWizard(models.TransientModel):
+ _name = 'x2m.matrix.demo.wiz'
+
+ line_ids = fields.Many2many(
+ 'x2m.demo.line', default=lambda self: self._default_line_ids())
+
+ def _default_line_ids(self):
+ recs = self.env['x2m.demo'].search([])
+ # same with users
+ users = self.env['x2m.demo.line'].search([]).mapped('user_id')
+ return [
+ (0, 0, {
+ 'name': "{}'s task on {}".format(usr.name, rec.name),
+ 'demo_id': rec.id,
+ 'user_id': usr.id,
+ 'value': 0,
+ })
+ # if the project doesn't have a task for the user, create a new one
+ if not rec.line_ids.filtered(lambda x: x.user_id == usr) else
+ # otherwise, return the task
+ (4, rec.line_ids.filtered(lambda x: x.user_id == usr)[0].id)
+ for rec in recs
+ for usr in users
+ ]
diff --git a/web_widget_x2many_2d_matrix_example/wizard/x2m_matrix.xml b/web_widget_x2many_2d_matrix_example/wizard/x2m_matrix.xml
new file mode 100644
index 00000000..db0c02ee
--- /dev/null
+++ b/web_widget_x2many_2d_matrix_example/wizard/x2m_matrix.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ x2m.matrix.demo.wiz
+ x2m.matrix.demo.wiz
+ form
+
+
+
+
+
+