Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions export_bg/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
.. |company| replace:: ADHOC SA

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.inc

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

=================
Export Background
=================

Automatically exports large datasets (>500 records) in background to avoid timeouts.

Installation
============

Install the module and its dependency: ``base_bg``

Configuration
=============

Optional: Configure the record threshold in **Settings > Technical > System Parameters**:

* Key: ``export_bg.record_threshold``
* Default: ``500``

Usage
=====

1. Go to any list view and select records to export
2. Click **Export** and choose your format (CSV or XLSX)
3. If records exceed the threshold:
- You'll receive a notification: "Export sent to background"
- You'll receive a message with download link when ready

For exports under the threshold, it works as normal (instant download).

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.dev-adhoc.com/

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/ingadhoc/miscellaneous/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* |company| |icon|

Contributors
------------

Maintainer
----------

|company_logo|

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.inc.
1 change: 1 addition & 0 deletions export_bg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
41 changes: 41 additions & 0 deletions export_bg/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
##############################################################################
#
# Copyright (C) 2026 ADHOC SA (http://www.adhoc.com.ar)
# All Rights Reserved.
#
# 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/>.
#
##############################################################################
{
"name": "Export Background",
"version": "18.0.1.0.0",
"category": "Technical",
"author": "ADHOC SA",
"website": "https://www.adhoc.com.ar",
"license": "AGPL-3",
"summary": "Export large datasets in background to avoid timeouts",
"depends": [
"base_bg",
"web",
],
"data": [],
"assets": {
"web.assets_backend": [
"export_bg/static/src/views/list_controller.js",
],
},
"installable": True,
"auto_install": False,
"application": False,
}
1 change: 1 addition & 0 deletions export_bg/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import export_bg_mixin
52 changes: 52 additions & 0 deletions export_bg/models/export_bg_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import base64
import io
import json

from markupsafe import Markup
from odoo import _, models
from odoo.addons.web.controllers.export import CSVExport
from odoo.tools.misc import xlsxwriter


class IrModel(models.Model):
_name = "ir.model"
_inherit = ["ir.model", "base.bg"]

def _prepare_export_data(self, data):
params = json.loads(data)
Model = self.env[params["model"]].with_context(**params.get("context", {}))
records = Model.browse(params["ids"]) if params.get("ids") else Model.search(params.get("domain", []))
return (
params,
[f["string"] for f in params["fields"]],
records.export_data([f["value"] for f in params["fields"]]).get("datas", []),
)

def web_export_csv(self, data):
params, headers, export_data = self._prepare_export_data(data)
content = CSVExport().from_data(params["fields"], headers, export_data).encode()
return self._save_attachment(params["model"], content, ".csv", "text/csv;charset=utf8")

def web_export_xlsx(self, data):
params, headers, export_data = self._prepare_export_data(data)
buf = io.BytesIO()
wb = xlsxwriter.Workbook(buf, {"in_memory": True})
ws = wb.add_worksheet()
ws.write_row(0, 0, headers)
for i, row in enumerate(export_data, 1):
ws.write_row(i, 0, row)
wb.close()
return self._save_attachment(
params["model"],
buf.getvalue(),
".xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)

def _save_attachment(self, model, content, ext, mime):
att = self.env["ir.attachment"].create(
{"name": f"{model}{ext}", "datas": base64.b64encode(content), "mimetype": mime}
)
return Markup(
f'<p>{_("Your export is ready!")}</p><p><a href="/web/content/{att.id}?download=true" class="btn btn-primary"><i class="fa fa-download"/> {_("Download")} {att.name}</a></p>'
)
43 changes: 43 additions & 0 deletions export_bg/static/src/views/list_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/** @odoo-module **/

import { ListController } from "@web/views/list/list_controller";
import { patch } from "@web/core/utils/patch";

patch(ListController.prototype, {
async downloadExport(fields, import_compat, format) {
const resIds = this.isDomainSelected ? false : await this.getSelectedResIds();
const recordCount = resIds ? resIds.length : (this.model.root.count || 0);

const threshold = await this.model.orm.call(
"ir.config_parameter",
"get_param",
["export_bg.record_threshold", "500"]
);

if (recordCount > parseInt(threshold)) {
const data = {
model: this.props.resModel,
fields: fields,
ids: resIds,
domain: this.model.root.domain,
import_compat: import_compat,
};

const method = format === "csv" ? "web_export_csv" : "web_export_xlsx";
const actionResult = await this.model.orm.call(
"ir.model",
"bg_enqueue",
[method],
{
data: JSON.stringify(data),
}
);

if (actionResult && actionResult.type === "ir.actions.client") {
this.env.services.action.doAction(actionResult);
}
} else {
await super.downloadExport(...arguments);
}
},
});