Skip to content
Open
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
83 changes: 83 additions & 0 deletions report_latex/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
=============
LaTeX reports
=============

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:41aa656fe61335b5c8ac1652f1dbefd76b06c7e233b9178346bf7eec9cd42570
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Freporting--engine-lightgray.png?logo=github
:target: https://github.com/OCA/reporting-engine/tree/18.0/report_latex
:alt: OCA/reporting-engine
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/reporting-engine-18-0/reporting-engine-18-0-report_latex
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/reporting-engine&target_branch=18.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

Provides Latex reports for Odoo.

Features:

- Syntax highlighting for Latex in the Ace code editor.
- Support for \\input{...} syntax; split long documents into multiple
files.
- Direct editing of the generated Latex source.

**Table of contents**

.. contents::
:local:

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

Bugs are tracked on `GitHub Issues <https://github.com/OCA/reporting-engine/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/reporting-engine/issues/new?body=module:%20report_latex%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Lambdao

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

- Len <[email protected]>

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

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.

This module is part of the `OCA/reporting-engine <https://github.com/OCA/reporting-engine/tree/18.0/report_latex>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
5 changes: 5 additions & 0 deletions report_latex/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright 2025 Lambdao
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import models
from . import controllers
41 changes: 41 additions & 0 deletions report_latex/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2025 Lambdao
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

{
"name": "LaTeX reports",
"category": "Reporting",
"version": "18.0.1.0.0",
"author": "Lambdao, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/reporting-engine",
"license": "AGPL-3",
"summary": """Create LaTeX reports.""",
"depends": ["web", "mail"],
"external_dependencies": {
"python": ["jinja2", "pypandoc"],
"deb": ["texlive", "texlive-extra-utils", "pandoc"], # pdflatex, latexpand
},
"installable": True,
"auto_install": False,
"data": [
"security/ir.model.access.csv",
"views/ir_actions_report.xml",
"views/latex_template.xml",
"views/latex_source.xml",
"views/menus.xml",
],
"assets": {
"web.assets_backend": [
"report_latex/static/src/js/latexactionservice.esm.js",
],
"web._assets_core": [
"report_latex/static/src/js/code_editor.esm.js",
],
"web.ace_lib": [
"report_latex/static/lib/ace/mode-latex.js",
],
},
"demo": [
"demo/report_attachments.xml",
"demo/report_latex.xml",
],
}
4 changes: 4 additions & 0 deletions report_latex/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2025 Lambdao
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import main
92 changes: 92 additions & 0 deletions report_latex/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2017 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import json
import mimetypes
from urllib.parse import parse_qs

from werkzeug import exceptions
from werkzeug.datastructures import MultiDict

from odoo.http import content_disposition, request, route, serialize_exception
from odoo.tools import html_escape

from odoo.addons.web.controllers.report import ReportController as ReportControllerBase


class ReportController(ReportControllerBase):
@route()
def report_routes(self, reportname, docids=None, converter=None, **data):
if converter != "latex":
return super().report_routes(
reportname=reportname, docids=docids, converter=converter, **data
)
context = dict(request.env.context)

if docids:
docids = [int(i) for i in docids.split(",")]
if data.get("options"):
data.update(json.loads(data.pop("options")))
if data.get("context"):
# Ignore 'lang' here, because the context in data is the
# one from the webclient *but* if the user explicitely wants to
# change the lang, this mechanism overwrites it.
data["context"] = json.loads(data["context"])
if data["context"].get("lang"):
del data["context"]["lang"]
context.update(data["context"])

ir_action = request.env["ir.actions.report"]
# TODO: is it useful to override to make sure the type is latex?
action_latex_report = ir_action._get_report_from_name(reportname)
if not action_latex_report:
raise exceptions.HTTPException(
description="Latex action report not found for report_name "
f"{reportname}"
)
res, filetype = ir_action._render(reportname, docids, data)
filename = action_latex_report.gen_report_download_filename(docids, data)
if not filename.endswith(filetype):
filename = f"{filename}.{filetype}"
content_type = mimetypes.guess_type("x." + filetype)[0]
http_headers = [
("Content-Type", content_type),
("Content-Length", len(res)),
("Content-Disposition", content_disposition(filename)),
]
return request.make_response(res, headers=http_headers)

@route()
def report_download(self, data, context=None, token=None, readonly=True):
"""This function is used by 'qwebactionmanager.js' in order to trigger
the download of a latex/controller report.

:param data: a javascript array JSON.stringified containg report
internal url ([0]) and type [1]
:returns: Response with a filetoken cookie and an attachment header
"""
requestcontent = json.loads(data)
url, report_type = requestcontent[0], requestcontent[1]
if report_type != "latex":
return super().report_download(
data, context, token=token, readonly=readonly
)
try:
reportname = url.split("/report/latex/")[1].split("?")[0]
docids = None
if "/" in reportname:
reportname, docids = reportname.split("/")
if docids:
response = self.report_routes(
reportname, docids=docids, converter="latex"
)
else: # TODO: ???
data = list(MultiDict(parse_qs(url.split("?")[1])).items())
response = self.report_routes(
reportname, converter="latex", **dict(data)
)
response.set_cookie("fileToken", context)
return response
except Exception as e:
se = serialize_exception(e)
error = {"code": 200, "message": "Odoo Server Error", "data": se}
return request.make_response(html_escape(json.dumps(error)))
27 changes: 27 additions & 0 deletions report_latex/demo/report_attachments.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2025 Lambdao
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<!-- note that it does not matter on which record the attachments are -->
<!-- since we explicitly refer to them in the template -->
<record id="demo_image_line" model="ir.attachment">
<field name="name">lambdao_line.png</field>
<field
name="datas"
type="base64"
file="report_latex/static/demo/lambdao_line.png"
/>
<field name="res_model">res.users</field>
<field name="res_id" ref="base.user_admin" />
</record>
<record id="demo_image_logo" model="ir.attachment">
<field name="name">lambdao_logo.png</field>
<field
name="datas"
type="base64"
file="report_latex/static/demo/lambdao_logo.png"
/>
<field name="res_model">res.users</field>
<field name="res_id" ref="base.user_admin" />
</record>
</odoo>
136 changes: 136 additions & 0 deletions report_latex/demo/report_latex.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2025 Lambdao
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="demo_res_users_latex_template_math" model="latex.template">
<field name="name">user_section_math</field>
<field name="is_root" eval="False" />
<field name="content">
\section{Mathematics}

$$Pr[\mathcal{G}(n,1/2) \models \neg \textup{Ext}_{r,s}] \leq {n \choose r}{n-r \choose s}(1-2^{-r-s})^{n-r-s} \rightarrow 0$$
</field>
</record>
<record id="demo_res_users_latex_template_info" model="latex.template">
<field name="name">user_section_info</field>
<field name="is_root" eval="False" />
<field name="content">
\section{User Information}

\begin{itemize}
\item Name: \VAR{object.name}
\item Login: \VAR{object.login}
\item Email: \VAR{object.email}
\item Company: \VAR{object.company_id.name}
%{ if object.partner_id.phone }%
\item Phone: \VAR{object.partner_id.phone}
%{ endif }%
\end{itemize}
</field>
</record>
<record id="demo_res_users_latex_template_groups" model="latex.template">
<field name="name">user_section_groups</field>
<field name="is_root" eval="False" />
<field name="content">
%{ if object.groups_id }%
\section{User Groups}
\begin{itemize}
%{ for group in object.groups_id }%
\item \VAR{group.name}
%{ endfor }%
\end{itemize}
%{ endif }%
</field>
</record>
<record id="demo_res_users_latex_template" model="latex.template">
<field name="name">Latex Demo Template</field>
<field name="is_root" eval="True" />
<field
name="subtemplate_ids"
eval="[(4, ref('demo_res_users_latex_template_math')),(4, ref('demo_res_users_latex_template_info')), (4, ref('demo_res_users_latex_template_groups'))]"
/>
<field
name="attachment_ids"
eval="[(4, ref('demo_image_logo')), (4, ref('demo_image_line'))]"
/>
<field name="content">
\documentclass{article}
\usepackage{lipsum}
\usepackage[utf8]{inputenc}

\usepackage{setspace}
\usepackage{eso-pic,graphicx}

\usepackage[english]{babel}
\setlength{\parskip}{1em}

\usepackage{avant}
\renewcommand*\familydefault{\sfdefault}
\usepackage[T1]{fontenc}

\usepackage{xparse}
\usepackage{blindtext}
\usepackage{enumitem}

\usepackage{hyperref}
\hypersetup{
colorlinks,
citecolor=black,
filecolor=black,
linkcolor=black,
urlcolor=black
}

\usepackage{amsmath}
\usepackage{amssymb}

\setlength\parindent{0pt}


\onehalfspacing

\AddToShipoutPicture{
\AtPageUpperLeft{\raisebox{-3\baselineskip}{\makebox[\paperwidth]{\begin{minipage}{21cm}\flushright
\includegraphics[width=4cm]{lambdao_logo}\hspace{2cm}
\end{minipage}}}}\AtPageLowerLeft{\raisebox{7\baselineskip}{\makebox[\paperwidth]{
\includegraphics[width=\textwidth]{lambdao_line}
}}}
}

\usepackage{xcolor}
\definecolor{orange}{RGB}{255,165,0}
\newcommand{\VAR}[1]{\textcolor{orange}{\texttt{\detokenize{#1}}}}

\begin{document}

\title{User Report for \VAR{object.name}}
\author{Generated by Odoo}
\date{\today}
\maketitle

\input{user_section_math}

\input{user_section_info}

\input{user_section_groups}

\section{Report Details}
This report was generated on \VAR{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} by user \VAR{user.name}.

\end{document}
</field>
</record>
<record id="demo_res_users_latex_report" model="ir.actions.report">
<field name="name">LaTeX Demo Report</field>
<field name="type">ir.actions.report</field>
<field name="model">res.users</field>
<field name="report_name">latex_user_info</field>
<field name="report_type">latex</field>
<field name="latex_template_id" ref="demo_res_users_latex_template" />
<field
name="print_report_name"
>object.name.replace(' ', '_') + '-demo.pdf'</field>
<field name="binding_model_id" ref="base.model_res_users" />
<field name="binding_type">report</field>
</record>
</odoo>
Loading