changeset 6abb3942043f in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=6abb3942043f
description:
Add header parameter on export data
issue9301
review439151003
diffstat:
CHANGELOG | 1 +
doc/ref/models.rst | 5 ++-
trytond/ir/export.py | 7 ++++++
trytond/ir/message.xml | 6 +++++
trytond/ir/routes.py | 6 +---
trytond/ir/view/export_form.xml | 4 ++-
trytond/model/modelstorage.py | 41 ++++++++++++++++++++++++++++------
trytond/model/modelview.py | 2 +-
trytond/tests/test_exportdata.py | 46 ++++++++++++++++++++++++++++++++++++++++
trytond/tests/test_routes.py | 13 ++++++-----
10 files changed, 109 insertions(+), 22 deletions(-)
diffs (293 lines):
diff -r f2213e8ce5b2 -r 6abb3942043f CHANGELOG
--- a/CHANGELOG Tue Jun 21 10:57:08 2022 +0200
+++ b/CHANGELOG Fri Jul 01 12:45:38 2022 +0200
@@ -1,3 +1,4 @@
+* Add header parameter on export data
* Enforce certificate validation for SMTP connection (issue11564)
* Use singleton for TableHandler
diff -r f2213e8ce5b2 -r 6abb3942043f doc/ref/models.rst
--- a/doc/ref/models.rst Tue Jun 21 10:57:08 2022 +0200
+++ b/doc/ref/models.rst Fri Jul 01 12:45:38 2022 +0200
@@ -413,18 +413,19 @@
Return a list of record instance for the ``ids``.
-.. classmethod:: ModelStorage.export_data(records, fields_names)
+.. classmethod:: ModelStorage.export_data(records, fields_names[, header])
Return a list of list of values for each ``records``.
The list of values follows ``fields_names``.
+ The result includes the description of the fields if ``header`` is set.
Relational fields are defined with ``/`` at any depth.
Descriptor on fields are available by appending ``.`` and the name of the
method on the field that returns the descriptor.
-.. classmethod:: ModelStorage.export_data_domain(domain, fields_names[,
offset[, limit[, order]]])
+.. classmethod:: ModelStorage.export_data_domain(domain, fields_names[,
offset[, limit[, order[, header]]]])
Call :meth:`search` and :meth:`export_data` together.
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/ir/export.py
--- a/trytond/ir/export.py Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/ir/export.py Fri Jul 01 12:45:38 2022 +0200
@@ -28,6 +28,9 @@
__name__ = "ir.export"
name = fields.Char('Name')
resource = fields.Char('Resource')
+ header = fields.Boolean(
+ "Header",
+ help="Check to include field names on the export.")
export_fields = fields.One2Many('ir.export.line', 'export',
'Fields')
@@ -38,6 +41,10 @@
update=RPC(instantiate=0, readonly=False))
@classmethod
+ def default_header(cls):
+ return False
+
+ @classmethod
def update(cls, exports, fields):
pool = Pool()
Line = pool.get('ir.export.line')
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/ir/message.xml
--- a/trytond/ir/message.xml Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/ir/message.xml Fri Jul 01 12:45:38 2022 +0200
@@ -390,5 +390,11 @@
<record model="ir.message" id="msg_view_search_invalid_domain">
<field name="text">Invalid domain or search criteria "%(domain)s"
for search "%(search)s".</field>
</record>
+ <record model="ir.message" id="msg_field_string">
+ <field name="text">%(field)s (string)</field>
+ </record>
+ <record model="ir.message" id="msg_field_model_name">
+ <field name="text">%(field)s (model name)</field>
+ </record>
</data>
</tryton>
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/ir/routes.py
--- a/trytond/ir/routes.py Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/ir/routes.py Fri Jul 01 12:45:38 2022 +0200
@@ -260,17 +260,15 @@
try:
if domain and isinstance(domain[0], (int, float)):
- rows = Model.export_data(domain, fields_names)
+ rows = Model.export_data(domain, fields_names, header)
else:
rows = Model.export_data_domain(
domain, fields_names,
- limit=limit, offset=offset, order=order)
+ limit=limit, offset=offset, order=order, header=header)
except (ValueError, KeyError):
abort(HTTPStatus.BAD_REQUEST)
data = io.StringIO(newline='')
writer = csv.writer(data, delimiter=delimiter, quotechar=quotechar)
- if header:
- writer.writerow(fields_names)
for row in rows:
writer.writerow(format_(row))
data = data.getvalue().encode(encoding)
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/ir/view/export_form.xml
--- a/trytond/ir/view/export_form.xml Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/ir/view/export_form.xml Fri Jul 01 12:45:38 2022 +0200
@@ -3,8 +3,10 @@
this repository contains the full copyright notices and license terms. -->
<form>
<label name="name"/>
- <field name="name"/>
+ <field name="name" colspan="3"/>
<label name="resource"/>
<field name="resource"/>
+ <label name="header"/>
+ <field name="header"/>
<field name="export_fields" colspan="4"/>
</form>
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/model/modelstorage.py
--- a/trytond/model/modelstorage.py Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/model/modelstorage.py Fri Jul 01 12:45:38 2022 +0200
@@ -738,23 +738,48 @@
return [data] + lines
@classmethod
- def export_data(cls, records, fields_names):
- '''
- Return list of list of values for each record.
- The list of values follows fields_names.
- Relational fields are defined with '/' at any depth.
- '''
+ def _convert_field_names(cls, fields_names):
+ pool = Pool()
+ ModelField = pool.get('ir.model.field')
+ result = []
+ for names in fields_names:
+ descriptions = []
+ class_ = cls
+ for i, name in enumerate(names):
+ translated = name.endswith('.translated')
+ if translated:
+ name = name[:-len('.translated')]
+ field = class_._fields[name]
+ field_name = ModelField.get_name(class_.__name__, name)
+ if translated:
+ if isinstance(field, fields.Selection):
+ field_name = gettext(
+ 'ir.msg_field_string', field=field_name)
+ elif isinstance(field, fields.Reference):
+ field_name = gettext(
+ 'ir.msg_field_model_name', field=field_name)
+ descriptions.append(field_name)
+ if hasattr(field, 'get_target'):
+ class_ = field.get_target()
+ result.append('/'.join(descriptions))
+ return result
+
+ @classmethod
+ def export_data(cls, records, fields_names, header=False):
fields_names = [x.split('/') for x in fields_names]
data = []
+ if header:
+ data.append(cls._convert_field_names(fields_names))
for record in records:
data += cls.__export_row(record, fields_names)
return data
@classmethod
def export_data_domain(
- cls, domain, fields_names, offset=0, limit=None, order=None):
+ cls, domain, fields_names, offset=0, limit=None, order=None,
+ header=False):
records = cls.search(domain, limit=limit, offset=offset, order=order)
- return cls.export_data(records, fields_names)
+ return cls.export_data(records, fields_names, header=header)
@classmethod
def import_data(cls, fields_names, data):
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/model/modelview.py
--- a/trytond/model/modelview.py Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/model/modelview.py Fri Jul 01 12:45:38 2022 +0200
@@ -299,7 +299,7 @@
relates = Action.get_keyword('form_relate', (cls.__name__, -1))
exports = Export.search_read(
[('resource', '=', cls.__name__)],
- fields_names=['name', 'export_fields.name'])
+ fields_names=['name', 'header', 'export_fields.name'])
emails = Email.search_read(
[('model.model', '=', cls.__name__)],
fields_names=['name'])
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/tests/test_exportdata.py
--- a/trytond/tests/test_exportdata.py Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/tests/test_exportdata.py Fri Jul 01 12:45:38 2022 +0200
@@ -404,3 +404,49 @@
ExportData.export_data_domain(
[('boolean', '=', True)], ['boolean']),
[[True]])
+
+ @with_transaction()
+ def test_header(self):
+ "Test export data with header"
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ export1, = ExportData.create([{
+ 'char': "Test",
+ 'integer': 2,
+ }])
+ self.assertEqual(
+ ExportData.export_data([export1], ['char', 'integer'], True),
+ [["Char", "Integer"], ["Test", 2]])
+
+ @with_transaction()
+ def test_nested_header(self):
+ "Test export data with header and nested fields"
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+
+ fields_names = [
+ 'many2one/name', 'many2one/rec_name', 'selection',
+ 'selection.translated', 'reference.translated',
+ 'reference/rec_name']
+ self.assertEqual(
+ ExportData.export_data([], fields_names, True),
+ [["Many2One/Name", "Many2One/Record Name", "Selection",
+ "Selection (string)", "Reference (model name)",
+ "Reference/Record Name"]])
+
+ @with_transaction()
+ def test_header_domain(self):
+ "Test export data with header and domain"
+ pool = Pool()
+ ExportData = pool.get('test.export_data')
+ ExportData.create([{
+ 'boolean': True,
+ }, {
+ 'boolean': False,
+ }])
+
+ self.assertEqual(
+ ExportData.export_data_domain(
+ [('boolean', '=', True)], ['boolean'], header=True),
+ [["Boolean"], [True]])
diff -r f2213e8ce5b2 -r 6abb3942043f trytond/tests/test_routes.py
--- a/trytond/tests/test_routes.py Tue Jun 21 10:57:08 2022 +0200
+++ b/trytond/tests/test_routes.py Fri Jul 01 12:45:38 2022 +0200
@@ -62,7 +62,7 @@
query_string=[('f', 'name')])
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.data, b'name\r\nAdministrator\r\n')
+ self.assertEqual(response.data, b'Nom\r\nAdministrator\r\n')
def test_data_multiple_fields(self):
"Test GET data with multiple fields"
@@ -74,7 +74,7 @@
self.assertEqual(response.status_code, 200)
self.assertEqual(
- response.data, b'name,login\r\nAdministrator,admin\r\n')
+ response.data, b'Nom,Identifiant\r\nAdministrator,admin\r\n')
def test_data_language(self):
"Test GET data with language"
@@ -89,7 +89,7 @@
])
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.data, 'name\r\nFrançais\r\n'.encode('utf-8'))
+ self.assertEqual(response.data, 'Nom\r\nFrançais\r\n'.encode('utf-8'))
def test_data_size(self):
"Test GET data with size limit"
@@ -143,7 +143,7 @@
self.assertEqual(response.status_code, 200)
self.assertEqual(
- response.data, 'name\r\nFrançais\r\n'.encode('latin1'))
+ response.data, 'Nom\r\nFrançais\r\n'.encode('latin1'))
def test_data_delimiter(self):
"Test GET data with delimiter"
@@ -155,7 +155,7 @@
self.assertEqual(response.status_code, 200)
self.assertEqual(
- response.data, b'name|login\r\nAdministrator|admin\r\n')
+ response.data, b'Nom|Identifiant\r\nAdministrator|admin\r\n')
def test_data_quotechar(self):
"Test GET data with quotechar"
@@ -168,7 +168,8 @@
self.assertEqual(response.status_code, 200)
self.assertEqual(
- response.data, b'*name*n*login*\r\n*Administrator*n*admin*\r\n')
+ response.data,
+ b'Nomn*Identifiant*\r\n*Administrator*n*admin*\r\n')
def test_data_no_header(self):
"Test GET data without header"