Browse Source

Merge pull request #870 from Tecnativa/10.0-base_export_manager

[10.0][MIG][base_export_manager] Full migration to version 10
pull/936/merge
Pedro M. Baeza 7 years ago
committed by GitHub
parent
commit
69995cf326
  1. 3
      base_export_manager/README.rst
  2. 1
      base_export_manager/__init__.py
  3. 10
      base_export_manager/__manifest__.py
  4. 6
      base_export_manager/data/ir_exports_data.xml
  5. 20
      base_export_manager/hooks.py
  6. 4
      base_export_manager/models/ir_exports.py
  7. 160
      base_export_manager/models/ir_exports_line.py
  8. 2
      base_export_manager/models/ir_model_access.py
  9. 2
      base_export_manager/models/res_users.py
  10. 86
      base_export_manager/static/src/js/base_export_manager.js
  11. 4
      base_export_manager/static/src/xml/base.xml
  12. 3
      base_export_manager/tests/__init__.py
  13. 4
      base_export_manager/tests/test_ir_exports.py
  14. 57
      base_export_manager/tests/test_ir_exports_line.py
  15. 28
      base_export_manager/views/ir_exports.xml

3
base_export_manager/README.rst

@ -44,7 +44,8 @@ To manage export profiles, you need to:
* Choose a name. * Choose a name.
* Choose a model (table in the database). * Choose a model (table in the database).
* Choose the fields to export. * Choose the fields to export.
* If you choose a related field, you can choose also up to 3 levels of subfields.
* If you choose a related field, you can choose also up to 4 levels of
subfields.
* You can drag & drop to reorder the fields. * You can drag & drop to reorder the fields.
To use one of those profiles, you need to: To use one of those profiles, you need to:

1
base_export_manager/__init__.py

@ -3,3 +3,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models from . import models
from .hooks import post_init_hook

10
base_export_manager/__manifest__.py

@ -5,12 +5,11 @@
{ {
'name': "Manage model export profiles", 'name': "Manage model export profiles",
'category': 'Personalization', 'category': 'Personalization',
'version': '9.0.1.1.0',
'version': '10.0.1.0.0',
'depends': [ 'depends': [
'web', 'web',
], ],
'data': [ 'data': [
'data/ir_exports_data.xml',
'views/assets.xml', 'views/assets.xml',
'views/ir_exports.xml', 'views/ir_exports.xml',
'views/ir_model.xml', 'views/ir_model.xml',
@ -20,13 +19,12 @@
'qweb': [ 'qweb': [
"static/src/xml/base.xml", "static/src/xml/base.xml",
], ],
'author': 'Antiun Ingeniería S.L., '
'Tecnativa, '
'author': 'Tecnativa, '
'LasLabs, ' 'LasLabs, '
'Ursa Information Systems, ' 'Ursa Information Systems, '
'Odoo Community Association (OCA)', 'Odoo Community Association (OCA)',
'website': 'http://www.antiun.com',
'website': 'https://www.tecnativa.com',
'license': 'AGPL-3', 'license': 'AGPL-3',
'installable': False,
'installable': True,
'application': False, 'application': False,
} }

6
base_export_manager/data/ir_exports_data.xml

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<function model="ir.exports.line" name="_install_base_export_manager"/>
</odoo>

20
base_export_manager/hooks.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import api, SUPERUSER_ID
def post_init_hook(cr, registry):
"""Loaded after installing the module.
``ir.exports.line.name`` was before a char field, and now it is a computed
char field with stored values. We have to inverse it to avoid database
inconsistencies.
"""
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
env["ir.exports.line"].search([
("field1_id", "=", False),
("export_id", "!=", False),
("name", "!=", False),
])._inverse_name()

4
base_export_manager/models/ir_exports.py

@ -2,8 +2,8 @@
# Copyright 2015-2016 Jairo Llopis <jairo.llopis@tecnativa.com> # Copyright 2015-2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import _, api, fields, models
from openerp.exceptions import ValidationError
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class IrExports(models.Model): class IrExports(models.Model):

160
base_export_manager/models/ir_exports_line.py

@ -3,8 +3,7 @@
# Copyright 2015-2016 Jairo Llopis <jairo.llopis@tecnativa.com> # Copyright 2015-2016 Jairo Llopis <jairo.llopis@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import models, fields, api, exceptions
from openerp.tools.translate import _
from odoo import _, models, fields, api, exceptions
class IrExportsLine(models.Model): class IrExportsLine(models.Model):
@ -30,6 +29,10 @@ class IrExportsLine(models.Model):
"ir.model.fields", "ir.model.fields",
"Third field", "Third field",
domain="[('model_id', '=', model3_id)]") domain="[('model_id', '=', model3_id)]")
field4_id = fields.Many2one(
"ir.model.fields",
"Fourth field",
domain="[('model_id', '=', model4_id)]")
model1_id = fields.Many2one( model1_id = fields.Many2one(
"ir.model", "ir.model",
"First model", "First model",
@ -44,6 +47,10 @@ class IrExportsLine(models.Model):
"ir.model", "ir.model",
"Third model", "Third model",
compute="_compute_model3_id") compute="_compute_model3_id")
model4_id = fields.Many2one(
"ir.model",
"Fourth model",
compute="_compute_model4_id")
sequence = fields.Integer() sequence = fields.Integer()
label = fields.Char( label = fields.Char(
compute="_compute_label") compute="_compute_label")
@ -54,98 +61,117 @@ class IrExportsLine(models.Model):
return self.env.context.get("default_model1_id", False) return self.env.context.get("default_model1_id", False)
@api.multi @api.multi
@api.depends("field1_id", "field2_id", "field3_id")
@api.depends("field1_id", "field2_id", "field3_id", "field4_id")
def _compute_name(self): def _compute_name(self):
"""Get the name from the selected fields.""" """Get the name from the selected fields."""
for s in self:
s.name = "/".join((s.field_n(num).name
for num in range(1, 4)
if s.field_n(num)))
for one in self:
name = "/".join((one.field_n(num).name for num in range(1, 5)
if one.field_n(num)))
if name != one.name:
one.name = name
@api.multi @api.multi
@api.depends("field1_id") @api.depends("field1_id")
def _compute_model2_id(self): def _compute_model2_id(self):
"""Get the related model for the second field.""" """Get the related model for the second field."""
ir_model = self.env["ir.model"]
for s in self:
s.model2_id = (
s.field1_id.ttype and
"2" in s.field1_id.ttype and
ir_model.search([("model", "=", s.field1_id.relation)]))
IrModel = self.env["ir.model"]
for one in self:
one.model2_id = (
one.field1_id.ttype and
"2" in one.field1_id.ttype and
IrModel.search([("model", "=", one.field1_id.relation)]))
@api.multi @api.multi
@api.depends("field2_id") @api.depends("field2_id")
def _compute_model3_id(self): def _compute_model3_id(self):
"""Get the related model for the third field.""" """Get the related model for the third field."""
ir_model = self.env["ir.model"]
for s in self:
s.model3_id = (
s.field2_id.ttype and
"2" in s.field2_id.ttype and
ir_model.search([("model", "=", s.field2_id.relation)]))
IrModel = self.env["ir.model"]
for one in self:
one.model3_id = (
one.field2_id.ttype and
"2" in one.field2_id.ttype and
IrModel.search([("model", "=", one.field2_id.relation)]))
@api.multi
@api.depends("field3_id")
def _compute_model4_id(self):
"""Get the related model for the third field."""
IrModel = self.env["ir.model"]
for one in self:
one.model4_id = (
one.field3_id.ttype and
"2" in one.field3_id.ttype and
IrModel.search([("model", "=", one.field3_id.relation)]))
@api.multi @api.multi
@api.depends('name') @api.depends('name')
def _compute_label(self): def _compute_label(self):
"""Column label in a user-friendly format and language.""" """Column label in a user-friendly format and language."""
translations = self.env["ir.translation"]
for s in self:
for one in self:
parts = list() parts = list()
for num in range(1, 4):
field = s.field_n(num)
for num in range(1, 5):
field = one.field_n(num)
if not field: if not field:
break break
# Translate label if possible # Translate label if possible
parts.append(
translations.search([
("type", "=", "field"),
("lang", "=", self.env.context.get("lang")),
("name", "=", "%s,%s" % (s.model_n(num).model,
field.name)),
]).value or
field.display_name)
s.label = ("%s (%s)" % ("/".join(parts), s.name)
if parts and s.name else False)
try:
parts.append(
one.env[one.model_n(num).model]._fields[field.name]
.get_description(one.env)["string"])
except KeyError:
# No human-readable string available, so empty this
return
one.label = ("%s (%s)" % ("/".join(parts), one.name)
if parts and one.name else False)
@api.multi @api.multi
def _inverse_name(self): def _inverse_name(self):
"""Get the fields from the name.""" """Get the fields from the name."""
for s in self:
# Field names can have up to only 3 indentation levels
parts = s.name.split("/", 2)
for num in range(1, 4):
try:
# Fail in excessive subfield level
field_name = parts[num - 1]
except IndexError:
# Remove subfield on failure
s[s.field_n(num, True)] = False
else:
model = s.model_n(num)
s[s.field_n(num, True)] = self._get_field_id(
model, field_name)
for one in self:
# Field names can have up to only 4 indentation levels
parts = one.name.split("/")
if len(parts) > 4:
raise exceptions.ValidationError(
_("It's not allowed to have more than 4 levels depth: "
"%s") % one.name)
for num in range(1, 5):
if num > len(parts):
# Empty subfield in this case
one[one.field_n(num, True)] = False
continue
field_name = parts[num - 1]
model = one.model_n(num)
# You could get to failing constraint while populating the
# fields, so we skip the uniqueness check and manually check
# the full constraint after the loop
one.with_context(skip_check=True)[one.field_n(num, True)] = (
one._get_field_id(model, field_name))
one._check_name()
@api.multi @api.multi
@api.constrains("field1_id", "field2_id", "field3_id")
@api.constrains("field1_id", "field2_id", "field3_id", "field4_id")
def _check_name(self): def _check_name(self):
for rec_id in self:
if not rec_id.label:
for one in self:
if not one.label:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("Field '%s' does not exist") % rec_id.name)
lines = self.search([('export_id', '=', rec_id.export_id.id),
('name', '=', rec_id.name)])
if len(lines) > 1:
raise exceptions.ValidationError(
_("Field '%s' already exists") % rec_id.name)
_("Field '%s' does not exist") % one.name)
if not one.env.context.get('skip_check'):
lines = one.search([('export_id', '=', one.export_id.id),
('name', '=', one.name)])
if len(lines) > 1:
raise exceptions.ValidationError(
_("Field '%s' already exists") % one.name)
@api.model
def _install_base_export_manager(self):
"""Populate ``field*_id`` fields."""
self.search([("export_id", "=", False)]).unlink()
self.search([("field1_id", "=", False),
("name", "!=", False)])._inverse_name()
@api.multi
@api.onchange('name')
def _onchange_name(self):
if self.name:
self._inverse_name()
else:
self.field1_id = False
self.field2_id = False
self.field3_id = False
self.field4_id = False
@api.model @api.model
def _get_field_id(self, model, name): def _get_field_id(self, model, name):
@ -157,9 +183,13 @@ class IrExportsLine(models.Model):
:param str name: :param str name:
Technical name of the field, like ``child_ids``. Technical name of the field, like ``child_ids``.
""" """
return self.env["ir.model.fields"].search(
field = self.env["ir.model.fields"].search(
[("name", "=", name), [("name", "=", name),
("model_id", "=", model.id)]) ("model_id", "=", model.id)])
if not field.exists():
raise exceptions.ValidationError(
_("Field '%s' not found in model '%s'") % (name, model.model))
return field
@api.multi @api.multi
def field_n(self, n, only_name=False): def field_n(self, n, only_name=False):

2
base_export_manager/models/ir_model_access.py

@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from openerp import models, fields
from odoo import models, fields
class IrModelAccess(models.Model): class IrModelAccess(models.Model):

2
base_export_manager/models/res_users.py

@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from openerp import api, models
from odoo import api, models
class ResUsers(models.Model): class ResUsers(models.Model):

86
base_export_manager/static/src/js/base_export_manager.js

@ -5,58 +5,13 @@
odoo.define('base_export_manager.base_export_manager', function(require) { odoo.define('base_export_manager.base_export_manager', function(require) {
'use strict'; 'use strict';
var jQuery = require('$');
var DataExport = require('web.DataExport');
var core = require('web.core'); var core = require('web.core');
var Model = require('web.DataModel'); var Model = require('web.DataModel');
var ListView = require('web.ListView'); var ListView = require('web.ListView');
var Sidebar = require('web.Sidebar'); var Sidebar = require('web.Sidebar');
var _t = core._t; var _t = core._t;
var Session = require('web.Session');
var session = require('web.session');
DataExport.include({
do_load_export_field: function(field_list) {
var export_node = this.$el.find("#fields_list");
_(field_list).each(function (field) {
export_node.append(new Option(field.label + ' (' + field.name + ')', field.name));
});
},
add_field: function(field_id, string) {
var field_list = this.$el.find('#fields_list');
if (this.$el.find("#fields_list option[value='" + field_id + "']") &&
!this.$el.find("#fields_list option[value='" + field_id + "']").length)
{
field_list.append(new Option(string + ' (' + field_id + ')', field_id));
}
},
});
Session.include({
get_export_models: function() {
if (!this.uid) {
return $.when().resolve(false);
}
var Users = new Model('res.users');
var export_models = Users.call('fetch_export_models', []);
return export_models;
},
});
ListView.include({ ListView.include({
view_loading: function(fvg) {
this._super(fvg);
this.is_export_manager();
},
is_export_manager: function () {
var self = this;
$.when(self.session.get_export_models()).then(function
(export_models) {
self.export_models=export_models;
});
},
/** /**
* Instantiate and render the sidebar. * Instantiate and render the sidebar.
* Sets this.sidebar * Sets this.sidebar
@ -66,26 +21,27 @@ odoo.define('base_export_manager.base_export_manager', function(require) {
**/ **/
render_sidebar: function($node) { render_sidebar: function($node) {
var self = this; var self = this;
self.render_export_enable = jQuery.inArray( this.model, self.export_models );
if (!this.sidebar && this.options.sidebar) {
this.sidebar = new Sidebar(this, {editable: this.is_action_enabled('edit')});
if (this.fields_view.toolbar) {
this.sidebar.add_toolbar(this.fields_view.toolbar);
}
this.sidebar.add_items('other', _.compact([
self.render_export_enable >= 0 && {label: _t("Export"), callback: this.on_sidebar_export},
this.fields_view.fields.active && {label: _t("Archive"), callback: this.do_archive_selected},
this.fields_view.fields.active && {label: _t("Unarchive"), callback: this.do_unarchive_selected},
this.is_action_enabled('delete') && {label: _t('Delete'), callback: this.do_delete_selected}
]));
$node = $node || this.options.$sidebar;
this.sidebar.appendTo($node);
// Hide the sidebar by default (it will be shown as soon as a record is selected)
this.sidebar.do_hide();
}
this._super($node);
var Users = new Model('res.users');
Users.call('fetch_export_models', []).done(function(export_models){
self.export_models = export_models;
self.render_export_enable = jQuery.inArray( self.model, self.export_models );
if(self.sidebar && self.sidebar.items && self.sidebar.items.other){
var items_data = [];
_.each(self.sidebar.items.other,function(rec){
if(rec.label != _t("Export")){
items_data.push(rec);
}
});
self.sidebar.items.other = items_data;
self.sidebar.add_items('other', _.compact([
self.render_export_enable >= 0 && {
label: _t("Export"),
callback: self.on_sidebar_export
},
]));
}
});
}, },
}); });
}); });

4
base_export_manager/static/src/xml/base.xml

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve"> <templates id="template" xml:space="preserve">
<t t-extend="ExportTreeView-Secondary.children">
<t t-jquery="#tree-column a" t-operation="append">
<t t-extend="Export.TreeItems">
<t t-jquery=".o_tree_column" t-operation="append">
(<t t-esc="field.id"/>) (<t t-esc="field.id"/>)
</t> </t>
</t> </t>

3
base_export_manager/tests/__init__.py

@ -3,4 +3,5 @@
# Copyright 2015 Antiun Ingeniería S.L. - Jairo Llopis # Copyright 2015 Antiun Ingeniería S.L. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_ir_exports, test_ir_exports_line
from . import test_ir_exports
from . import test_ir_exports_line

4
base_export_manager/tests/test_ir_exports.py

@ -2,8 +2,8 @@
# © 2015 Antiun Ingeniería S.L. - Jairo Llopis # © 2015 Antiun Ingeniería S.L. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.exceptions import ValidationError
from openerp.tests.common import TransactionCase
from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
class TestIrExportsCase(TransactionCase): class TestIrExportsCase(TransactionCase):

57
base_export_manager/tests/test_ir_exports_line.py

@ -1,18 +1,26 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2015 Antiun Ingenieria S.L. - Javier Iniesta # Copyright 2015 Antiun Ingenieria S.L. - Javier Iniesta
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tests.common import TransactionCase
from openerp.exceptions import ValidationError
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
class TestIrExportsLineCase(TransactionCase): class TestIrExportsLineCase(TransactionCase):
def setUp(self): def setUp(self):
super(TestIrExportsLineCase, self).setUp() super(TestIrExportsLineCase, self).setUp()
m_ir_exports = self.env['ir.exports'] m_ir_exports = self.env['ir.exports']
self.export = m_ir_exports.create({'name': 'Partner Test', self.export = m_ir_exports.create({'name': 'Partner Test',
'resource': 'res.partner'}) 'resource': 'res.partner'})
self.partner_model = self.env['ir.model'].search(
[('model', '=', 'res.partner')])
self.field_parent_id = self.env['ir.model.fields'].search(
[('name', '=', 'parent_id'),
('model_id', '=', self.partner_model.id)])
self.field_name = self.env['ir.model.fields'].search(
[('name', '=', 'name'),
('model_id', '=', self.partner_model.id)])
def test_check_name(self): def test_check_name(self):
m_ir_exports_line = self.env['ir.exports.line'] m_ir_exports_line = self.env['ir.exports.line']
@ -34,3 +42,46 @@ class TestIrExportsLineCase(TransactionCase):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
m_ir_exports_line.create({'name': '', m_ir_exports_line.create({'name': '',
'export_id': self.export.id}) 'export_id': self.export.id})
def test_model_default_by_context(self):
"""Fields inherit the model_id by context."""
line = self.env["ir.exports.line"].with_context(
default_model1_id=self.export.model_id.id).create({
"name": "name",
"export_id": self.export.id,
})
self.assertEqual(line.model1_id, self.export.model_id)
def test_inverse_name(self):
line = self.env['ir.exports.line'].create({
'export_id': self.export.id,
'name': 'parent_id/parent_id/parent_id/name',
})
self.assertEqual(line.model1_id, self.partner_model)
self.assertEqual(line.model2_id, self.partner_model)
self.assertEqual(line.field1_id, self.field_parent_id)
self.assertEqual(line.field2_id, self.field_parent_id)
self.assertEqual(line.field3_id, self.field_parent_id)
self.assertEqual(line.field4_id, self.field_name)
def test_compute_name(self):
line = self.env['ir.exports.line'].create({
'export_id': self.export.id,
'field1_id': self.field_parent_id.id,
'field2_id': self.field_parent_id.id,
'field3_id': self.field_parent_id.id,
'field4_id': self.field_name.id,
})
self.assertEqual(line.name, 'parent_id/parent_id/parent_id/name')
def test_write_name_same_root(self):
self.env['ir.exports.line'].create({
'export_id': self.export.id,
'name': 'parent_id',
})
line = self.env['ir.exports.line'].create({
'export_id': self.export.id,
'name': 'name',
})
# This should end without errors
line.name = 'parent_id/name'

28
base_export_manager/views/ir_exports.xml

@ -47,33 +47,25 @@
<field name="model1_id" invisible="True"/> <field name="model1_id" invisible="True"/>
<field name="model2_id" invisible="True"/> <field name="model2_id" invisible="True"/>
<field name="model3_id" invisible="True"/> <field name="model3_id" invisible="True"/>
<field name="model4_id" invisible="True"/>
<field name="label"/> <field name="label"/>
<field name="name"/> <field name="name"/>
<field <field
name="field1_id" name="field1_id"
required="True" required="True"
options="{
'no_open': True,
'no_create': True,
}"/>
options="{'no_open': True, 'no_create': True}"/>
<field <field
name="field2_id" name="field2_id"
attrs="{
'readonly': [('model2_id', '=', False)],
}"
options="{
'no_open': True,
'no_create': True,
}"/>
attrs="{'readonly': [('model2_id', '=', False)]}"
options="{'no_open': True, 'no_create': True}"/>
<field <field
name="field3_id" name="field3_id"
attrs="{
'readonly': [('model3_id', '=', False)],
}"
options="{
'no_open': True,
'no_create': True,
}"/>
attrs="{'readonly': [('model3_id', '=', False)]}"
options="{'no_open': True, 'no_create': True}"/>
<field
name="field4_id"
attrs="{'readonly': [('model3_id', '=', False)]}"
options="{'no_open': True, 'no_create': True}"/>
</tree> </tree>
</field> </field>
</group> </group>

Loading…
Cancel
Save