Commit b96ec371 authored by Jérome Perrin's avatar Jérome Perrin

deferred_style: alarm to automate report production

See Alarm_generateReportDocumentList for the full API and the test for example
usage.
parent d770debe
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Alarm" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>active_sense_method_id</string> </key>
<value> <string>Alarm_generateReportDocumentList</string> </value>
</item>
<item>
<key> <string>automatic_solve</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>configuration_form_id</string> </key>
<value> <string>Alarm_viewGenerateReportDocumentConfiguration</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>enabled</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>template_report_alarm</string> </value>
</item>
<item>
<key> <string>periodicity_day_frequency</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>periodicity_hour</string> </key>
<value>
<tuple>
<int>0</int>
</tuple>
</value>
</item>
<item>
<key> <string>periodicity_minute</string> </key>
<value>
<tuple>
<int>0</int>
</tuple>
</value>
</item>
<item>
<key> <string>periodicity_month</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_month_day</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>periodicity_start_date</string> </key>
<value>
<object>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1609459200.0</float>
<string>GMT</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>periodicity_week</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Alarm</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Report Alarm</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# coding: utf-8
"""
Alarm must define a script that returns a list of dictionnaries with the following keys:
- form_id str: id of an ERP5 Form or ERP5 Report. Required.
- context erp5.portal_type.Base: the context to render the report. Required.
- parameters dict: request parameters to render the report. Required.
Must be serializable for CMFActivity
- skin_name str: skin selection to use for this report ('ODS' | 'ODT'). Required.
- format Optional[str]: convert the document to this format. Note that in scenarios like
storing the result report in document module, it's better to keep the default format (None)
and convert on demand the stored document.
- language str: Localizer language to use. Required.
- setup Callable[[dict], dict]: a function to call at setup before rendering the report.
This function receive this dict as argument and must return a dict of the same type.
- done Callable[[str, list[dict]], None]: a function called at the end of report production.
This function receive two arguments, similar to ERP5Site_notifyReportComplete
- subject str: the name of the report
- attachment_list dict: files produced by the report, dicts with following keys:
- name str: file name
- mime str: file mime type
- content bytes: file body
Note that this script will be called multiple times: A first time to decide which reports have
to be produced and when reports are finished, once for each report, to get the `done` callback.
"""
priority = 3
portal = context.getPortalObject()
report_configuration_script_id = context.getProperty('report_configuration_script_id')
assert report_configuration_script_id
for idx, report_data in enumerate(getattr(context, report_configuration_script_id)()):
notify_report_complete_kwargs = {
'alarm_relative_url': context.getRelativeUrl(),
'idx': idx,
}
if report_data.get('setup'):
report_data = report_data['setup'](report_data)
report_context = report_data.get('context', context)
report_active_context = report_context.activate(
activity='SQLQueue',
node=portal.portal_preferences.getPreferredDeferredReportActivityFamily(),
tag=tag,
priority=priority,
)
if getattr(getattr(report_context, report_data['form_id']), 'pt', 'form_list') == 'form_report':
# erp5 report
report_active_context.Base_computeReportSection(
form=report_data['form_id'],
request_other=report_data['parameters'],
user_name=None,
tag=tag,
skin_name=report_data['skin_name'],
format=report_data.get('format', None),
priority=priority,
localizer_language=report_data['language'],
notify_report_complete_script_id='ERP5Site_finalizeAlarmReportDocumentGeneration',
notify_report_complete_kwargs=notify_report_complete_kwargs,
)
else:
# simple view
params = {}
if 'format' in report_data:
params['format'] = report_data['format']
report_active_context.Base_renderSimpleView(
localizer_language=report_data['language'],
skin_name=report_data['skin_name'],
request_form=report_data['parameters'],
deferred_style_dialog_method=report_data['form_id'],
user_name=None,
params=params,
notify_report_complete_script_id='ERP5Site_finalizeAlarmReportDocumentGeneration',
notify_report_complete_kwargs=notify_report_complete_kwargs,
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>tag, fixit=False, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_generateReportDocumentList</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5 Form" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>Base_edit</string> </value>
</item>
<item>
<key> <string>action_title</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>my_report_configuration_script_id</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Alarm_viewGenerateReportDocumentConfiguration</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string>Alarm_viewGenerateReportDocumenConfiguration</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>ERP5 Form</string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>form_view</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Configuration</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>description</string>
<string>display_width</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_report_configuration_script_id</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>description</string> </key>
<value> <string>ID of a script returning the report configuration. See Alarm_generateReportDocumentList for the exact API</string> </value>
</item>
<item>
<key> <string>display_width</string> </key>
<value> <int>20</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Report Configuration Script ID</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
# pylint:disable=redefined-builtin
portal = context.getPortalObject()
assert alarm_relative_url
alarm = portal.restrictedTraverse(alarm_relative_url)
report_configuration_script_id = alarm.getProperty('report_configuration_script_id')
assert report_configuration_script_id
config = getattr(alarm, report_configuration_script_id)()[idx]
config['done'](subject, attachment_list)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>user_name, subject, message, attachment_list, format, idx, alarm_relative_url</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>ERP5Site_finalizeAlarmReportDocumentGeneration</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
2021-02-04 Jérome
* Alarm to automate report creation
2009-09-12 Jérome
* Allow rendering of any form / printout in deferred mode
\ No newline at end of file
portal_alarms/template_report_alarm
\ No newline at end of file
......@@ -27,6 +27,7 @@
##############################################################################
import unittest
import textwrap
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.utils import createZODBPythonScript
from Testing import ZopeTestCase
......@@ -398,8 +399,103 @@ class TestODTDeferredStyle(TestDeferredStyleBase):
portal_type = "Text"
class TestDeferredReportAlarm(DeferredStyleTestCase):
def getBusinessTemplateList(self):
return super(TestDeferredReportAlarm, self).getBusinessTemplateList() + (
'erp5_pdm',
'erp5_simulation',
'erp5_trade',
'erp5_accounting',
'erp5_knowledge_pad',
'erp5_web',
'erp5_ingestion',
'erp5_ingestion_mysql_innodb_catalog',
'erp5_dms',
)
def test_alarm(self):
# create some data for reports
self.portal.person_module.newContent(portal_type='Person', first_name="not_included")
self.portal.person_module.newContent(portal_type='Person', first_name="yes_included").validate()
# make a script to configure the reports. Once reports are finished, this script
# will save in document module.
report_configuration_script_id = 'Alarm_getTestReportList{}'.format(self.id())
createZODBPythonScript(
self.portal.portal_skins.custom,
report_configuration_script_id,
'',
textwrap.dedent(
'''\
# coding: utf-8
portal = context.getPortalObject()
def makeCallbackFunction(report_title, report_reference):
def done(subject, attachment_list):
for attachment in attachment_list:
document = portal.portal_contributions.newContent(
data=attachment['content'],
filename=attachment['name'],
title=report_title,
reference=report_reference
)
document.share()
return done
report_data_list = [
{
'form_id': 'PersonModule_viewPersonList',
'context': portal.person_module,
'parameters': {
'validation_state': 'validated',
},
'skin_name': 'ODS',
'language': 'fr',
'format': 'txt',
'mode': 'view',
'done': makeCallbackFunction('Persons %s' % DateTime(), 'TEST-Persons.Report')
},
{
'form_id': 'AccountModule_viewTrialBalanceReport',
'context': portal.accounting_module,
'parameters': {
'from_date': DateTime(2021, 1, 1),
'at_date': DateTime(2021, 12, 31),
'section_category': 'group',
'section_category_strict': False,
'simulation_state': ['delivered'],
'show_empty_accounts': True,
'expand_accounts': False,
'per_account_class_summary': False,
'show_detailed_balance_columns': False,
},
'skin_name': 'ODS',
'language': 'fr',
'mode': 'report',
'done': makeCallbackFunction('Trial Balance %s' % DateTime(), 'TEST-Trial.Balance.Report')
}
]
return report_data_list
'''))
alarm = self.portal.portal_alarms.template_report_alarm.Base_createCloneDocument(batch_mode=True)
alarm.edit(
report_configuration_script_id=report_configuration_script_id
)
alarm.activeSense()
self.tic()
person_report, = self.portal.portal_catalog.getDocumentValueList(reference='TEST-Persons.Report')
self.assertIn('yes_included', person_report.getTextContent())
self.assertNotIn('not_included', person_report.getTextContent())
trial_balance_report, = self.portal.portal_catalog.getDocumentValueList(reference='TEST-Trial.Balance.Report')
self.assertEqual(trial_balance_report.getPortalType(), 'Spreadsheet')
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestODSDeferredStyle))
suite.addTest(unittest.makeSuite(TestODTDeferredStyle))
suite.addTest(unittest.makeSuite(TestDeferredReportAlarm))
return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment