Skip to content
Merged
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ addons:
packages:
- expect-dev # provides unbuffer utility
- python-lxml # because pip installation is slow
- pdftk

language: python

Expand Down
116 changes: 116 additions & 0 deletions report_fillpdf/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

====================
Base report fill PDF
====================

This module provides a basic report class that fills pdfs.

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

Make sure you have ``fdfgen`` Python module installed::

$ pip install fdfgen

For testing it is also necessary ``pdftk`` app installed:

Ubuntu ::

apt-get install pdftk

OSX ::

* Install pdftk (https://www.pdflabs.com/tools/pdftk-server/).

Windows ::

* Install pdftk (https://www.pdflabs.com/tools/pdftk-server/).

Usage
=====

An example of Fill PDF report for partners on a module called `module_name`:

A python class ::

from odoo import models

class PartnerFillPDF(models.AbstractModel):
_name = 'report.module_name.report_name'
_inherit = 'report.report_fillpdf.abstract'

@api.model
def get_original_document_path(self, data, objs):
return get_resource_path(
'report_fillpdf', 'static/src/pdf', 'partner_pdf.pdf')

@api.model
def get_document_values(self, data, objs):
objs.ensure_one()
return {'name': objs.name}

A computed form can be executed modifying the computing function ::

from odoo import models

class PartnerFillPDF(models.AbstractModel):
_name = 'report.module_name.report_name'
_inherit = 'report.report_fillpdf.abstract'

@api.model
def get_form(self, data, objs):
return self.env['ir.attachment'].search([], limit=1)

@api.model
def get_document_values(self, data, objs):
objs.ensure_one()
return {'name': objs.name}


A report XML record ::

<report
id="partner_fillpdf"
model="res.partner"
string="Fill PDF"
report_type="fillpdf"
name="report_fillpdf.partner_fillpdf"
file="res_partner"
attachment_use="False"
/>

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

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 smashing it by providing a detailed and welcomed feedback.

Credits
=======

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

* Enric Tobella <etobella@creublanca.es>

Maintainer
----------

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

This module is maintained by the OCA.

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.

To contribute to this module, please visit https://odoo-community.org.
5 changes: 5 additions & 0 deletions report_fillpdf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from . import controllers
from . import models
from . import report
32 changes: 32 additions & 0 deletions report_fillpdf/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': "Base report PDF Filler",

'summary': """
Base module that fills PDFs""",
'author': 'Creu Blanca,'
'Odoo Community Association (OCA)',
'website': "http://github.com/oca/reporting-engine",
'category': 'Reporting',
'version': '11.0.1.0.0',
'license': 'AGPL-3',
'external_dependencies': {
'python': [
'fdfgen',
],
'bin': [
'pdftk',
]
},
'depends': [
'base', 'web',
],
'data': [
'views/webclient_templates.xml',
],
'demo': [
'demo/report.xml',
],
'installable': True,
}
1 change: 1 addition & 0 deletions report_fillpdf/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
43 changes: 43 additions & 0 deletions report_fillpdf/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright (C) 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).

from odoo.addons.web.controllers import main as report
from odoo.http import route, request

import json


class ReportController(report.ReportController):
@route()
def report_routes(self, reportname, docids=None, converter=None, **data):
if converter == 'fillpdf':
report = request.env['ir.actions.report']._get_report_from_name(
reportname)
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'])
pdf = report.with_context(context).render_fillpdf(
docids, data=data
)[0]
pdfhttpheaders = [
('Content-Type', 'application/pdf'),
('Content-Length', len(pdf)),
(
'Content-Disposition',
'attachment; filename=' + report.report_file + '.pdf'
)
]
return request.make_response(pdf, headers=pdfhttpheaders)
return super(ReportController, self).report_routes(
reportname, docids, converter, **data
)
16 changes: 16 additions & 0 deletions report_fillpdf/demo/report.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!--
© 2017 Creu Blanca
License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
-->
<report
id="partner_fillpdf"
model="res.partner"
string="Fill PDF"
report_type="fillpdf"
name="report_fillpdf.partner_fillpdf"
file="res_partner"
attachment_use="False"
/>
</odoo>
1 change: 1 addition & 0 deletions report_fillpdf/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import ir_report
33 changes: 33 additions & 0 deletions report_fillpdf/models/ir_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, fields, models, _
from odoo.exceptions import UserError


class ReportAction(models.Model):
_inherit = 'ir.actions.report'

report_type = fields.Selection(selection_add=[("fillpdf", "PDF Filler")])

@api.model
def render_fillpdf(self, docids, data):
report_model_name = 'report.%s' % self.report_name
report_model = self.env.get(report_model_name)
if report_model is None:
raise UserError(_('%s model was not found' % report_model_name))
return report_model.with_context({
'active_model': self.model
}).fill_report(docids, data)

@api.model
def _get_report_from_name(self, report_name):
res = super(ReportAction, self)._get_report_from_name(report_name)
if res:
return res
report_obj = self.env['ir.actions.report']
qwebtypes = ['fillpdf']
conditions = [('report_type', 'in', qwebtypes),
('report_name', '=', report_name)]
context = self.env['res.users'].context_get()
return report_obj.with_context(context).search(conditions, limit=1)
2 changes: 2 additions & 0 deletions report_fillpdf/report/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import report_fill_pdf
from . import report_partner_pdf
65 changes: 65 additions & 0 deletions report_fillpdf/report/report_fill_pdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).


from io import BytesIO
import os
from contextlib import closing
import logging
import tempfile
from subprocess import Popen, PIPE
from odoo import api, models, tools

_logger = logging.getLogger(__name__)
try:
from fdfgen import forge_fdf
EXTERNAL_DEPENDENCY_BINARY_PDFTK = tools.find_in_path('pdftk')
except (ImportError, IOError) as err:
_logger.debug(err)
raise err


class ReportFillPDFAbstract(models.AbstractModel):
_name = 'report.report_fillpdf.abstract'

def fill_report(self, docids, data):
objs = self.env[self.env.context.get('active_model')].browse(docids)
return self.fill_pdf_form(
self.get_form(data, objs),
self.get_document_values(data, objs)), 'pdf'

@api.model
def get_original_document_path(self, data, objs):
raise NotImplementedError()

@api.model
def get_form(self, data, objs):
with open(self.get_original_document_path(data, objs), 'rb') as file:
result = file.read()
return result

@api.model
def get_document_values(self, data, objs):
return {}

@api.model
def fill_pdf_form(self, form, vals):
fdf = forge_fdf("", vals.items(), [], [], [])
document_fd, document_path = tempfile.mkstemp(
suffix='.pdf', prefix='')
with closing(os.fdopen(document_fd, 'wb')) as body_file:
body_file.write(form)
args = [
EXTERNAL_DEPENDENCY_BINARY_PDFTK,
document_path,
"fill_form", "-",
"output", "-",
"dont_ask",
"flatten"
]
p = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate(fdf)
os.unlink(document_path)
if stderr.strip():
raise IOError(stderr)
return BytesIO(stdout).getvalue()
20 changes: 20 additions & 0 deletions report_fillpdf/report/report_partner_pdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2017 Creu Blanca
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, models
from odoo.modules import get_resource_path


class PartnerPDF(models.AbstractModel):
_name = 'report.report_fillpdf.partner_fillpdf'
_inherit = 'report.report_fillpdf.abstract'

@api.model
def get_original_document_path(self, data, objs):
return get_resource_path(
'report_fillpdf', 'static/src/pdf', 'partner_pdf.pdf')

@api.model
def get_document_values(self, data, objs):
objs.ensure_one()
return {'name': objs.name}
Binary file added report_fillpdf/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions report_fillpdf/static/src/js/report/qwebactionmanager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// © 2017 Creu Blanca
// License AGPL-3.0 or later (https://www.gnuorg/licenses/agpl.html).
odoo.define('report_fillpdf.report', function(require){
'use strict';

var ActionManager= require('web.ActionManager');
var crash_manager = require('web.crash_manager');
var framework = require('web.framework');

ActionManager.include({
ir_actions_report: function (action, options){
var self = this;
var cloned_action = _.clone(action);
if (cloned_action.report_type === 'fillpdf') {
framework.blockUI();
var report_fillpdf_url = 'report/fillpdf/' + cloned_action.report_name;
if(cloned_action.context.active_ids){
report_fillpdf_url += '/' + cloned_action.context.active_ids.join(',');
}else{
report_fillpdf_url += '?options=' + encodeURIComponent(JSON.stringify(cloned_action.data));
report_fillpdf_url += '&context=' + encodeURIComponent(JSON.stringify(cloned_action.context));
}
self.getSession().get_file({
url: report_fillpdf_url,
data: {data: JSON.stringify([
report_fillpdf_url,
cloned_action.report_type
])},
error: crash_manager.rpc_error.bind(crash_manager),
success: function (){
if(cloned_action && options && !cloned_action.dialog){
options.on_close();
}
}
});
framework.unblockUI();
return;
}
return self._super(action, options);
}
});
});
Binary file added report_fillpdf/static/src/pdf/partner_pdf.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions report_fillpdf/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_report
Loading