diff --git a/CHANGELOG b/CHANGELOG
index 06f9f3595c..f1006bb317 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,4 @@
+* Add support for single record report
* Add support for Flat OpenDocument
* Add get_email in trytond.report
* Replace plain extension by txt
diff --git a/doc/topics/reports/index.rst b/doc/topics/reports/index.rst
index ab7bbf8bb2..d389af2185 100644
--- a/doc/topics/reports/index.rst
+++ b/doc/topics/reports/index.rst
@@ -50,6 +50,10 @@ When defining an `ir.action.report` the following attributes are available:
* ``template_extension``: The template format.
+ * ``single``: `True` if the template works only for one record. If such
+ report is called with more than one record, a zip file containing all the
+ reports will be generated.
+
Report Usage
============
@@ -129,12 +133,12 @@ Accessing models from within the report
---------------------------------------
By default instances of the models the report is for are passed in to the
-report via a list of objects called `objects`. These objects behave just as
-they would within trytond itself. You can access any of the models relations as
-well. For example within the invoice report each object is an invoice and you
-can access the name of the party of the invoice via `invoice.party.name`.
-Additional objects can be passed to a report. This is discussed below in
-`Passing custom data to a report`.
+report via a list of objects called `records` (or `record` if `single` is
+`True`). These records behave just as they would within trytond itself. You
+can access any of the models relations as well. For example within the invoice
+report each object is an invoice and you can access the name of the party of
+the invoice via `invoice.party.name`. Additional objects can be passed to a
+report. This is discussed below in `Passing custom data to a report`.
Within Tryton the underlying model the report can be found by following the
Menu to ``Administration > UI > Actions > Report``. Furthermore in tryton the
diff --git a/trytond/ir/action.py b/trytond/ir/action.py
index c04f666b1b..fd82b940c7 100644
--- a/trytond/ir/action.py
+++ b/trytond/ir/action.py
@@ -409,6 +409,8 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
action = fields.Many2One('ir.action', 'Action', required=True,
ondelete='CASCADE')
direct_print = fields.Boolean('Direct Print')
+ single = fields.Boolean("Single",
+ help="Check if the template works only for one record.")
template_extension = fields.Selection([
('odt', 'OpenDocument Text'),
('odp', 'OpenDocument Presentation'),
@@ -582,6 +584,10 @@ class ActionReport(ActionMixin, ModelSQL, ModelView):
def default_direct_print():
return False
+ @classmethod
+ def default_single(cls):
+ return False
+
@staticmethod
def default_template_extension():
return 'odt'
diff --git a/trytond/ir/view/action_report_form.xml b/trytond/ir/view/action_report_form.xml
index ac77fd5c15..de9363cc6f 100644
--- a/trytond/ir/view/action_report_form.xml
+++ b/trytond/ir/view/action_report_form.xml
@@ -23,6 +23,8 @@ this repository contains the full copyright notices and license terms. -->
+
+
diff --git a/trytond/report/report.py b/trytond/report/report.py
index 5dc60ae242..68b208bab0 100644
--- a/trytond/report/report.py
+++ b/trytond/report/report.py
@@ -1,12 +1,14 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
-import os
import datetime
+import os
+import subprocess
import tempfile
import warnings
-import subprocess
+import zipfile
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
+from io import BytesIO
try:
import html2text
@@ -152,17 +154,32 @@ class Report(URLMixin, PoolBase):
else:
action_report = ActionReport(action_id)
- records = None
+ records = []
model = action_report.model or data.get('model')
if model:
records = cls._get_records(ids, model, data)
- report_context = cls.get_context(records, data)
- oext, content = cls.convert(action_report,
- cls.render(action_report, report_context))
+ if action_report.single and len(records) > 1:
+ content = BytesIO()
+ with zipfile.ZipFile(content, 'w') as content_zip:
+ for record in records:
+ oext, rcontent = cls._execute(
+ [record], data, action_report)
+ rfilename = '%s-%s.%s' % (
+ record.id, record.rec_name, oext)
+ content_zip.writestr(rfilename, rcontent)
+ content = content.getvalue()
+ oext = 'zip'
+ else:
+ oext, content = cls._execute(records, data, action_report)
if not isinstance(content, unicode):
content = bytearray(content) if bytes == str else bytes(content)
return (oext, content, action_report.direct_print, action_report.name)
+ @classmethod
+ def _execute(cls, records, data, action):
+ report_context = cls.get_context(records, data)
+ return cls.convert(action, cls.render(action, report_context))
+
@classmethod
def _get_records(cls, ids, model, data):
pool = Pool()
@@ -200,6 +217,7 @@ class Report(URLMixin, PoolBase):
report_context['context'] = Transaction().context
report_context['user'] = User(Transaction().user)
report_context['records'] = records
+ report_context['record'] = records[0] if records else None
report_context['format_date'] = cls.format_date
report_context['format_currency'] = cls.format_currency
report_context['format_number'] = cls.format_number