[IMP] sql_export_excel: black, isort, prettier

pull/698/head
hkapatel 2021-06-21 18:18:03 +05:30 committed by Florian da Costa
parent 0937bf7265
commit 3b578f7a4b
4 changed files with 114 additions and 83 deletions

View File

@ -2,23 +2,23 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': 'SQL Export Excel', "name": "SQL Export Excel",
'version': '12.0.1.1.0', "version": "12.0.1.1.0",
'author': 'Akretion,Odoo Community Association (OCA)', "author": "Akretion,Odoo Community Association (OCA)",
'website': 'http://github/oca/server-tools', "website": "https://github.com/OCA/server-tools",
'license': 'AGPL-3', "license": "AGPL-3",
'category': 'Generic Modules/Others', "category": "Generic Modules/Others",
'summary': 'Allow to export a sql query to an excel file.', "summary": "Allow to export a sql query to an excel file.",
'depends': [ "depends": [
'sql_export', "sql_export",
], ],
'external_dependencies': { "external_dependencies": {
'python': [ "python": [
'openpyxl', "openpyxl",
], ],
}, },
'data': [ "data": [
'views/sql_export_view.xml', "views/sql_export_view.xml",
], ],
'installable': True, "installable": True,
} }

View File

@ -1,71 +1,79 @@
# Copyright 2019 Akretion # Copyright 2019 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, exceptions, fields, models, _
from io import BytesIO
import logging
import base64 import base64
import logging
from io import BytesIO
from odoo import _, api, exceptions, fields, models
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
try: try:
import openpyxl import openpyxl
except ImportError: except ImportError:
_logger.debug('Can not import openpyxl') _logger.debug("Can not import openpyxl")
class SqlExport(models.Model): class SqlExport(models.Model):
_inherit = 'sql.export' _inherit = "sql.export"
file_format = fields.Selection( file_format = fields.Selection(selection_add=[("excel", "Excel")])
selection_add=[('excel', 'Excel')])
header = fields.Boolean( header = fields.Boolean(
default=True, default=True, help="Indicate if the header should be exported to the file."
help="Indicate if the header should be exported to the file.") )
attachment_id = fields.Many2one( attachment_id = fields.Many2one(
'ir.attachment', string='Excel Template', "ir.attachment",
string="Excel Template",
help="If you configure an excel file (in xlsx format) here, the " help="If you configure an excel file (in xlsx format) here, the "
"result of the query will be injected in it.\nIt is usefull to " "result of the query will be injected in it.\nIt is usefull to "
"feed data in a excel file pre-configured with calculation") "feed data in a excel file pre-configured with calculation",
)
sheet_position = fields.Integer( sheet_position = fields.Integer(
default=1, default=1,
help="Indicate the sheet's position of the excel template where the " help="Indicate the sheet's position of the excel template where the "
"result of the sql query should be injected.") "result of the sql query should be injected.",
)
row_position = fields.Integer( row_position = fields.Integer(
default=1, default=1,
help="Indicate from which row the result of the query should be " help="Indicate from which row the result of the query should be " "injected.",
"injected.") )
col_position = fields.Integer( col_position = fields.Integer(
string="Column Position", string="Column Position",
default=1, default=1,
help="Indicate from which column the result of the query should be " help="Indicate from which column the result of the query should be "
"injected.") "injected.",
)
@api.constrains('sheet_position') @api.constrains("sheet_position")
def check_sheet_position(self): def check_sheet_position(self):
for export in self: for export in self:
if export.sheet_position < 1: if export.sheet_position < 1:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("The sheet position can't be less than 1.")) _("The sheet position can't be less than 1.")
)
@api.constrains('row_position') @api.constrains("row_position")
def check_row_position(self): def check_row_position(self):
for export in self: for export in self:
if export.row_position < 1: if export.row_position < 1:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("The row position can't be less than 1.")) _("The row position can't be less than 1.")
)
@api.constrains('col_position') @api.constrains("col_position")
def check_column_position(self): def check_column_position(self):
for export in self: for export in self:
if export.col_position < 1: if export.col_position < 1:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("The column position can't be less than 1.")) _("The column position can't be less than 1.")
)
@api.multi @api.multi
def _get_file_extension(self): def _get_file_extension(self):
self.ensure_one() self.ensure_one()
if self.file_format == 'excel': if self.file_format == "excel":
return 'xlsx' return "xlsx"
else: else:
return super()._get_file_extension() return super()._get_file_extension()
@ -73,7 +81,8 @@ class SqlExport(models.Model):
def excel_get_data_from_query(self, variable_dict): def excel_get_data_from_query(self, variable_dict):
self.ensure_one() self.ensure_one()
res = self._execute_sql_request( res = self._execute_sql_request(
params=variable_dict, mode='fetchall', header=self.header) params=variable_dict, mode="fetchall", header=self.header
)
# Case we insert data in an existing excel file. # Case we insert data in an existing excel file.
if self.attachment_id: if self.attachment_id:
datas = self.attachment_id.datas datas = self.attachment_id.datas
@ -86,8 +95,11 @@ class SqlExport(models.Model):
ws = sheets[self.sheet_position - 1] ws = sheets[self.sheet_position - 1]
except IndexError: except IndexError:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("The Excel Template file contains less than %s sheets " _(
"Please, adjust the Sheet Position parameter.")) "The Excel Template file contains less than %s sheets "
"Please, adjust the Sheet Position parameter."
)
)
row_position = self.row_position or 1 row_position = self.row_position or 1
col_position = self.col_position or 1 col_position = self.col_position or 1
# Case of excel file creation # Case of excel file creation

View File

@ -2,24 +2,24 @@
# @author: Florian da Costa # @author: Florian da Costa
# 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 odoo.tests.common import TransactionCase
import base64 import base64
from io import BytesIO
import logging import logging
from io import BytesIO
from odoo.tests.common import TransactionCase
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
try: try:
import openpyxl import openpyxl
except ImportError: except ImportError:
_logger.debug('Can not import openpyxl') _logger.debug("Can not import openpyxl")
class TestExportSqlQueryExcel(TransactionCase): class TestExportSqlQueryExcel(TransactionCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.wizard_obj = self.env['sql.file.wizard'] self.wizard_obj = self.env["sql.file.wizard"]
def get_workbook_from_query(self, wizard): def get_workbook_from_query(self, wizard):
wizard.export_sql() wizard.export_sql()
@ -30,27 +30,29 @@ class TestExportSqlQueryExcel(TransactionCase):
def test_excel_file_generation(self): def test_excel_file_generation(self):
test_query = "SELECT 'testcol1' as firstcol, 2 as second_col" test_query = "SELECT 'testcol1' as firstcol, 2 as second_col"
query_vals = { query_vals = {
'name': 'Test Query Excel', "name": "Test Query Excel",
'query': test_query, "query": test_query,
'file_format': 'excel' "file_format": "excel",
} }
query = self.env['sql.export'].create(query_vals) query = self.env["sql.export"].create(query_vals)
query.button_validate_sql_expression() query.button_validate_sql_expression()
wizard = self.wizard_obj.create({ wizard = self.wizard_obj.create(
'sql_export_id': query.id, {
}) "sql_export_id": query.id,
}
)
workbook = self.get_workbook_from_query(wizard) workbook = self.get_workbook_from_query(wizard)
ws = workbook.active ws = workbook.active
# Check values, header should be here by default # Check values, header should be here by default
self.assertEqual(ws.cell(row=1, column=1).value, 'firstcol') self.assertEqual(ws.cell(row=1, column=1).value, "firstcol")
self.assertEqual(ws.cell(row=2, column=1).value, 'testcol1') self.assertEqual(ws.cell(row=2, column=1).value, "testcol1")
self.assertEqual(ws.cell(row=2, column=2).value, 2) self.assertEqual(ws.cell(row=2, column=2).value, 2)
query.write({'header': False}) query.write({"header": False})
wb2 = self.get_workbook_from_query(wizard) wb2 = self.get_workbook_from_query(wizard)
ws2 = wb2.active ws2 = wb2.active
# Check values, the header should not be present # Check values, the header should not be present
self.assertEqual(ws2.cell(row=1, column=1).value, 'testcol1') self.assertEqual(ws2.cell(row=1, column=1).value, "testcol1")
self.assertEqual(ws2.cell(row=1, column=2).value, 2) self.assertEqual(ws2.cell(row=1, column=2).value, 2)
def test_excel_file_insert(self): def test_excel_file_insert(self):
@ -60,8 +62,8 @@ class TestExportSqlQueryExcel(TransactionCase):
ws = wb.active ws = wb.active
ws.cell(row=1, column=1, value="My Test Value") ws.cell(row=1, column=1, value="My Test Value")
ws2 = wb.create_sheet("data") ws2 = wb.create_sheet("data")
ws2.cell(row=1, column=1, value='Partner Id') ws2.cell(row=1, column=1, value="Partner Id")
ws2.cell(row=1, column=2, value='Partner Name') ws2.cell(row=1, column=2, value="Partner Name")
output = BytesIO() output = BytesIO()
wb.save(output) wb.save(output)
data = output.getvalue() data = output.getvalue()
@ -69,10 +71,10 @@ class TestExportSqlQueryExcel(TransactionCase):
# Create attachment with the created xlsx file which will be used as # Create attachment with the created xlsx file which will be used as
# template in the sql query # template in the sql query
attachmnent_vals = { attachmnent_vals = {
'name': 'template xlsx sql export Res Partner', "name": "template xlsx sql export Res Partner",
'datas': base64.b64encode(data), "datas": base64.b64encode(data),
} }
attachment = self.env['ir.attachment'].create(attachmnent_vals) attachment = self.env["ir.attachment"].create(attachmnent_vals)
# Create the query and configure it to insert the data in the second # Create the query and configure it to insert the data in the second
# sheet of the xlsx template file and start inserting data at the # sheet of the xlsx template file and start inserting data at the
@ -80,19 +82,21 @@ class TestExportSqlQueryExcel(TransactionCase):
# already contains a header) # already contains a header)
test_query = "SELECT id, name FROM res_partner" test_query = "SELECT id, name FROM res_partner"
query_vals = { query_vals = {
'name': 'Test Query Excel', "name": "Test Query Excel",
'query': test_query, "query": test_query,
'file_format': 'excel', "file_format": "excel",
'attachment_id': attachment.id, "attachment_id": attachment.id,
'sheet_position': 2, "sheet_position": 2,
'header': False, "header": False,
'row_position': 2, "row_position": 2,
} }
query = self.env['sql.export'].create(query_vals) query = self.env["sql.export"].create(query_vals)
query.button_validate_sql_expression() query.button_validate_sql_expression()
wizard = self.wizard_obj.create({ wizard = self.wizard_obj.create(
'sql_export_id': query.id, {
}) "sql_export_id": query.id,
}
)
# Check the generated excel file. The first sheet should still contain # Check the generated excel file. The first sheet should still contain
# the same data and the second sheet should have kept the header and # the same data and the second sheet should have kept the header and
@ -101,7 +105,7 @@ class TestExportSqlQueryExcel(TransactionCase):
sheets = wb2.worksheets sheets = wb2.worksheets
ws1 = sheets[0] ws1 = sheets[0]
# Check values, header should be here by default # Check values, header should be here by default
self.assertEqual(ws1.cell(row=1, column=1).value, 'My Test Value') self.assertEqual(ws1.cell(row=1, column=1).value, "My Test Value")
ws2 = sheets[1] ws2 = sheets[1]
self.assertEqual(ws2.cell(row=1, column=1).value, 'Partner Id') self.assertEqual(ws2.cell(row=1, column=1).value, "Partner Id")
self.assertTrue(ws2.cell(row=2, column=1).value) self.assertTrue(ws2.cell(row=2, column=1).value)

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" ?>
<openerp> <openerp>
<data> <data>
@ -8,11 +8,26 @@
<field name="inherit_id" ref="sql_export.sql_export_view_form" /> <field name="inherit_id" ref="sql_export.sql_export_view_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="file_format" position="after"> <field name="file_format" position="after">
<field name="header" attrs="{'invisible': [('file_format', '=', 'csv')]}"/> <field
<field name="attachment_id" attrs="{'invisible': [('file_format', '!=', 'excel')]}"/> name="header"
<field name="sheet_position" attrs="{'invisible': [('attachment_id', '=', False)]}"/> attrs="{'invisible': [('file_format', '=', 'csv')]}"
<field name="row_position" attrs="{'invisible': [('attachment_id', '=', False)]}"/> />
<field name="col_position" attrs="{'invisible': [('attachment_id', '=', False)]}"/> <field
name="attachment_id"
attrs="{'invisible': [('file_format', '!=', 'excel')]}"
/>
<field
name="sheet_position"
attrs="{'invisible': [('attachment_id', '=', False)]}"
/>
<field
name="row_position"
attrs="{'invisible': [('attachment_id', '=', False)]}"
/>
<field
name="col_position"
attrs="{'invisible': [('attachment_id', '=', False)]}"
/>
</field> </field>
</field> </field>
</record> </record>