Skip to content

Commit b79d175

Browse files
committed
Merge PR OCA#873 into 16.0
Signed-off-by legalsylvain
2 parents 3e0e4dd + 5811a07 commit b79d175

22 files changed

+798
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../sql_export_delta

setup/sql_export_delta/setup.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import setuptools
2+
3+
setuptools.setup(
4+
setup_requires=['setuptools-odoo'],
5+
odoo_addon=True,
6+
)

sql_export/models/sql_export.py

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ class SqlExport(models.Model):
4141
default="utf-8",
4242
)
4343

44+
keep_generated_file = fields.Boolean(
45+
help="Check this to keep generated export files as attachments"
46+
)
47+
4448
def _compute_use_properties(self):
4549
for rec in self:
4650
rec.use_properties = bool(rec.query_properties_definition)

sql_export/tests/test_sql_query.py

+13
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,16 @@ def test_sql_query_with_params(self):
101101
wizard.export_sql()
102102
export = base64.b64decode(wizard.binary_file)
103103
self.assertTrue(export)
104+
105+
def test_keep_generated_file(self):
106+
"""Test that we keep generated files"""
107+
self.sql_report_demo.keep_generated_file = True
108+
wizard = self.wizard_obj.create(
109+
{
110+
"sql_export_id": self.sql_report_demo.id,
111+
}
112+
)
113+
wizard.export_sql()
114+
attachment = wizard._get_field_attachment()
115+
wizard.sudo().unlink()
116+
self.assertTrue(attachment.exists())

sql_export/views/sql_export_view.xml

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
name="file_format"
4343
attrs="{'readonly': [('state', '!=', 'draft')]}"
4444
/>
45+
<field
46+
name="keep_generated_file"
47+
attrs="{'invisible': [('file_format', '!=', 'csv')], 'required': [('file_format', '=', 'csv')], 'readonly': [('state', '!=', 'draft')]}"
48+
groups="base.group_no_one"
49+
/>
4550
<field
4651
name="copy_options"
4752
attrs="{'invisible': [('file_format', '!=', 'csv')], 'required': [('file_format', '=', 'csv')], 'readonly': [('state', '!=', 'draft')]}"

sql_export/wizard/wizard_file.py

+28
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
44

55
from datetime import datetime
6+
from mimetypes import guess_type
67

78
from odoo import _, fields, models
89
from odoo.exceptions import UserError
@@ -78,6 +79,12 @@ def export_sql(self):
7879
sql_export.id,
7980
),
8081
)
82+
self._get_field_attachment().write(
83+
{
84+
"name": self.file_name,
85+
"mimetype": guess_type(self.file_name)[0],
86+
}
87+
)
8188
action = {
8289
"name": "SQL Export",
8390
"type": "ir.actions.act_url",
@@ -87,3 +94,24 @@ def export_sql(self):
8794
"target": "self",
8895
}
8996
return action
97+
98+
def _get_field_attachment(self):
99+
"""Return the attachment of the binary_file field"""
100+
return self.env["ir.attachment"].search(
101+
[
102+
("res_model", "=", self._name),
103+
("res_id", "in", self.ids),
104+
("res_field", "=", "binary_file"),
105+
],
106+
)
107+
108+
def unlink(self):
109+
for this in self.filtered("sql_export_id.keep_generated_file"):
110+
this._get_field_attachment().write(
111+
{
112+
"res_model": this.sql_export_id._name,
113+
"res_id": this.sql_export_id.id,
114+
"res_field": None,
115+
}
116+
)
117+
return super().unlink()

sql_export_delta/README.rst

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
==========================
2+
SQL Export (delta support)
3+
==========================
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:9494034b2f072b0d68ca3c3736ce6176299e3fede3f85b06ae004461e90f8431
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Alpha
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
18+
:alt: License: AGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github
20+
:target: https://github.com/OCA/reporting-engine/tree/16.0/sql_export_delta
21+
:alt: OCA/reporting-engine
22+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
23+
:target: https://translation.odoo-community.org/projects/reporting-engine-16-0/reporting-engine-16-0-sql_export_delta
24+
:alt: Translate me on Weblate
25+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=16.0
27+
:alt: Try me on Runboat
28+
29+
|badge1| |badge2| |badge3| |badge4| |badge5|
30+
31+
This module extends the functionality of ``sql_export`` to allow you to
32+
only export changes between export runs.
33+
34+
.. IMPORTANT::
35+
This is an alpha version, the data model and design can change at any time without warning.
36+
Only for development or testing purpose, do not use in production.
37+
`More details on development status <https://odoo-community.org/page/development-status>`_
38+
39+
**Table of contents**
40+
41+
.. contents::
42+
:local:
43+
44+
Configuration
45+
=============
46+
47+
To configure this module, you need to:
48+
49+
1. Go to an export in draft mode
50+
2. Check the flag 'Delta'
51+
52+
Bug Tracker
53+
===========
54+
55+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/reporting-engine/issues>`_.
56+
In case of trouble, please check there if your issue has already been reported.
57+
If you spotted it first, help us to smash it by providing a detailed and welcomed
58+
`feedback <https://github.com/OCA/reporting-engine/issues/new?body=module:%20sql_export_delta%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
59+
60+
Do not contact contributors directly about support or help with technical issues.
61+
62+
Credits
63+
=======
64+
65+
Authors
66+
-------
67+
68+
* Hunki Enterprises BV
69+
70+
Contributors
71+
------------
72+
73+
- Holger Brunn <[email protected]>
74+
(https://hunki-enterprises.com)
75+
76+
Maintainers
77+
-----------
78+
79+
This module is maintained by the OCA.
80+
81+
.. image:: https://odoo-community.org/logo.png
82+
:alt: Odoo Community Association
83+
:target: https://odoo-community.org
84+
85+
OCA, or the Odoo Community Association, is a nonprofit organization whose
86+
mission is to support the collaborative development of Odoo features and
87+
promote its widespread use.
88+
89+
.. |maintainer-hbrunn| image:: https://github.com/hbrunn.png?size=40px
90+
:target: https://github.com/hbrunn
91+
:alt: hbrunn
92+
93+
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
94+
95+
|maintainer-hbrunn|
96+
97+
This module is part of the `OCA/reporting-engine <https://github.com/OCA/reporting-engine/tree/16.0/sql_export_delta>`_ project on GitHub.
98+
99+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

sql_export_delta/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from . import models
2+
from . import wizards
3+
from .hooks import uninstall_hook

sql_export_delta/__manifest__.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2024 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0)
3+
4+
{
5+
"name": "SQL Export (delta support)",
6+
"summary": "Support exporting only the changes from last export",
7+
"version": "16.0.1.0.0",
8+
"development_status": "Alpha",
9+
"category": "Extra Tools",
10+
"website": "https://github.com/OCA/reporting-engine",
11+
"author": "Hunki Enterprises BV, Odoo Community Association (OCA)",
12+
"maintainers": ["hbrunn"],
13+
"license": "AGPL-3",
14+
"uninstall_hook": "uninstall_hook",
15+
"external_dependencies": {
16+
"python": [],
17+
"bin": [],
18+
},
19+
"depends": [
20+
"sql_export",
21+
],
22+
"data": [
23+
"views/sql_export.xml",
24+
],
25+
"demo": [],
26+
}

sql_export_delta/hooks.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright 2024 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0)
3+
from odoo import SUPERUSER_ID, api
4+
5+
6+
def uninstall_hook(cr, registry):
7+
env = api.Environment(cr, SUPERUSER_ID, {})
8+
for export in env["sql.export"].search([]):
9+
export._export_delta_cleanup(keep_last=False)

sql_export_delta/models/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import sql_export

sql_export_delta/models/sql_export.py

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2024 Hunki Enterprises BV
2+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl-3.0)
3+
4+
from psycopg2.sql import SQL, Identifier
5+
6+
from odoo import fields, models
7+
8+
9+
class SqlExport(models.Model):
10+
_inherit = "sql.export"
11+
12+
export_delta = fields.Boolean(
13+
string="Delta",
14+
help="With this checked, the full result of the query "
15+
"will be stored as table in the database, but the file generated will "
16+
"only contain rows not existing in the n-1st export",
17+
)
18+
19+
def write(self, vals):
20+
"""Delete previous results when we change the query"""
21+
if "query" in vals:
22+
for this in self:
23+
this._export_delta_cleanup(keep_last=False)
24+
return super().write(vals)
25+
26+
def _execute_sql_request(
27+
self,
28+
params=None,
29+
mode="fetchall",
30+
rollback=True,
31+
view_name=False,
32+
copy_options="CSV HEADER DELIMITER ';'",
33+
header=False,
34+
):
35+
delta_id = self.env.context.get("export_delta_id")
36+
37+
if delta_id:
38+
original_query = self.env.cr.mogrify(self.query, params).decode("utf-8")
39+
result_table = self._export_delta_table_name(delta_id)
40+
table_query = SQL(
41+
"WITH result as ({0}) SELECT * INTO TABLE {1} FROM result"
42+
).format(SQL(original_query), Identifier(result_table))
43+
previous_result_table = self._export_delta_existing_tables()[-1:]
44+
if previous_result_table:
45+
result_query = SQL("SELECT * FROM {0} EXCEPT SELECT * FROM {1}").format(
46+
Identifier(result_table),
47+
Identifier(previous_result_table[0]),
48+
)
49+
else:
50+
result_query = SQL("SELECT * FROM {0}").format(Identifier(result_table))
51+
self.env.cr.execute(table_query)
52+
# inject new query in cache for super to use
53+
self._cache["query"] = result_query
54+
result = super()._execute_sql_request(
55+
params=None,
56+
mode=mode,
57+
rollback=rollback,
58+
view_name=view_name,
59+
copy_options=copy_options,
60+
header=header,
61+
)
62+
self.invalidate_recordset(["query"])
63+
self._export_delta_cleanup(keep_last=True)
64+
else:
65+
result = super()._execute_sql_request(
66+
params=params,
67+
mode=mode,
68+
rollback=rollback,
69+
view_name=view_name,
70+
copy_options=copy_options,
71+
header=header,
72+
)
73+
74+
return result
75+
76+
def _export_delta_table_name(self, identifier):
77+
"""
78+
Return the name of a table to store data for delta export, must end with
79+
{identifier}
80+
"""
81+
return f"sql_export_delta_{self.id}_{identifier}"
82+
83+
def _export_delta_existing_tables(self):
84+
"""Return all table names used for storing data for delta export"""
85+
self.env.cr.execute(
86+
"SELECT table_name FROM information_schema.tables WHERE table_name LIKE %s",
87+
(self._export_delta_table_name("%"),),
88+
)
89+
return sorted(
90+
[name for name, in self.env.cr.fetchall()],
91+
key=lambda name: int(name[len(self._export_delta_table_name("")) :]),
92+
)
93+
94+
def _export_delta_cleanup(self, keep_last=True):
95+
"""Delete tables storing data for delta export"""
96+
table_names = self._export_delta_existing_tables()[: -1 if keep_last else None]
97+
for table_name in table_names:
98+
self.env.cr.execute(SQL("DROP TABLE {0}").format(Identifier(table_name)))

sql_export_delta/readme/CONFIGURE.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
To configure this module, you need to:
2+
3+
1. Go to an export in draft mode
4+
2. Check the flag 'Delta'
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Holger Brunn \<[email protected]> (https://hunki-enterprises.com)
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This module extends the functionality of ``sql_export`` to allow you to only export changes between export runs.
9.23 KB
Loading

0 commit comments

Comments
 (0)