Browse Source

Improve UX for base_export_manager.

- Improve user instructions in README.
- Require some required fields.
- Allow to select models from a list.
- Allow to select up to 3 fields from dynamic lists.
- Improve translations.
- More tests.
- Translate column labels.

Some methods have been renamed, so version tag is raised to 8.0.2.0.0.
pull/317/head
Jairo Llopis 9 years ago
parent
commit
8807d02fbe
  1. 40
      base_export_manager/README.rst
  2. 24
      base_export_manager/__init__.py
  3. 27
      base_export_manager/__openerp__.py
  4. 8
      base_export_manager/data/ir_exports_data.xml
  5. 50
      base_export_manager/i18n/base_exports_manager.pot
  6. 41
      base_export_manager/i18n/es.po
  7. 26
      base_export_manager/models/__init__.py
  8. 65
      base_export_manager/models/ir_exports.py
  9. 205
      base_export_manager/models/ir_exports_line.py
  10. 26
      base_export_manager/static/src/js/main.js
  11. 7
      base_export_manager/tests/__init__.py
  12. 28
      base_export_manager/tests/test_ir_exports.py
  13. 6
      base_export_manager/tests/test_ir_exports_line.py
  14. 32
      base_export_manager/views/ir_exports_view.xml

40
base_export_manager/README.rst

@ -7,12 +7,39 @@ Manages model export profiles
=============================
This module allows an admin to manage export profiles (ir.exports) that Odoo
shows nowhere.
stores internally but shows nowhere.
Usage
=====
Manage export profiles at Settings > Technical > User Interface > Export profiles
You can create the export profiles as you are used to:
* Go to any list view.
* Check some records.
* Press *More > Export*.
* Use the wizard to choose the columns to export.
* Press *Save fields list*.
* Give it a name.
* Press *OK*.
To manage export profiles, you need to:
* Go to *Settings > Technical > User Interface > Export Profiles*.
* Create a new one.
* Choose a name.
* Choose a model (table in the database).
* Choose the fields to export.
* If you choose a related field, you can choose also up to 3 levels of
subfields.
* You can drag & drop to reorder the fields.
To use one of those profiles, you need to:
* Go to any list view.
* Check some records.
* Press *More > Export*.
* Choose your saved export from *Saved exports*.
* Press *Export to file*.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
@ -21,10 +48,7 @@ Manage export profiles at Settings > Technical > User Interface > Export profile
Known issues / Roadmap
======================
* Module must enable the functionality for creating export profiles from that view so:
* Field resource must show all models available to export
* Field "Field name" must show fields from that model
* Translated labels are not used in final exported file.
Bug Tracker
===========
@ -43,6 +67,8 @@ Contributors
* Antonio Espinosa <antonioea@antiun.com>
* Javier Iniesta <javieria@antiun.com>
* Rafael Blasco <rafabn@antiun.com>
* Jairo Llopis <yajo.sk8@gmail.com>
Maintainer
----------
@ -57,4 +83,4 @@ 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 http://odoo-community.org.
To contribute to this module, please visit https://odoo-community.org.

24
base_export_manager/__init__.py

@ -1,25 +1,5 @@
# -*- coding: utf-8 -*-
# Python source code encoding : https://www.python.org/dev/peps/pep-0263/
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright :
# (c) 2015 Antiun Ingenieria, SL (Madrid, Spain, http://www.antiun.com)
# Antonio Espinosa <antonioea@antiun.com>
#
# 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/>.
#
##############################################################################
# © 2015 Antiun Ingeniería S.L. - Antonio Espinosa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models

27
base_export_manager/__openerp__.py

@ -1,33 +1,16 @@
# -*- coding: utf-8 -*-
# Python source code encoding : https://www.python.org/dev/peps/pep-0263/
##############################################################################
#
# OpenERP, Odoo Source Management Solution
# Copyright (c) 2015 Antiun Ingeniería S.L. (http://www.antiun.com)
# Antonio Espinosa <antonioea@antiun.com>
#
# 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/>.
#
##############################################################################
# © 2015 Antiun Ingeniería S.L. - Antonio Espinosa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': "Manages model export profiles",
'category': 'Personalization',
'version': '8.0.1.0.0',
'version': '8.0.2.0.0',
'depends': [
'web',
],
'data': [
'data/ir_exports_data.xml',
'views/assets.xml',
'views/ir_exports_view.xml',
],

8
base_export_manager/data/ir_exports_data.xml

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

50
base_export_manager/i18n/base_exports_manager.pot

@ -1,50 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * base_exports_manager
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-10-27 12:38+0000\n"
"PO-Revision-Date: 2015-10-27 12:38+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: base_exports_manager
#: view:ir.exports:base_exports_manager.ir_exports_form_view
msgid "Export Profile"
msgstr ""
#. module: base_exports_manager
#: model:ir.actions.act_window,name:base_exports_manager.ir_exports_action
#: view:ir.exports:base_exports_manager.ir_exports_tree_view
#: model:ir.ui.menu,name:base_exports_manager.ir_exports_menu
msgid "Export Profiles"
msgstr ""
#. module: base_exports_manager
#: code:addons/base_exports_manager/models/ir_exports_line.py:62
#, python-format
msgid "Field '%s' already exists"
msgstr ""
#. module: base_exports_manager
#: code:addons/base_exports_manager/models/ir_exports_line.py:57
#, python-format
msgid "Field '%s' does not exist"
msgstr ""
#. module: base_exports_manager
#: field:ir.exports.line,sequence:0
msgid "Sequence"
msgstr ""
#. module: base_exports_manager
#: field:ir.exports.line,label:0
msgid "Label"
msgstr ""

41
base_export_manager/i18n/es.po

@ -7,15 +7,22 @@ msgid ""
msgstr ""
"Project-Id-Version: server-tools (8.0)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-11-27 14:01+0000\n"
"PO-Revision-Date: 2015-11-12 16:32+0000\n"
"POT-Creation-Date: 2015-12-24 16:36+0100\n"
"PO-Revision-Date: 2015-12-24 16:36+0100\n"
"Last-Translator: <>\n"
"Language-Team: Spanish (http://www.transifex.com/oca/OCA-server-tools-8-0/language/es/)\n"
"Language-Team: Spanish (http://www.transifex.com/oca/OCA-server-tools-8-0/"
"language/es/)\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: es\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.5\n"
#. module: base_export_manager
#: help:ir.exports,model_id:0
msgid "Database model to export."
msgstr "Modelo de la base de datos a exportar."
#. module: base_export_manager
#: view:ir.exports:base_export_manager.ir_exports_form_view
@ -30,18 +37,38 @@ msgid "Export Profiles"
msgstr "Perfiles de exportación"
#. module: base_export_manager
#: code:addons/base_export_manager/models/ir_exports_line.py:61
#: code:addons/base_export_manager/models/ir_exports_line.py:154
#, python-format
msgid "Field '%s' already exists"
msgstr "El campo '%s' ya existe"
#. module: base_export_manager
#: code:addons/base_export_manager/models/ir_exports_line.py:56
#: code:addons/base_export_manager/models/ir_exports_line.py:149
#, python-format
msgid "Field '%s' does not exist"
msgstr "El campo '%s' no existe"
#. module: base_export_manager
#: field:ir.exports.line,field1_id:0
msgid "First field"
msgstr "Primer campo"
#. module: base_export_manager
#: field:ir.exports,model_id:0
msgid "Model"
msgstr "Modelo"
#. module: base_export_manager
#: field:ir.exports.line,field2_id:0
msgid "Second field"
msgstr "Segundo campo"
#. module: base_export_manager
#: field:ir.exports.line,sequence:0
msgid "Sequence"
msgstr "Secuencia"
#. module: base_export_manager
#: field:ir.exports.line,field3_id:0
msgid "Third field"
msgstr "Tercer campo"

26
base_export_manager/models/__init__.py

@ -1,25 +1,5 @@
# -*- coding: utf-8 -*-
# Python source code encoding : https://www.python.org/dev/peps/pep-0263/
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright :
# (c) 2015 Antiun Ingenieria, SL (Madrid, Spain, http://www.antiun.com)
# Antonio Espinosa <antonioea@antiun.com>
#
# 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/>.
#
##############################################################################
# © 2015 Antiun Ingeniería S.L. - Antonio Espinosa
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import ir_exports_line
from . import ir_exports, ir_exports_line

65
base_export_manager/models/ir_exports.py

@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# © 2015 Antiun Ingeniería S.L. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
class IrExports(models.Model):
_inherit = 'ir.exports'
name = fields.Char(required=True)
resource = fields.Char(
required=True,
readonly=True,
help="Model's technical name.")
model_id = fields.Many2one(
"ir.model",
"Model",
required=True,
store=True,
domain=[("osv_memory", "=", False)],
compute="_compute_model_id",
inverse="_inverse_model_id",
help="Database model to export.")
@api.multi
@api.depends("resource")
def _compute_model_id(self):
"""Get the model from the resource."""
for s in self:
s.model_id = self._get_model_id(s.resource)
@api.multi
def _inverse_model_id(self):
"""Get the resource from the model."""
for s in self:
s.resource = s.model_id.model
@api.multi
@api.onchange("resource")
def _onchange_resource(self):
"""Void fields if model is changed in a view."""
for s in self:
s.export_fields = False
@api.model
def _get_model_id(self, resource):
"""Return a model object from its technical name.
:param str resource:
Technical name of the model, like ``ir.model``.
"""
return self.env["ir.model"].search([("model", "=", resource)])
@api.model
def create(self, vals):
"""Add new required value when missing.
This is required because this model is created from a QWeb wizard view
that does not populate ``model_id``, and it is easier to hack here than
in the view.
"""
vals.setdefault("model_id",
self._get_model_id(vals.get("resource")).id)
return super(IrExports, self).create(vals)

205
base_export_manager/models/ir_exports_line.py

@ -1,26 +1,7 @@
# -*- coding: utf-8 -*-
# Python source code encoding : https://www.python.org/dev/peps/pep-0263/
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright :
# (c) 2015 Antiun Ingenieria, SL (Madrid, Spain, http://www.antiun.com)
# Antonio Espinosa <antonioea@antiun.com>
#
# 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/>.
#
##############################################################################
# © 2015 Antiun Ingeniería S.L. - Antonio Espinosa
# © 2015 Antiun Ingeniería S.L. - Jairo Llopis
# 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 _
@ -30,28 +11,120 @@ class IrExportsLine(models.Model):
_inherit = 'ir.exports.line'
_order = 'sequence,id'
name = fields.Char(
required=True,
readonly=True,
store=True,
compute="_compute_name",
inverse="_inverse_name",
help="Field's technical name.")
field1_id = fields.Many2one(
"ir.model.fields",
"First field",
required=True,
domain="[('model_id', '=', model1_id)]")
field2_id = fields.Many2one(
"ir.model.fields",
"Second field",
domain="[('model_id', '=', model2_id)]")
field3_id = fields.Many2one(
"ir.model.fields",
"Third field",
domain="[('model_id', '=', model3_id)]")
model1_id = fields.Many2one(
string="First model",
readonly=True,
related="export_id.model_id")
model2_id = fields.Many2one(
"ir.model",
"Second model",
compute="_compute_model2_id")
model3_id = fields.Many2one(
"ir.model",
"Third model",
compute="_compute_model3_id")
sequence = fields.Integer()
label = fields.Char(string='Label', compute="_get_label")
def _get_label_string(self):
self.ensure_one()
model_name = self.export_id.resource
label = ''
if not self.name:
return False
for field in self.name.split('/'):
model = self.env['ir.model'].search([('model', '=', model_name)])
field_obj = model.field_id.filtered(lambda r: r.name == field)
if not field_obj:
return False
label = label + _(field_obj.field_description) + '/'
model_name = field_obj.relation
return label.rstrip('/') + ' (' + self.name + ')'
label = fields.Char(
compute="_compute_label")
@api.multi
@api.depends("field1_id", "field2_id", "field3_id")
def _compute_name(self):
"""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)))
@api.multi
@api.depends("field1_id")
def _compute_model2_id(self):
"""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)]))
@api.multi
@api.depends("field2_id")
def _compute_model3_id(self):
"""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)]))
@api.multi
@api.depends('name')
def _compute_label(self):
"""Column label in a user-friendly format and language."""
translations = self.env["ir.translation"]
for s in self:
parts = list()
for num in range(1, 4):
field = s.field_n(num)
if not field:
break
# 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)
@api.multi
def _inverse_name(self):
"""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)
@api.one
@api.constrains('name')
@api.constrains("field1_id", "field2_id", "field3_id")
def _check_name(self):
if not self._get_label_string():
if not self.label:
raise exceptions.ValidationError(
_("Field '%s' does not exist") % self.name)
lines = self.search([('export_id', '=', self.export_id.id),
@ -60,11 +133,49 @@ class IrExportsLine(models.Model):
raise exceptions.ValidationError(
_("Field '%s' already exists") % self.name)
@api.one
@api.depends('name')
def _get_label(self):
self.label = self._get_label_string()
@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.model
def _get_field_id(self, model, name):
"""Get a field object from its model and name.
:param int model:
``ir.model`` object that contains the field.
:param str name:
Technical name of the field, like ``child_ids``.
"""
return self.env["ir.model.fields"].search(
[("name", "=", name),
("model_id", "=", model.id)])
@api.multi
def field_n(self, n, only_name=False):
"""Helper to choose the field according to its indentation level.
:param int n:
Number of the indentation level to choose the field, from 1 to 3.
:param bool only_name:
Return only the field name, or return its value.
"""
name = "field%d_id" % n
return name if only_name else self[name]
@api.multi
def model_n(self, n, only_name=False):
"""Helper to choose the model according to its indentation level.
:param int n:
Number of the indentation level to choose the model, from 1 to 3.
@api.onchange('name')
def _onchange_name(self):
self.label = self._get_label_string()
:param bool only_name:
Return only the model name, or return its value.
"""
name = "model%d_id" % n
return name if only_name else self[name]

26
base_export_manager/static/src/js/main.js

@ -1,27 +1,5 @@
/**
* # -*- coding: utf-8 -*-
* ##############################################################################
* #
* # OpenERP, Open Source Management Solution
* # This module copyright :
* # (c) 2014 Antiun Ingenieria, SL (Madrid, Spain, http://www.antiun.com)
* # Antonio Espinosa <antonioea@antiun.com>
* #
* # 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/>.
* #
* ##############################################################################
*/
/* © 2015 Antiun Ingeniería S.L. - Antonio Espinosa
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). */
// Check jQuery available
if (typeof jQuery === 'undefined') { throw new Error('Requires jQuery') }

7
base_export_manager/tests/__init__.py

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# License AGPL-3: Antiun Ingenieria S.L. - Javier Iniesta
# See README.rst file on addon root folder for more details
# © 2015 Antiun Ingeniería S.L. - Javier Iniesta
# © 2015 Antiun Ingeniería S.L. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_ir_exports_line
from . import test_ir_exports, test_ir_exports_line

28
base_export_manager/tests/test_ir_exports.py

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# © 2015 Antiun Ingeniería S.L. - Jairo Llopis
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tests.common import TransactionCase
class TestIrExportsCase(TransactionCase):
def test_create_with_basic_data(self):
"""Emulate creation from original form.
This form is handled entirely client-side, and lacks some required
field values.
"""
# Emulate creation from JsonRpc, without model_id and field#_id
data = {
"name": u"Test éxport",
"resource": "ir.exports",
"export_fields": [
[0, 0, {"name": "export_fields"}],
[0, 0, {"name": "export_fields/create_uid"}],
[0, 0, {"name": "export_fields/create_date"}],
[0, 0, {"name": "export_fields/field1_id"}],
],
}
record = self.env["ir.exports"].create(data)
self.assertEqual(record.model_id.model, data["resource"])

6
base_export_manager/tests/test_ir_exports_line.py

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# License AGPL-3: Antiun Ingenieria S.L. - Javier Iniesta
# See README.rst file on addon root folder for more details
# © 2015 Antiun Ingenieria S.L. - Javier Iniesta
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from openerp.tests.common import TransactionCase
from openerp.exceptions import ValidationError
@ -29,7 +29,7 @@ class TestIrExportsLineCase(TransactionCase):
m_ir_exports_line = self.env['ir.exports.line']
export_line = m_ir_exports_line.create({'name': 'parent_id/name',
'export_id': self.export.id})
self.assertEqual(export_line.label,
self.assertEqual(export_line.with_context(lang="en_US").label,
"Related Company/Name (parent_id/name)")
with self.assertRaises(ValidationError):
m_ir_exports_line.create({'name': '',

32
base_export_manager/views/ir_exports_view.xml

@ -20,6 +20,7 @@
<field name="arch" type="xml">
<tree string="Export Profiles">
<field name="name"/>
<field name="model_id"/>
<field name="resource"/>
</tree>
</field>
@ -32,14 +33,43 @@
<form string="Export Profile">
<group>
<field name="name"/>
<field name="model_id"
options="{'no_create': True}"/>
<field name="resource"/>
</group>
<group>
<field name="export_fields" nolabel="1">
<tree editable="bottom">
<field name="sequence" invisible="True"/>
<field name="model1_id" invisible="True"/>
<field name="model2_id" invisible="True"/>
<field name="model3_id" invisible="True"/>
<field name="label"/>
<field name="name"/>
<field name="sequence" invisible="1"/>
<field
name="field1_id"
options="{
'no_open': True,
'no_create': True,
}"/>
<field
name="field2_id"
attrs="{
'readonly': [('model2_id', '=', False)],
}"
options="{
'no_open': True,
'no_create': True,
}"/>
<field
name="field3_id"
attrs="{
'readonly': [('model3_id', '=', False)],
}"
options="{
'no_open': True,
'no_create': True,
}"/>
</tree>
</field>
</group>

Loading…
Cancel
Save