diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bcc247..253da33 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,12 +21,13 @@ jobs: name: Setup Log Folder For Reports command: sudo mkdir -p .log && sudo chmod 777 .log - # - run: - # name: Run Test - # command: docker-compose run --rm odoo run_pytest.sh + - run: + name: Run Test + command: docker-compose run --rm odoo run_pytest.sh + - # - store_test_results: - # path: .log + - store_test_results: + path: .log # job that find the next tag for the current branch/repo and push the tag to github. # it will trigger the publish of a new docker image. diff --git a/.docker_files/main/__manifest__.py b/.docker_files/main/__manifest__.py index d89d7a4..83adafc 100644 --- a/.docker_files/main/__manifest__.py +++ b/.docker_files/main/__manifest__.py @@ -11,7 +11,6 @@ "category": "Other", "summary": "Install all addons required for testing.", "depends": [ - "account", "report_aeroo", ], "installable": True, diff --git a/.docker_files/requirements.txt b/.docker_files/requirements.txt index 368472c..513c93d 100644 --- a/.docker_files/requirements.txt +++ b/.docker_files/requirements.txt @@ -1,9 +1,6 @@ -# git+https://github.com/aeroo/aeroolib.git -# waiting for PR https://github.com/aeroo/aeroolib/pull/12 -git+https://github.com/adhoc-dev/aeroolib@master-fix-ods -git+https://github.com/aeroo/currency2text.git -# use this genshi version to fix error when, for eg, you send arguments like "date=True" check this https://genshi.edgewall.org/ticket/600 +git+https://github.com/numigi/aeroolib@master +Babel==2.9.1 genshi==0.7.7 freezegun==1.5.1 html2text==2024.2.26 -ddt==1.2.1 +ddt==1.2.1 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 672725e..3606ef1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,24 @@ FROM quay.io/numigi/odoo-public:16.latest LABEL maintainer="contact@numigi.com" USER root +#Install dependencies for aeroo reports +RUN apt-get update && apt-get install -y --no-install-recommends \ + default-jre \ + libreoffice-java-common \ + libreoffice-writer \ + poppler-utils -COPY .docker_files/requirements.txt . -RUN pip3 install -r requirements.txt +# we can't use `pip install --user` as the $HOME of odoo is a volume +# so everything that is installed in $HOME will be overwritten by the mounting. -ENV THIRD_PARTY_ADDONS /mnt/third-party-addons -RUN mkdir -p "${THIRD_PARTY_ADDONS}" && chown -R odoo "${THIRD_PARTY_ADDONS}" +COPY .docker_files/requirements.txt ./requirements.txt +RUN pip3 install -r ./requirements.txt && rm ./requirements.txt USER odoo -COPY report_aeroo /mnt/extra-addons/report_aeroo +COPY ./report_aeroo /mnt/extra-addons/report_aeroo COPY .docker_files/main /mnt/extra-addons/main COPY .docker_files/odoo.conf /etc/odoo + + diff --git a/report_aeroo/LICENSE b/report_aeroo/LICENSE deleted file mode 100644 index d9fe216..0000000 --- a/report_aeroo/LICENSE +++ /dev/null @@ -1,30 +0,0 @@ -################################################################################ -# -# Copyright (c) 2009-2018 Alistek ( http://www.alistek.com ) All Rights Reserved. -# General contacts -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This module is GPLv3 or newer and incompatible -# with OpenERP SA "AGPL + Private Use License"! -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -################################################################################ diff --git a/report_aeroo/README.md b/report_aeroo/README.md deleted file mode 100755 index a7ad735..0000000 --- a/report_aeroo/README.md +++ /dev/null @@ -1,88 +0,0 @@ -This is ALPHA version of Aeroo Reports for Odoo v11 --------------------------------------------------------------------------------- - -Enterprise grade reporting solution for Odoo --------------------------------------------------------------------------------- -Aeroo Reports for Odoo is a comprehensive and versatile reporting engine based -on Aeroo Library. - -It supports most of the current and leacy business document formats. Being it -printable invoice, personalized HTML content for e-mail marketing or just an -inventory labels - Aeroo Reports can do them all. - -Even more, using RAW reporting option, you can create reports for your custom -document format, that gives full advantage of integrating bost office & -industrial printing hardware and software. - -Developing new reports is as easy as using mainstream office packages - -OpenOffice.org/LibreOffice. That means, use them as WYSIWYG template editor. - -For more information on how this technology differs from other -reporting options, please reference reporting engine comparison matrix: -http://www.alistek.com/wiki/index.php/Comparison_matrix_of_reporting_engines_for_OpenERP - - -Sponsors of Aeroo Reports port for Odoo v11 --------------------------------------------------------------------------------- -Special thanks goes to ADHOC commitment and FlectraHQ for making huge difference - -* ADHOC - https://www.adhoc.com.ar/ -* FlectraHQ - https://flectrahq.com/ -* BESCO - http://besco.vn" -* Serpent CS - http://www.serpentcs.com/ -* CYSFuturo - support@​cysfuturo.com - -Modules in this category --------------------------------------------------------------------------------- -* Aeroo Reports (report_aeroo), this module -* Aeroo Reports Direct Print (report_aeroo_direct_print), print reports without preview -* Aeroo Reports Prinscreen (report_aeroo_printscreen), report any model in a spreadsheet report -* Aeroo Reports demo (report_aeroo_sample), just a demo - -More information and documentation --------------------------------------------------------------------------------- -http://www.alistek.com/wiki/index.php/Main_Page - -Report templates in the following formats --------------------------------------------------------------------------------- -* Open Document Format (ODF) - .odt, .ods; -* Other ASCII based formats, like HTML, CSV, etc. - -Output formats: --------------------------------------------------------------------------------- -* Open Document Format (ODF) - .odt, .ods; -* Other ASCII based formats, like HTML, CSV, etc. -* using Aeroo DOCS - PDF, DOC, XLS, CSV. - -Reporting engine features --------------------------------------------------------------------------------- -* Add reports from UI "on the fly"; -* Install reports from module; -* Dynamic template load/unload; -* Extra Functions - set of functions for rapid template development; -* Use templates stored on filesystem, database or elsewhere; -* Same button - different templates; -* Powerful stylesheet system for ODF templates; -* Global or local stylesheets; -* Template preloading for performance concerns; -* User defined parsers; -* Report deactivation; -* Optional format fallback; -* Add/Remove print button wizards; -* Test report on particular object ID, directly from Report form; -* Translatable reports; -* Translation export; -* Number of copies; -* Universal Report wizard; -* Override report file extension (for direct printing, etc); -* Separate input/output format selections; - -Input - Output format pairs --------------------------------------------------------------------------------- -* odt - odt/doc/pdf; -* ods - ods/xls/pdf/csv; -* html - html; - -Original work and inspiration --------------------------------------------------------------------------------- -This module is based on the original work of Simone Orsi (Domsense) diff --git a/report_aeroo/README.rst b/report_aeroo/README.rst new file mode 100644 index 0000000..5736fff --- /dev/null +++ b/report_aeroo/README.rst @@ -0,0 +1,681 @@ +============= +Aeroo Reports +============= +This is the main module required for using Aeroo reports. + +.. contents:: Table of Contents + +Context +======= + +Qweb +---- +Odoo comes with a native reporting engine called ``Qweb``. +This engine is very good for some kind of reports. +When you need a web report with dynamic behavior, qweb is an appropriate choice. + +However, qweb has weaknesses when rendering PDF documents, such as invoices, quotations and delivery slips. + +First, Qweb templates are hard to customize and maintain. + +Second, a Qweb report has a single template for all languages. +The translations are edited separately and injected in the template at rendering. +When making a change to a template, you must verify that it renders appropriately for all active translations. + +The most major point is portability of reports between major versions of Odoo. +When migrating your system to an earlier version, you want to minimize the time required to port your reports. + +Aeroo +----- +Aeroo is an alternative to Qweb. + +It uses ``Libreoffice`` documents as templates. +It aims to offer lattitude to the end user regarding the look of a report. + +.. image:: static/description/libreoffice_invoice.png + +The report can be rendered as ``PDF``. + +.. image:: static/description/libreoffice_invoice_pdf.png + +Installation +============ +There are two linux packages required for running this module. + +.. code-block:: bash + + sudo apt-get update && apt-get install -y --no-install-recommends \ + libreoffice-writer \ + poppler-utils + +The module uses `libreoffice-writer `_ in headless mode for rendering the reports. + +When reports in pdf format for multiple records (in list view) it uses `poppler-utils `_ +to merge the rendered reports into a single pdf. + +See the Dockerfile on this repository for details. + +Configuration +============= +Aeroo reports can be found under the ``Dashboard`` application. + +.. image:: static/description/aeroo_report_menu.png + +When configuring an aeroo report, multiple parameters must be defined. + +.. image:: static/description/report_form.png + +Name +---- +The field ``Name`` is the label that will appear on the print button. + +.. image:: static/description/invoice_print_button.png + +Model +----- +This is the technical value that links the report with a given type of document. + +In the example, the model is an invoice, so the technical value is ``account.move``. +This technical value can be found in the url of the form view. + +.. image:: static/description/invoice_url_model.png + +Template Name +------------- +This is a technical value that identifies your report in Odoo. +The given value is arbitrary. + +.. image:: static/description/report_technical_name.png + +You should choose a value with no accent, no special caracters and no space. +Only letters and underscores. + +The value must be unique throughout the system. + +Template Mime-type +------------------ +This field identifies the type of template. + +.. image:: static/description/report_template_mime_type.png + +Output Mime-type +---------------- +Three formats are available for the generated report. + +.. image:: static/description/report_output_mime_type.png + +Typically, a report is printed as ``PDF``. + +However, for testing a report, rendering as ``ODT`` can be useful. + +Otherwise, rendering as ``Microsoft Word`` can be useful in case you +need to edit the document manually before printing it as ``PDF``. + +Template +-------- +There are 3 options for defining the report template. + +.. image:: static/description/report_template_options.png + +Database +~~~~~~~~ +This option allows to upload a template file from your computer. + +.. image:: static/description/report_template_database.png + +File +~~~~ +This option allows to use a file defined in a module. + +.. image:: static/description/report_template_file.png + +The given path must start with the name of the module, +followed by the path of the file inside that module. + +This option is mostly intended for demo reports. + +Multiple Templates +~~~~~~~~~~~~~~~~~~ +The third option is ``Different Template per Language / Company``. + +.. image:: static/description/report_template_multi.png + +This option allows to define a specific template to use per company and / or language. + +When managing a report that needs to be printed in the language of a partner, +it is easier to maintain completely separate templates for each language. + +Also, mainting separate templates per company is useful if you want the look of the report +to be different per company. + +.. image:: static/description/report_template_multi_form.png + +Both the language and the company are optional fields. +Letting the field empty is a wildcard. + +The first matching template is always used when printing a report. +Therefore, template lines with wildcards should be placed last. + +.. image:: static/description/report_template_multi_filled.png + +Report Context +-------------- +When formating numbers, currencies and dates in a report, the report engine needs to know +for which language, timezone and localization to format these values. + +This section allows the engine to evaluate these values. + +.. image:: static/description/report_context.png + +Typically, the values will be inherited from the user generating the report. + +.. image:: static/description/report_context_user.png + +Or linked to the partner related to the document. + +.. image:: static/description/report_context_partner.png + +List Views +---------- +By default, aeroo reports can be generated from a list view. + +.. image:: static/description/list_view_standard_report.png + +The result is a merged ``PDF`` document containing the combined reports for all selected records. + +.. image:: static/description/list_view_standard_report_pdf.png + +However, it is sometime required to have a single report that takes as input a list of records. + +One typical example is a report based on a selection of timesheet lines. + +You can define such report by checking the box ``Generate Report From Record List``. + +.. image:: static/description/report_from_record_list.png + +When printing the report, the template is rendered only one time with the given list of records. + +.. image:: static/description/list_view_report.png + +.. image:: static/description/list_view_report_pdf.png + +Inside the Libreoffice template, instead of using the variable ``o``, you must iterate over the variable ``objects``. + +.. image:: static/description/report_from_record_list_template.png + +Attachments / Filename +---------------------- +By default, when printing a report, the name of the file is the name of the report. + +.. image:: static/description/default_filename.png + +This can be customized. + +.. image:: static/description/report_attachment_filename.png + +You can also customize the file name per language. + +.. image:: static/description/report_attachment_filename_multi.png + +.. + + A line with the field Language empty is interpreted as a wildcard. + Such line must be placed last. + +Reload From Attachment +---------------------- +When this box is checked, the report will be saved as attachment to the document when printed. + +.. image:: static/description/report_reload_from_attachment.png + +Then, when printing again the report, the same file is returned instead of rerendering the report. + +The report is rerendered if the file name changes. + +This feature is typically used for invoices. +Once sent to a customer, the PDF of an invoice may not be changed. + +Add To Print Menu +----------------- +The button ``Add in the Print menu`` adds an item in the print menu of the form view of the related model. + +.. image:: static/description/report_add_print_menu.png + +.. image:: static/description/form_print_menu.png + +Editing a Template +================== + +Fields +------ +To display the value of a field inside a template, you must insert a field of type ``Placeholder``. + +.. image:: static/description/libreoffice_insert_field.png + +.. image:: static/description/libreoffice_insert_field_placeholder.png + +In ``Placeholder``, you can define the expression to evaluate. + +.. image:: static/description/libreoffice_placeholder_filled.png + +Then click on insert. + +.. image:: static/description/libreoffice_placeholder_insert.png + +In this example, we are printing the name of the partner related to the document. + +The variable ``o`` represents the document being printed (for example, an invoice or a sales order). + +If Statements +------------- +It is possible to display a section of the report based on a condition. + +.. image:: static/description/libreoffice_if_statement.png + +For this to work, you need to insert two fields of type ``Input Field``. + +.. image:: static/description/libreoffice_insert_input_field.png + +Inside ``Reference``, you can write your condition. + +.. image:: static/description/libreoffice_if_statement_reference.png + +The condition must be formatted like an xml node. +The attribute test contains the expression to evaluate. + +.. + + + +The second input field contains the end statement. + +.. image:: static/description/libreoffice_if_statement_end.png + +For Loops +--------- +It is possible to iterate over a list of records inside a table. + +.. image:: static/description/libreoffice_for_loop.png + +For this to work, the beginning and ending clauses of the loop must be placed in rows of the table. +The rows containing these clauses are removed when rendering the report. + +The beginning clause must contain the code of the loop. +The format is similar to ``if statements``. + +.. image:: static/description/libreoffice_for_loop_reference.png + +The attribute each must contain the loop. + +.. + + + +1. The first part ``line`` is the name of the variable for the iteratee. It can be a variable name of your choice. + +2. The second part ``o.invoice_line_ids`` is the iterator. + +Images +------ +The engine allows to render images in reports. + +To do so, you must insert a frame. + +.. image:: static/description/libreoffice_insert_frame.png + +In the ``Options`` tab, enter the technical value in ``Name``. + +.. image:: static/description/libreoffice_frame_options.png + +The technical value is: + +.. + + image: asimage(your_expression) + +Where ``your_expression`` is the python expression to get the content of your image. + +In the ``Type`` tab, make sure that your image is sized relative to the paragraph. + +.. image:: static/description/libreoffice_frame_type.png + +Then, you may resize the frame to get the desired width and height. + +.. image:: static/description/libreoffice_image_resize.png + +Barcodes +-------- +Barcodes can be inserted the same way as other images. + +However, the technical value is a bit different. + +.. image:: static/description/libreoffice_frame_barcode_options.png + +.. + + image: barcode(your_barcode, barcode_type, height) + +For now, the available types of barcode are: + +* ean13 +* code128 +* code39 + +QR Codes +-------- +QR codes can also be inserted the same way as images. + +.. image:: static/description/libreoffice_writer_qrcode.png + +.. image:: static/description/libreoffice_frame_qrcode_options.png + +.. + + image: qrcode(your_code, size='x.xin') + +The parameter size must contain the desired size of the image. +It should be the same as the width and height defined on the frame. + +.. image:: static/description/libreoffice_frame_qrcode_type.png + +.. image:: static/description/libreoffice_writer_qrcode_rendered.png + +Numbers +------- +When inserting a field that renders a number, you must use a utility function +to format the number properly. + +.. image:: static/description/libreoffice_number_utilities.png + +Aeroo defines helpers for formatting numbers. + +* format_decimal +* format_currency +* format_hours + +Example for format_decimal +~~~~~~~~~~~~~~~~~~~~~~~~~~ +This function takes a number. +It returns the amount formatted in the context of the report. + +.. code-block:: python + + format_decimal(o.amount_total) + +If the report is printed in Canada French, the output will look like: + +.. code-block:: + + 1 500,00 + +Exemple for format_currency +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This function takes a number and a currency object. +It returns the amount and currency symbol formatted in the context of the report. + +.. code-block:: python + + format_currency(o.amount_total, o.currency_id) + +If the report is printed in Canada French, the output will look like: + +.. code-block:: + + 1 500,00 $US + +Example for format_hours +~~~~~~~~~~~~~~~~~~~~~~~~ +This function formats an amount into hours and minutes. + +.. code-block:: python + + format_hours(o.amount) + +Suppose the amount is ``1.25``, the formatted amount will be ``01:15``. + +Force a number format +~~~~~~~~~~~~~~~~~~~~~ +Both format_decimal and format_currency functions accept an optional `amount_format` parameter. + +This parameter accepts a number format using the variables documented on the babel website: + +http://babel.pocoo.org/en/latest/numbers.html#pattern-syntax + +Forcing a Country +~~~~~~~~~~~~~~~~~ +Languages in Odoo are very complex to maintain. +For example, having all ``en_CA``, ``en_US``, ``fr_CA``, ``fr_FR`` loaded in Odoo would lead to a lot of maintainance effort. + +Depending on the country, the amount in currency should be formatted differently: + +* If you have a customer in United-States, he might expect the default ``$`` symbol to represent ``USD``, and ``CA$`` to represent ``CAD``. +* If your customer is in Canada, he might however expect ``$`` to represent ``CAD``, and ``US$`` to represent ``USD``. + +Aeroo mitigates this issue by combining the contextual Odoo language and country together. + +If your Odoo language is ``fr_FR`` and your country is Canada, you get the locale ``fr_CA``. + +To use this feature, you may call the ``format_currency`` with an optional ``country`` parameter. + +.. code-block:: python + + format_currency(o.amount_total, o.currency_id, country=o.partner_id.country_id) + + +Default Countries and Currencies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Since version ``2.2.0`` of ``report_aeroo``, it is possible to define a default country and currency on the report. + +.. image:: static/description/report_context_country_and_currency.png + +These fields are evaluated at rendering, like ``Language Evaluation`` and ``Company Evaluation``. + +The values are used by default in the ``format_currency`` function. +Therefore, in your template, each time you need to show an amount in currency, you only need to pass the amount as parameter: + +.. code-block:: python + + format_currency(o.amount_total) + +Suppose the language is evaluated to ``fr_FR``, the country is ``Canada`` and the currency is ``USD``, +you would get an amount format as follow: + +.. code-block:: + + 1 500,00 $US + +Date and Time +------------- +Similarly to numbers, you can format a date field. + +.. image:: static/description/libreoffice_date_field.png + +Aeroo defines the following helpers for formatting dates and time. + +* format_date +* format_datetime +* today +* now +* relativedelta + +The variables that you can use in these functions are documented on the babel website: + +http://babel.pocoo.org/en/latest/dates.html#date-fields + +Exemple for format_date +~~~~~~~~~~~~~~~~~~~~~~~ +This function formats a date object into a string. + +.. code-block:: python + + format_date(o.date_invoice, 'dd MMMM yyyy') + +If the report is printed in French, the output will look like: + +.. code-block:: + + 06 avril 2018 + +Exemple for format_datetime +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This function formats a datetime object into a string. + +.. code-block:: python + + format_datetime(o.confirmation_date, 'dd MMMM yyyy hh:mm a') + +If the report is printed in French, the output will look like: + +.. code-block:: + + 6 avril 2018 10:34 AM + +Exemple for today +~~~~~~~~~~~~~~~~~ +The function ``today`` is the same as ``format_date``, but with the current date in the user's timezone. + +.. code-block:: python + + today('dd MMMM yyyy') + +Suppose we are on the 6 of April 2018 and the report is printed in French, the output will look like: + +.. code-block:: + + 06 avril 2018 + +The function accepts an optional parameter ``delta`` allowing to pass a time delta to apply to the current date. + +Here is an example for printing the current date plus 2 months. + +.. code-block:: python + + today('dd MMMM yyyy', delta=relativedelta(months=2)) + +Exemple for now +~~~~~~~~~~~~~~~ +The function ``now`` is the same as ``format_datetime``, but with the current time in the user's timezone. + +.. code-block:: python + + now('dd MMMM yyyy hh:mm a') + +Suppose we are on the 6 of April 2018, 10:34 AM and the report is printed in French, the output will look like: + +.. code-block:: + + 06 avril 2018 10:34 AM + +The function accepts an optional parameter ``delta`` allowing to pass a time delta to apply to the current time. + +Here is an example for printing the current time plus 2 months. + +.. code-block:: python + + now('dd MMMM yyyy hh:mm a', delta=relativedelta(months=2)) + +Time Delta +~~~~~~~~~~ +You may use the function relativedelta to add an interval to a date to be printed in a report. + +For example, suppose your invoice is issued on ``2022-08-01`` +and you want to print a date 2 months in the future (relative to the invoice date). + +.. code-block:: python + + format_date(o.date_invoice + relativedelta(months=2), 'dd MMMM yyyy') + + +The result is: + +.. code-block:: + + 01 octobre 2022 + +The function relativedelta can be used with different types of intervals (days, months, weeks, years, etc). +The documentation can be found `here `_. + +Grouping Rows +------------- +It is possible to group rows to display in a table. + +In the following example, the invoice lines are grouped by per product category: + +.. code-block:: xml + + + +Each tuple contains: + +1. The groupment key +2. The records matching this groupment key + +Example +~~~~~~~ +Here is a preview on how to organize the for/each statements in your libreoffice template. + +.. image:: static/description/libreoffice_group_by.png + +In this example, we define two nested ``For Each`` loops. + +The outer loop groups the records by month. + +Inside the outer loop, the month is printed in one line, followed by one line +per record for this month. + +Grouping Rows in a Particular Order +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +By default, the groupment keys are sorted in natural order. +If the groupment key is a string, it will be sorted alphabetically. + +Usually, we will require to sort the grouped records by some criteria. +This can be done using the argument ``sort`` of the ``group_by`` function. + +The ``sort`` argument expects a function. +This function takes as argument the groupment key. + +In the following example, the groupment keys (the products) are sorted by their ``Display Name``. + +.. code-block:: xml + + + +Spreadsheets +------------ +The module allows to use a spreadsheet (ods) as template. + +.. image:: static/description/report_ods.png + +In a spreadsheet, you must insert hyperlinks in order to display data dynamically. + +Go to: Insert -> Hyperlink, then in the field URL, write python://your-python-expression + +.. image:: static/description/libreoffice_calc_insert_link.png + +.. image:: static/description/libreoffice_calc_insert_link_2.png + +Here is an example for a list of partner names and emails. + +.. image:: static/description/libreoffice_calc_with_links.png + +When rendered the report looks like this. + +.. image:: static/description/report_ods_rendered.png + +Email Templates +=============== +The module adds an easy way to attach reports to an email template. + +.. image:: static/description/email_template_form.png + +The difference between this feature and a report attachment from `Advanced Settings / Optional report to print and attach` is: + +1. You may attach more than one aeroo reports. +2. You do not need to redefine the name of the attachment in the email template. + The attachment name will be the one defined on the report. + +Contributors +============ +* Alistek +* Savoir-faire Linux +* Numigi (tm) and all its contributors (https://bit.ly/numigiens) diff --git a/report_aeroo/__init__.py b/report_aeroo/__init__.py old mode 100755 new mode 100644 index 6211379..de1d3dd --- a/report_aeroo/__init__.py +++ b/report_aeroo/__init__.py @@ -1,25 +1,8 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -# check_list = [ -# 'import aeroolib', -# 'import genshi', -# 'from genshi.template import NewTextTemplate', -# 'from xml.dom import minidom', -# 'from pyPdf import PdfFileWriter, PdfFileReader', -# ] - -# from . import check_deps -# check_deps(check_list) +# Copyright 2008-2014 Alistek +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). from . import controllers from . import models -from . import report_parser - -from . import report -from . import demo - -from . import wizard +from . import tests diff --git a/report_aeroo/__manifest__.py b/report_aeroo/__manifest__.py index 7fc754d..db248e7 100644 --- a/report_aeroo/__manifest__.py +++ b/report_aeroo/__manifest__.py @@ -1,33 +1,33 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ +# Copyright 2008-2014 Alistek +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018-Today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). { - 'name': 'Aeroo Reports', - 'version': "16.0.1.2.0", - 'category': 'Generic Modules/Aeroo Reports', - 'summary': 'Enterprise grade reporting solution', - 'author': 'Alistek', - 'website': 'http://www.alistek.com', - 'complexity': "easy", - 'depends': ['base', 'web', 'mail'], - 'data': [ - "views/report_view.xml", + "name": "Aeroo Reports", + "version": "16.0.1.0.1", + "category": "Generic Modules/Aeroo Reports", + "summary": "Enterprise grade reporting solution", + "author": "Alistek", + "maintainer": "Numigi", + "website": "https://bit.ly/numigi-com", + "depends": ["mail"], + "external_dependencies": { + "python": ["aeroolib", "babel", "genshi"], + }, + "data": [ + "security/security.xml", + "views/ir_actions_report.xml", + "views/mail_template.xml", "data/report_aeroo_data.xml", - "wizard/installer.xml", "security/ir.model.access.csv", - "demo/report_sample.xml", ], - 'assets': { - 'web.assets_backend': [ - 'report_aeroo/static/src/js/report/reportactionmanager.js', + "demo": ["demo/report_sample.xml"], + "assets": { + "web.assets_backend": [ + "report_aeroo/static/src/js/action_manager.js" ], }, "license": "GPL-3 or any later version", - 'installable': True, - 'active': False, - 'application': True, - 'auto_install': False, + "installable": True, } diff --git a/report_aeroo/barcode/EANBarCode.py b/report_aeroo/barcode/EANBarCode.py old mode 100755 new mode 100644 diff --git a/report_aeroo/barcode/FreeMonoBold.ttf b/report_aeroo/barcode/FreeMonoBold.ttf old mode 100755 new mode 100644 diff --git a/report_aeroo/barcode/__init__.py b/report_aeroo/barcode/__init__.py old mode 100755 new mode 100644 index 64fcc48..a8acc34 --- a/report_aeroo/barcode/__init__.py +++ b/report_aeroo/barcode/__init__.py @@ -1,6 +1,7 @@ ############################################################################## # -# Copyright (c) 2008-2011 Alistek Ltd (http://www.alistek.com) All Rights Reserved. +# Copyright (c) 2008-2011 Alistek Ltd (http://www.alistek.com) +# All Rights Reserved. # General contacts # # WARNING: This program as such is intended to be used by professional @@ -27,6 +28,4 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -############################################################################## - -from . import barcode +############################################################################# diff --git a/report_aeroo/barcode/barcode.py b/report_aeroo/barcode/barcode.py deleted file mode 100755 index e464b87..0000000 --- a/report_aeroo/barcode/barcode.py +++ /dev/null @@ -1,59 +0,0 @@ -############################################################################## -# -# Copyright (c) 2008-2011 Alistek Ltd (http://www.alistek.com) All Rights Reserved. -# General contacts -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This module is GPLv3 or newer and incompatible -# with OpenERP SA "AGPL + Private Use License"! -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -############################################################################## - -from .code128 import get_code -from .code39 import create_c39 -from .EANBarCode import EanBarCode -from io import StringIO - - -def make_barcode(code, code_type='ean13', rotate=None, height=50, xw=1): - if code: - if code_type.lower() == 'ean13': - bar = EanBarCode() - im = bar.getImage(code, height) - elif code_type.lower() == 'code128': - im = get_code(code, xw, height) - elif code_type.lower() == 'code39': - im = create_c39(height, xw, code) - else: - return StringIO(), 'image/png' - - tf = StringIO() - try: - if rotate is not None: - im = im.rotate(int(rotate)) - except BaseException: - pass - im.save(tf, 'png') - size_x = str(im.size[0] / 96.0) + 'in' - size_y = str(im.size[1] / 96.0) + 'in' - return tf, 'image/png', size_x, size_y diff --git a/report_aeroo/barcode/code128.py b/report_aeroo/barcode/code128.py old mode 100755 new mode 100644 index 7f6cc9e..09b2f29 --- a/report_aeroo/barcode/code128.py +++ b/report_aeroo/barcode/code128.py @@ -3,9 +3,9 @@ # This list was cut'n'pasted verbatim from the "Code 128 Specification Page" # at http://www.adams1.com/pub/russadam/128code.html - from PIL import Image + codelist = """0 SP SP 00 2 1 2 2 2 2 1 ! ! 01 2 2 2 1 2 2 2 " " 02 2 2 2 2 2 1 @@ -116,27 +116,28 @@ 105 (Hex 89) START (Code C) 2 1 1 2 3 2 106 STOP 2 3 3 1 1 1 2""" + codes = {} values = {} -for lst in codelist.split('\n'): - lst.strip() - num, a1, b1, c1, code = lst.split('\t') - num = int(num.split(' ')[0]) +for element in codelist.split("\n"): + element.strip() + num, a1, b1, c1, code = element.split("\t") + num = int(num.split(" ")[0]) values[num] = [int(x) for x in code.split()] codes[b1.strip()] = num -codes[' '] = codes['SP'] +codes[" "] = codes["SP"] -for lst in other.split('\n'): - lst.strip() - num, name, code = lst.split('\t') - num = int(num.split(' ')[0]) +for element in other.split("\n"): + element.strip() + num, name, code = element.split("\t") + num = int(num.split(" ")[0]) values[num] = [int(x) for x in code.split()] codes[name.strip()] = num def encode_message(msg): - startnum = codes['START (Code B)'] + startnum = codes["START (Code B)"] message = values[startnum][:] chksum = startnum mult = 1 @@ -150,17 +151,17 @@ def encode_message(msg): chksum = chksum % 103 message = message + values[chksum] - message = message + values[codes['STOP']] + message = message + values[codes["STOP"]] return message def get_code(message, xw=1, h=100, rotate=None): - """ message is message to code. - xw is horizontal multiplier (in pixels width of narrowest bar) - h is height in pixels. + """message is message to code. + xw is horizontal multiplier (in pixels width of narrowest bar) + h is height in pixels. - Returns a Python Imaging Library object.""" + Returns a Python Imaging Library object.""" widths = [xw * 20] + encode_message(message) + [xw * 20] @@ -170,7 +171,10 @@ def get_code(message, xw=1, h=100, rotate=None): bits = bits + [i] * w * xw i = 1 - i - i = Image.new('1', (len(bits), h), 1) + # print len(bits) + # print bits + + i = Image.new("1", (len(bits), h), 1) for b in range(len(bits)): for y in range(h): diff --git a/report_aeroo/barcode/code39.py b/report_aeroo/barcode/code39.py old mode 100755 new mode 100644 index 381491a..8ccc35c --- a/report_aeroo/barcode/code39.py +++ b/report_aeroo/barcode/code39.py @@ -10,8 +10,10 @@ # Usage example: # code39.py 100 2 "Hello World" barcode.png # -# This creates a PNG image "barcode.png" containing a barcode of the height of 100px -# a min line width of 2px with "Hello World" encoded as "*HELLO WORLD*" in Code 39 +# This creates a PNG image "barcode.png" containing a barcode of the height +# of 100px +# a min line width of 2px with "Hello World" encoded as "*HELLO WORLD*" in +# Code 39 from PIL import Image, ImageDraw, ImageFont from odoo.tools import config, ustr @@ -69,7 +71,7 @@ } -def create_c39(height, smallest, text): +def create_c39(height, smallest, text): # noqa C901 pixel_length = 0 i = 0 newtext = "" @@ -82,23 +84,28 @@ def create_c39(height, smallest, text): cmap = charmap[char] if len(cmap) != 9: continue + j = 0 while j < 9: seg = int(cmap[j]) + if seg == 0 or seg == 1: pixel_length = pixel_length + smallest seglist.append(seg) elif seg == 2 or seg == 3: pixel_length = pixel_length + smallest * 3 seglist.append(seg) + j = j + 1 + newtext += char except BaseException: continue + pixel_length = pixel_length + 2 * marginx + len(newtext) * smallest pixel_height = height + 2 * marginy + fontsize - barcode_img = Image.new('RGB', [pixel_length, pixel_height], "white") + barcode_img = Image.new("RGB", [pixel_length, pixel_height], "white") if len(seglist) == 0: return barcode_img @@ -135,25 +142,44 @@ def create_c39(height, smallest, text): if ((i + 1) % 9) == 0: j = 1 while j <= smallest: - draw.line((current_x, marginy, current_x, marginy + height), - fill=(255, 255, 255)) + draw.line( + (current_x, marginy, current_x, marginy + height), + fill=(255, 255, 255), + ) current_x = current_x + 1 j = j + 1 i = i + 1 - ad = os.path.abspath(os.path.join(ustr(config['root_path']), u'addons')) - mod_path_list = map(lambda m: os.path.abspath(ustr(m.strip())), - config['addons_path'].split(',')) + ad = os.path.abspath(os.path.join(ustr(config["root_path"]), "addons")) + mod_path_list = list( + map( + lambda m: os.path.abspath(ustr(m.strip())), config["addons_path"].split(",") + ) + ) mod_path_list.append(ad) for mod_path in mod_path_list: - font_file = (mod_path + os.path.sep + "report_aeroo" + os.path.sep - + "barcode" + os.path.sep + "FreeMonoBold.ttf") + font_file = ( + mod_path + + os.path.sep + + "report_aeroo" + + os.path.sep + + "barcode" + + os.path.sep + + "FreeMonoBold.ttf" + ) if os.path.lexists(font_file): font = ImageFont.truetype(font_file, fontsize) - draw.text((pixel_length / 2 - len(newtext) * (fontsize / 2) / 2 - len(newtext), - height + fontsize), newtext, font=font, fill=0) + draw.text( + ( + pixel_length / 2 - len(newtext) * (fontsize / 2) / 2 - len(newtext), + height + fontsize, + ), + newtext, + font=font, + fill=0, + ) del draw diff --git a/report_aeroo/barcode/qr.py b/report_aeroo/barcode/qr.py new file mode 100644 index 0000000..dafc40e --- /dev/null +++ b/report_aeroo/barcode/qr.py @@ -0,0 +1,12 @@ +# Copyright 2022 - today Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +def make_qr_code(code): + import qrcode + + qr = qrcode.QRCode(box_size=10) + qr.add_data(code) + qr.make(fit=True) + + return qr.make_image(fill_color="black", back_color="white") diff --git a/report_aeroo/check_deps.py b/report_aeroo/check_deps.py deleted file mode 100755 index 9590a7f..0000000 --- a/report_aeroo/check_deps.py +++ /dev/null @@ -1,53 +0,0 @@ -################################################################################ -# -# Copyright (c) 2009-2014 Alistek ( http://www.alistek.com ) All Rights Reserved. -# General contacts -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This module is GPLv3 or newer and incompatible -# with OpenERP SA "AGPL + Private Use License"! -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -################################################################################ - -from odoo.osv import osv -from odoo import _ - -__all__ = [ - 'check_deps', -] - - -def check_deps(check_list): - error = False - import_errors = [] - for imp in check_list: - try: - exec(imp in {}) - except ImportError as e: - error = True - import_errors.append(str(e)) - if error: - raise osv.except_osv( - _('Warning!') + ' ' + _('Unmet python dependencies!'), '\n'.join( - import_errors) - ) diff --git a/report_aeroo/config_pixmaps/module_banner_1.png b/report_aeroo/config_pixmaps/module_banner_1.png deleted file mode 100644 index f30d523..0000000 Binary files a/report_aeroo/config_pixmaps/module_banner_1.png and /dev/null differ diff --git a/report_aeroo/controllers/__init__.py b/report_aeroo/controllers/__init__.py index 806dfff..acc6289 100644 --- a/report_aeroo/controllers/__init__.py +++ b/report_aeroo/controllers/__init__.py @@ -1,7 +1,5 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ +# Copyright 2017 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). -from . import main +from . import main, portal diff --git a/report_aeroo/controllers/main.py b/report_aeroo/controllers/main.py index 08aed9c..6cf712c 100644 --- a/report_aeroo/controllers/main.py +++ b/report_aeroo/controllers/main.py @@ -1,110 +1,87 @@ -# Copyright 2017 ACSONE SA/NV -# Copyright 2018 - Brain-tec AG - Carlos Jesus Cebrian -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -import json -from werkzeug.urls import url_decode - -from odoo import http -from odoo.http import route, request, content_disposition +# Copyright 2017 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). -from odoo.addons.web.controllers import report +import json +from odoo import http, _ +from odoo.http import request, content_disposition from odoo.tools import html_escape +from odoo.exceptions import ValidationError +MIMETYPES_MAPPING = { + "doc": "application/vnd.ms-word", + "ods": "application/vnd.oasis.opendocument.spreadsheet", + "odt": "application/vnd.oasis.opendocument.text", + "pdf": "application/pdf", + "xls": "application/vnd.ms-excel", +} -class ReportController(report.ReportController): +DEFAULT_MIMETYPE = "octet-stream" - MIMETYPES = { - 'txt': 'text/plain', - 'html': 'text/html', - 'doc': 'application/vnd.ms-word', - 'odt': 'application/vnd.oasis.opendocument.text', - 'ods': 'application/vnd.oasis.opendocument.spreadsheet', - 'pdf': 'application/pdf', - 'sxw': 'application/vnd.sun.xml.writer', - 'xls': 'application/vnd.ms-excel', - } - @route() - def report_routes(self, reportname, docids=None, converter=None, **data): - if converter != 'aeroo': - return super(ReportController, self).report_routes( - reportname=reportname, docids=docids, converter=converter, - **data) - context = dict(request.env.context) +class AerooReportController(http.Controller): - 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"]) + @http.route("/web/report_aeroo", type="http", auth="user") + def generate_aeroo_report(self, report_id, record_ids, token, debug=False): + """Generate an aeroo report. - # Aeroo Reports starts here - report_obj = request.env['ir.actions.report'] - report = report_obj._get_report_from_name(reportname) - if context.get('print_with_sudo'): - report = report.sudo() - context['report_name'] = reportname - context['return_filename'] = True - res, extension, filename = report.with_context(context)._render_aeroo( - reportname, docids, data=data) - mimetype = self.MIMETYPES.get(res, 'application/octet-stream') - httpheaders = [ - ('Content-Disposition', content_disposition(filename)), - ('Content-Type', mimetype), - ('Content-Length', len(res)) - ] - return request.make_response(res, headers=httpheaders) + Add the filename of the generated report to the response headers. + If the aeroo report is generated for multiple records, the + file name is simply {report.name}.pdf. + """ + report_id = int(report_id) + record_ids = json.loads(record_ids) - @route() - def report_download(self, data, context=None): - """This function is used by 'qwebactionmanager.js' in order to trigger - the download of a py3o/controller report. + report = request.env["ir.actions.report"].browse(report_id) + content, out_format = report._render_aeroo(record_ids, {}) - :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, type = requestcontent[0], requestcontent[1] - if type != 'aeroo': - return super(ReportController, self).report_download(data, context=context) + if len(record_ids) == 1: + record = request.env[report.model].browse(record_ids[0]) + file_name = report.get_aeroo_filename(record, out_format) + else: + file_name = "%s.%s" % (report.name, out_format) + + report_mimetype = MIMETYPES_MAPPING.get(out_format, DEFAULT_MIMETYPE) try: - reportname = url.split('/report/aeroo/')[1].split('?')[0] - docids = None - if '/' in reportname: - reportname, docids = reportname.split('/') - # on aeroo we support docids + data - data = url_decode(url.split('?')[1]).items() - # TODO deberiamos ver si podemos mejorar esto que va de la mano con algo - # que comentamos en js y no parece ser lo que hacen otros. Basicamente - # estamos obteniendo lo que mandamos en context al imprimir - # el reporte, desde la URl - context = dict(data).get('context', context) - response = self.report_routes(reportname, docids=docids, converter='aeroo', - context=context) - # if docids: - # # Generic report: - # response = self.report_routes( - # reportname, docids=docids, converter='aeroo') - # else: - # # Particular report: - # # decoding the args represented in JSON - # data = url_decode(url.split('?')[1]).items() - # response = self.report_routes( - # reportname, converter='aeroo', **dict(data)) + response = request.make_response( + content, + headers=[ + ("Content-Disposition", content_disposition(file_name)), + ("Content-Type", report_mimetype), + ("Content-Length", len(content)), + ], + cookies={"fileToken": token}, + ) + return response except Exception as e: se = http.serialize_exception(e) - error = { - 'code': 200, - 'message': "Odoo Server Error", - 'data': se - } + error = {'code': 200, 'message': "Odoo Server Error", 'data': se} return request.make_response(html_escape(json.dumps(error))) + + @staticmethod + def _get_aeroo_report_from_name(report_name): + """Get an aeroo report template from the given report name.""" + report = request.env["ir.actions.report"].search( + [ + ("report_name", "=", report_name), + ] + ) + if not report: + raise ValidationError( + _("No aeroo report found with the name {report_name}."), + report_name=report_name, + ) + + if len(report) > 1: + report_display_names = "\n".join(report.mapped("display_name")) + raise ValidationError( + _( + "Multiple aeroo reports found with the same name ({report_name}):\n\n" + "{report_display_names}" + ).format( + report_name=report_name, report_display_names=report_display_names + ) + ) + + return report diff --git a/report_aeroo/controllers/portal.py b/report_aeroo/controllers/portal.py new file mode 100644 index 0000000..7802559 --- /dev/null +++ b/report_aeroo/controllers/portal.py @@ -0,0 +1,33 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +from odoo.addons.portal.controllers.portal import CustomerPortal +from odoo.http import content_disposition, request + + +class Portal(CustomerPortal): + + def _show_aeroo_report(self, record, template, download=False): + """Show the given aeroo in the portal. + + This method is an adapted version of CustomerPortal._show_report found here: + odoo/addons/portal/controllers/portal.py + + :param record: the odoo record for which to print the report. + :param template: the aeroo report template. + :param download: whether the report is dowloaded or only shown to the screen. + """ + pdf = template.sudo()._render_aeroo( + doc_ids=[record.id], force_output_format="pdf" + )[0] + + headers = [ + ("Content-Type", "application/pdf"), + ("Content-Length", len(pdf)), + ] + + if download: + filename = template.sudo().get_aeroo_filename(record, "pdf") + headers.append(("Content-Disposition", content_disposition(filename))) + + return request.make_response(pdf, headers=headers) diff --git a/report_aeroo/data/report_aeroo_data.xml b/report_aeroo/data/report_aeroo_data.xml index 18ff0de..085a4e2 100755 --- a/report_aeroo/data/report_aeroo_data.xml +++ b/report_aeroo/data/report_aeroo_data.xml @@ -2,58 +2,46 @@ - - ODF Text Document (.odt) - oo-odt - oo-odt - - - - ODF Spreadsheet (.ods) - oo-ods - oo-ods - - - - Generic - genshi-raw - genshi-raw - - - - PDF - Portable Document Format (.pdf) - oo-pdf - oo-odt - writer_pdf_Export - - - - PDF - Portable Document Format (.pdf) - oo-pdf - oo-ods - calc_pdf_Export - - - - Microsoft Word 97/2000/XP (.doc) - oo-doc - oo-odt - MS Word 97 - - - - Microsoft Excel 97/2000/XP (.xls) - oo-xls - oo-ods - MS Excel 97 - - - - Text CSV (.csv) - oo-csv - oo-ods - Text - txt - csv (StarCalc) - + + ODF Text Document (.odt) + odt + odt + + + + ODF Spreadsheet (.ods) + ods + ods + + + + PDF - Portable Document Format (.pdf) + pdf + odt + + + + + + + + + + Microsoft Word 97/2000/XP (.doc) + doc + odt + + + + Microsoft Excel 97/2000/XP (.xls) + xls + ods + + + + Text CSV (.csv) + csv + ods + - diff --git a/report_aeroo/demo/__init__.py b/report_aeroo/demo/__init__.py index 111c09b..1e07c74 100644 --- a/report_aeroo/demo/__init__.py +++ b/report_aeroo/demo/__init__.py @@ -1,7 +1,32 @@ -################################################################################ +############################################################################## # -# This file is part of Aeroo Reports software - for license refer LICENSE file +# Copyright (c) 2008-2011 Alistek Ltd (http://www.alistek.com) All Rights Reserved. +# General contacts # -################################################################################ +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This module is GPLv3 or newer and incompatible +# with OpenERP SA "AGPL + Private Use License"! +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## -from . import parser +import parser diff --git a/report_aeroo/demo/lorem.py b/report_aeroo/demo/lorem.py new file mode 100644 index 0000000..74766ee --- /dev/null +++ b/report_aeroo/demo/lorem.py @@ -0,0 +1,1987 @@ +# -*- coding: utf8 -*- + +from optparse import OptionParser +from string import join + +shortname = "lorem" +program = "Lorem Ipsum Generator (%s)" % shortname +version = "0.6" +copyright_text = "Copyright (C) 2007 Per Erik Strandberg" +license_text = ( + """This is free software, and you are welcome to redistribute it + under the GNU General Public License + %s comes with NO WARRANTY, to the extent permitted by law.""" + % shortname +) + +# Copyright 2007 Per Erik Strandberg: per at pererikstrandberg dot se +# +# This program is free software you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + + +GPLv3 = """GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and +other kinds of works. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, the GNU General +Public License is intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. +We, the Free Software Foundation, use the GNU General Public License for most +of our software it applies also to any other work released this way by its +authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for them if you wish), that you +receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs, and that you know you can +do these things. + +To protect your rights, we need to prevent others from denying you these +rights or asking you to surrender the rights. Therefore, you have certain +responsibilities if you distribute copies of the software, or if you modify +it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must pass on to the recipients the same freedoms that you received. +You must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert +copyright on the software, and (2) offer you this License giving you legal +permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that +there is no warranty for this free software. For both users' and authors' +sake, the GPL requires that modified versions be marked as changed, so that +their problems will not be attributed erroneously to authors of previous +versions. + +Some devices are designed to deny users access to install or run modified +versions of the software inside them, although the manufacturer can do so. +This is fundamentally incompatible with the aim of protecting users' freedom +to change the software. The systematic pattern of such abuse occurs in the +area of products for individuals to use, which is precisely where it is most +unacceptable. Therefore, we have designed this version of the GPL to prohibit +the practice for those products. If such problems arise substantially in other +domains, we stand ready to extend this provision to those domains in future +versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States +should not allow patents to restrict development and use of software on +general-purpose computers, but in those that do, we wish to avoid the special +danger that patents applied to a free program could make it effectively +proprietary. To prevent this, the GPL assures that patents cannot be used to +render the program non-free. + +The precise terms and conditions for copying, distribution and modification +follow. + + +TERMS AND CONDITIONS +0. Definitions. + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this License. +Each licensee is addressed as "you". "Licensees" and "recipients" may be +individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in a +fashion requiring copyright permission, other than the making of an exact +copy. The resulting work is called a "modified version" of the earlier work or +a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on the +Program. + +To "propagate" a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under +applicable copyright law, except executing it on a computer or modifying a +private copy. Propagation includes copying, distribution (with or without +modification), making available to the public, and in some countries other +activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in +force. You may convey covered works to others for the sole purpose of +having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed section 10 makes +it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code +keep intact all notices of the absence of any warranty and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + + * a) The work must carry prominent notices stating that you +modified it, and giving a relevant date. + * b) The work must carry prominent notices stating that it is +released under this License and any conditions added under section +7. This requirement modifies the requirement in section 4 to "keep +intact all notices". + * c) You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This License +will therefore apply, along with any applicable section 7 additional +terms, to the whole of the work, and all its parts, regardless of how +they are packaged. This License gives no permission to license the +work in any other way, but it does not invalidate such permission if +you have separately received it. + * d) If the work has interactive user interfaces, each must +display Appropriate Legal Notices however, if the Program has +interactive interfaces that do not display Appropriate Legal Notices, +your work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + + * a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium customarily +used for software interchange. + * b) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a written +offer, valid for at least three years and valid for as long as you +offer spare parts or customer support for that product model, to give +anyone who possesses the object code either (1) a copy of the +Corresponding Source for all the software in the product that is +covered by this License, on a durable physical medium customarily used +for software interchange, for a price no more than your reasonable +cost of physically performing this conveying of source, or (2) access +to copy the Corresponding Source from a network server at no charge. + * c) Convey individual copies of the object code with a copy of +the written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and only +if you received the object code with such an offer, in accord with +subsection 6b. + * d) Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to copy +the object code is a network server, the Corresponding Source may be +on a different server (operated by you or a third party) that supports +equivalent copying facilities, provided you maintain clear directions +next to the object code saying where to find the Corresponding +Source. Regardless of what server hosts the Corresponding Source, you +remain obligated to ensure that it is available for as long as needed +to satisfy these requirements. + * e) Convey the object code using peer-to-peer transmission, +provided you inform other peers where the object code and +Corresponding Source of the work are being offered to the general +public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its +conditions. Additional permissions that are applicable to the entire +Program shall be treated as though they were included in this License, +to the extent that they are valid under applicable law. If additional +permissions apply only to part of the Program, that part may be used +separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + + * a) Disclaiming warranty or limiting liability differently from +the terms of sections 15 and 16 of this License or + * b) Requiring preservation of specified reasonable legal notices +or author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it or + * c) Prohibiting misrepresentation of the origin of that material, +or requiring that modified versions of such material be marked in +reasonable ways as different from the original version or + * d) Limiting the use for publicity purposes of names of licensors +or authors of the material or + * e) Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks or + * f) Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions the +above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. 9. Acceptance Not Required for Having +Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU General Public +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that numbered version or +of any later version published by the Free Software Foundation. If the +Program does not specify a version number of the GNU General Public +License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU General Public License can be used, that proxy's public +statement of acceptance of a version permanently authorizes you to +choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice +like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the +appropriate parts of the General Public License. Of course, your +program's commands might be different for a GUI interface, you would +use an "about box". + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU GPL, see . + +The GNU General Public License does not permit incorporating your +program into proprietary programs. If your program is a subroutine +library, you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +GNU Lesser General Public License instead of this License. But first, +please read .""" + + +# Thanks to +# * Barre, for the n overflow solution +# * http://lipsum.sourceforge.net/whatis.php for source lorems and inspiration + + +def get_lorem(q=0): + i = -1 + + i += 1 + if q == i: + # This text is under public domain + # Lorem ipsum + # Cicero + return """lorem ipsum dolor sit amet consetetur sadipscing elitr sed diam nonumy +eirmod tempor invidunt ut labore et dolore magna aliquyam erat sed diam +voluptua at vero eos et accusam et justo duo dolores et ea rebum stet clita +kasd gubergren no sea takimata sanctus est lorem ipsum dolor sit amet lorem +ipsum dolor sit amet consetetur sadipscing elitr sed diam nonumy eirmod +tempor invidunt ut labore et dolore magna aliquyam erat sed diam voluptua at +vero eos et accusam et justo duo dolores et ea rebum stet clita kasd +gubergren no sea takimata sanctus est lorem ipsum dolor sit amet lorem ipsum +dolor sit amet consetetur sadipscing elitr sed diam nonumy eirmod tempor +invidunt ut labore et dolore magna aliquyam erat sed diam voluptua at vero +eos et accusam et justo duo dolores et ea rebum stet clita kasd gubergren no +sea takimata sanctus est lorem ipsum dolor sit amet + +duis autem vel eum iriure dolor in hendrerit in vulputate velit esse +molestie consequat vel illum dolore eu feugiat nulla facilisis at vero eros +et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril +delenit augue duis dolore te feugait nulla facilisi lorem ipsum dolor sit +amet consectetuer adipiscing elit sed diam nonummy nibh euismod tincidunt ut +laoreet dolore magna aliquam erat volutpat + +ut wisi enim ad minim veniam quis nostrud exerci tation ullamcorper suscipit +lobortis nisl ut aliquip ex ea commodo consequat duis autem vel eum iriure +dolor in hendrerit in vulputate velit esse molestie consequat vel illum +dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio +dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te +feugait nulla facilisi + +nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet +doming id quod mazim placerat facer possim assum lorem ipsum dolor sit amet +consectetuer adipiscing elit sed diam nonummy nibh euismod tincidunt ut +laoreet dolore magna aliquam erat volutpat ut wisi enim ad minim veniam quis +nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea +commodo consequat + +duis autem vel eum iriure dolor in hendrerit in vulputate velit esse +molestie consequat vel illum dolore eu feugiat nulla facilisis + +at vero eos et accusam et justo duo dolores et ea rebum stet clita kasd +gubergren no sea takimata sanctus est lorem ipsum dolor sit amet lorem ipsum +dolor sit amet consetetur sadipscing elitr sed diam nonumy eirmod tempor +invidunt ut labore et dolore magna aliquyam erat sed diam voluptua at vero +eos et accusam et justo duo dolores et ea rebum stet clita kasd gubergren no +sea takimata sanctus est lorem ipsum dolor sit amet lorem ipsum dolor sit +amet consetetur sadipscing elitr at accusam aliquyam diam diam dolore +dolores duo eirmod eos erat et nonumy sed tempor et et invidunt justo labore +stet clita ea et gubergren kasd magna no rebum sanctus sea sed takimata ut +vero voluptua est lorem ipsum dolor sit amet lorem ipsum dolor sit amet +consetetur sadipscing elitr sed diam nonumy eirmod tempor invidunt ut labore +et dolore magna aliquyam erat + +consetetur sadipscing elitr sed diam nonumy eirmod tempor invidunt ut labore +et dolore magna aliquyam erat sed diam voluptua at vero eos et accusam et +justo duo dolores et ea rebum stet clita kasd gubergren no sea takimata +sanctus est lorem ipsum dolor sit amet lorem ipsum dolor sit amet consetetur +sadipscing elitr sed diam nonumy eirmod tempor invidunt ut labore et dolore +magna aliquyam erat sed diam voluptua at vero eos et accusam et justo duo +dolores et ea rebum stet clita kasd gubergren no sea takimata sanctus est +lorem ipsum dolor sit amet lorem ipsum dolor sit amet consetetur sadipscing +elitr sed diam nonumy eirmod tempor invidunt ut labore et dolore magna +aliquyam erat sed diam voluptua at vero eos et accusam et justo duo dolores +et ea rebum stet clita kasd gubergren no sea takimata sanctus est lorem +ipsum dolor sit amet""" + + i += 1 + if q == i: + # This text is under public domain + # Childe Harold's Pilgrimage - Canto the first (I.-X.) + # Lord Byron + return """oh thou in hellas deemed of heavenly birth +muse formed or fabled at the minstrels will +since shamed full oft by later lyres on earth +mine dares not call thee from thy sacred hill +yet there ive wandered by thy vaunted rill +yes sighed oer delphis longdeserted shrine +where save that feeble fountain all is still +nor mote my shell awake the weary nine +to grace so plain a talethis lowly lay of mine + +whilome in albions isle there dwelt a youth +who ne in virtues ways did take delight +but spent his days in riot most uncouth +and vexed with mirth the drowsy ear of night +ah me in sooth he was a shameless wight +sore given to revel and ungodly glee +few earthly things found favour in his sight +save concubines and carnal companie +and flaunting wassailers of high and low degree + +childe harold was he hight but whence his name +and lineage long it suits me not to say +suffice it that perchance they were of fame +and had been glorious in another day +but one sad losel soils a name for aye +however mighty in the olden time +nor all that heralds rake from coffined clay +nor florid prose nor honeyed lines of rhyme +can blazon evil deeds or consecrate a crime + +childe harold basked him in the noontide sun +disporting there like any other fly +nor deemed before his little day was done +one blast might chill him into misery +but long ere scarce a third of his passed by +worse than adversity the childe befell +he felt the fulness of satiety +then loathed he in his native land to dwell +which seemed to him more lone than eremites sad cell + +for he through sins long labyrinth had run +nor made atonement when he did amiss +had sighed to many though he loved but one +and that loved one alas could neer be his +ah happy she to scape from him whose kiss +had been pollution unto aught so chaste +who soon had left her charms for vulgar bliss +and spoiled her goodly lands to gild his waste +nor calm domestic peace had ever deigned to taste + +and now childe harold was sore sick at heart +and from his fellow bacchanals would flee +tis said at times the sullen tear would start +but pride congealed the drop within his ee +apart he stalked in joyless reverie +and from his native land resolved to go +and visit scorching climes beyond the sea +with pleasure drugged he almost longed for woe +and een for change of scene would seek the shades below + +the childe departed from his fathers hall +it was a vast and venerable pile +so old it seemed only not to fall +yet strength was pillared in each massy aisle +monastic dome condemned to uses vile +where superstition once had made her den +now paphian girls were known to sing and smile +and monks might deem their time was come agen +if ancient tales say true nor wrong these holy men + +yet ofttimes in his maddest mirthful mood +strange pangs would flash along childe harolds brow +as if the memory of some deadly feud +or disappointed passion lurked below +but this none knew nor haply cared to know +for his was not that open artless soul +that feels relief by bidding sorrow flow +nor sought he friend to counsel or condole +whateer this grief mote be which he could not control + +and none did love him though to hall and bower +he gathered revellers from far and near +he knew them flatterers of the festal hour +the heartless parasites of present cheer +yea none did love himnot his lemans dear +but pomp and power alone are womans care +and where these are light eros finds a feere +maidens like moths are ever caught by glare +and mammon wins his way where seraphs might despair + +childe harold had a mothernot forgot +though parting from that mother he did shun +a sister whom he loved but saw her not +before his weary pilgrimage begun +if friends he had he bade adieu to none +yet deem not thence his breast a breast of steel +ye who have known what tis to dote upon +a few dear objects will in sadness feel +such partings break the heart they fondly hope to heal""" + + i += 1 + if q == i: + # This text is under public domain + # Decameron - Novella Prima + # Giovanni Boccaccio + return """convenevole cosa e carissime donne che ciascheduna cosa la quale +l'uomo fa dallo ammirabile e santo nome di colui il quale di tutte fu facitore le dea +principio per che dovendo io al vostro novellare sí come primo dare +cominciamento intendo da una delle sue maravigliose cose incominciare accio +che quella udita la nostra speranza in lui sí come in cosa impermutabile si +fermi e sempre sia da noi il suo nome lodato manifesta cosa e che sí come le +cose temporali tutte sono transitorie e mortali cosí in sé e fuor di sé +esser piene di noia d'angoscia e di fatica e a infiniti pericoli sogiacere +alle quali senza niuno fallo né potremmo noi che viviamo mescolati in esse e +che siamo parte d'esse durare né ripararci se spezial grazia di dio forza e +avvedimento non ci prestasse la quale a noi e in noi non e da credere che +per alcun nostro merito discenda ma dalla sua propria benignita mossa e da' +prieghi di coloro impetrata che sí come noi siamo furon mortali e bene i +suoi piaceri mentre furono in vita seguendo ora con lui eterni son divenuti +e beati alli quali noi medesimi sí come a procuratori informati per +esperienza della nostra fragilita forse non audaci di porgere i prieghi +nostri nel cospetto di tanto giudice delle cose le quali a noi reputiamo +oportune gli porgiamo e ancora piú in lui verso noi di pietosa liberalita +pieno discerniamo che non potendo l'acume dell'occhio mortale nel segreto +della divina mente trapassare in alcun modo avvien forse tal volta che da +oppinione ingannati tale dinanzi alla sua maesta facciamo procuratore che da +quella con etterno essilio e iscacciato e nondimeno esso al quale niuna cosa +e occulta piú alla purita del pregator riguardando che alla sua ignoranza o +allo essilio del pregato cosí come se quegli fosse nel suo cospetto beato +essaudisce coloro che 'l priegano il che manifestamente potra apparire nella +novella la quale di raccontare intendo manifestamente dico non il giudicio +di dio ma quel degli uomini seguitando""" + + i += 1 + if q == i: + # This text is under public domain + # Faust: Der Tragödie erster Teil + # Johann Wolfgang von Goethe + return """ihr naht euch wieder schwankende gestalten +die früh sich einst dem trüben blick gezeigt +versuch ich wohl euch diesmal festzuhalten +fühl ich mein herz noch jenem wahn geneigt +ihr drängt euch zu nun gut so mögt ihr walten +wie ihr aus dunst und nebel um mich steigt +mein busen fühlt sich jugendlich erschüttert +vom zauberhauch der euren zug umwittert + +ihr bringt mit euch die bilder froher tage +und manche liebe schatten steigen auf +gleich einer alten halbverklungnen sage +kommt erste lieb und freundschaft mit herauf +der schmerz wird neu es wiederholt die klage +des lebens labyrinthisch irren lauf +und nennt die guten die um schöne stunden +vom glück getäuscht vor mir hinweggeschwunden + +sie hören nicht die folgenden gesänge +die seelen denen ich die ersten sang +zerstoben ist das freundliche gedränge +verklungen ach der erste widerklang +mein lied ertönt der unbekannten menge +ihr beifall selbst macht meinem herzen bang +und was sich sonst an meinem lied erfreuet +wenn es noch lebt irrt in der welt zerstreuet + +und mich ergreift ein längst entwöhntes sehnen +nach jenem stillen ernsten geisterreich +es schwebet nun in unbestimmten tönen +mein lispelnd lied der äolsharfe gleich +ein schauer faßt mich träne folgt den tränen +das strenge herz es fühlt sich mild und weich +was ich besitze seh ich wie im weiten +und was verschwand wird mir zu wirklichkeiten""" + + i += 1 + if q == i: + # This text is under public domain + # In der Fremde + # Heinrich Heine + return """es treibt dich fort von ort zu ort +du weißt nicht mal warum +im winde klingt ein sanftes wort +schaust dich verwundert um + +die liebe die dahinten blieb +sie ruft dich sanft zurück +o komm zurück ich hab dich lieb +du bist mein einz'ges glück + +doch weiter weiter sonder rast +du darfst nicht stillestehn +was du so sehr geliebet hast +sollst du nicht wiedersehn + +du bist ja heut so grambefangen +wie ich dich lange nicht geschaut +es perlet still von deinen wangen +und deine seufzer werden laue + +denkst du der heimat die so ferne +so nebelferne dir verschwand +gestehe mir's du wärest gerne +manchmal im teuren vaterland + +denkst du der dame die so niedlich +mit kleinem zürnen dich ergötzt +oft zürntest du dann ward sie friedlich +und immer lachtet ihr zuletzt + +denkst du der freunde die da sanken +an deine brust in großer stund' +im herzen stürmten die gedanken +jedoch verschwiegen blieb der mund + +denkst du der mutter und der schwester +mit beiden standest du ja gut +ich glaube gar es schmilzt mein bester +in deiner brust der wilde mut + +denkst du der vögel und der bäume +des schönen gartens wo du oft +geträumt der liebe junge träume +wo du gezagt wo du gehofft + +es ist schon spät die nacht ist helle +trübhell gefärbt vom feuchten schnee +ankleiden muß ich mich nun schnelle +und in gesellschaft gehn o weh""" + + i += 1 + if q == i: + # This text is under public domain + # Le Bateau Ivre + # Arthur Baudelaire + return """comme je descendais des fleuves impassibles +je ne me sentis plus guidé par les haleurs +des peaux-rouges criards les avaient pris pour cibles +les ayant cloués nus aux poteaux de couleurs + +j'étais insoucieux de tous les équipages +porteur de blés flamands ou de cotons anglais +quand avec mes haleurs ont fini ces tapages +les fleuves m'ont laissé descendre ou je voulais + +dans les clapotements furieux des marées +moi l'autre hiver plus sourd que les cerveaux d'enfants +je courus et les péninsules démarrées +n'ont pas subi tohu-bohus plus triomphants + +la tempete a béni mes éveils maritimes +plus léger qu'un bouchon j'ai dansé sur les flots +qu'on appelle rouleurs éternels de victimes +dix nuits sans regretter l'oeil niais des falots + +plus douce qu'aux enfants la chair des pommes sures +l'eau verte pénétra ma coque de sapin +et des taches de vins bleus et des vomissures +me lava dispersant gouvernail et grappin + +et des lors je me suis baigné dans le poeme +de la mer infusé d'astres et lactescent +dévorant les azurs verts ou flottaison bleme +et ravie un noyé pensif parfois descend + +ou teignant tout a coup les bleuités délires +et rythmes lents sous les rutilements du jour +plus fortes que l'alcool plus vastes que nos lyres +fermentent les rousseurs ameres de l'amour + +je sais les cieux crevant en éclairs et les trombes +et les ressacs et les courants je sais le soir +l'aube exaltée ainsi qu'un peuple de colombes +et j'ai vu quelque fois ce que l'homme a cru voir + +j'ai vu le soleil bas taché d'horreurs mystiques +illuminant de longs figements violets +pareils a des acteurs de drames tres-antiques +les flots roulant au loin leurs frissons de volets + +j'ai revé la nuit verte aux neiges éblouies +baiser montant aux yeux des mers avec lenteurs +la circulation des seves inouies +et l'éveil jaune et bleu des phosphores chanteurs + +j'ai suivi des mois pleins pareille aux vacheries +hystériques la houle a l'assaut des récifs +sans songer que les pieds lumineux des maries +pussent forcer le mufle aux océans poussifs + +j'ai heurté savez-vous d'incroyables florides +melant aux fleurs des yeux de pantheres a peaux +d'hommes des arcs-en-ciel tendus comme des brides +sous l'horizon des mers a de glauques troupeaux + +j'ai vu fermenter les marais énormes nasses +ou pourrit dans les joncs tout un léviathan +des écroulement d'eau au milieu des bonaces +et les lointains vers les gouffres cataractant + +glaciers soleils d'argent flots nacreux cieux de braises +échouages hideux au fond des golfes bruns +ou les serpents géants dévorés de punaises +choient des arbres tordus avec de noirs parfums + +j'aurais voulu montrer aux enfants ces dorades +du flot bleu ces poissons d'or ces poissons chantants +- des écumes de fleurs ont bercé mes dérades +et d'ineffables vents m'ont ailé par instants + +parfois martyr lassé des pôles et des zones +la mer dont le sanglot faisait mon roulis doux +montait vers moi ses fleurs d'ombres aux ventouses jaunes +et je restais ainsi qu'une femme a genoux + +presque île balottant sur mes bords les querelles +et les fientes d'oiseaux clabaudeurs aux yeux blonds +et je voguais lorsqu'a travers mes liens freles +des noyés descendaient dormir a reculons + +or moi bateau perdu sous les cheveux des anses +jeté par l'ouragan dans l'éther sans oiseau +moi dont les monitors et les voiliers des hanses +n'auraient pas repeché la carcasse ivre d'eau + +libre fumant monté de brumes violettes +moi qui trouais le ciel rougeoyant comme un mur +qui porte confiture exquise aux bons poetes +des lichens de soleil et des morves d'azur + +qui courais taché de lunules électriques +planche folle escorté des hippocampes noirs +quand les juillets faisaient crouler a coups de triques +les cieux ultramarins aux ardents entonnoirs + +moi qui tremblais sentant geindre a cinquante lieues +le rut des béhémots et les maelstroms épais +fileur éternel des immobilités bleues +je regrette l'europe aux anciens parapets + +j'ai vu des archipels sidéraux et des îles +dont les cieux délirants sont ouverts au vogueur +- est-ce en ces nuits sans fond que tu dors et t'exiles +million d'oiseaux d'or ô future vigueur - + +mais vrai j'ai trop pleuré les aubes sont navrantes +toute lune est atroce et tout soleil amer +l'âcre amour m'a gonflé de torpeurs enivrantes +ô que ma quille éclate ô que j'aille a la mer + +si je désire une eau d'europe c'est la flache +noire et froide ou vers le crépuscule embaumé +un enfant accroupi plein de tristesses lâche +un bateau frele comme un papillon de mai + +je ne puis plus baigné de vos langueurs ô lames +enlever leur sillage aux porteurs de cotons +ni traverser l'orgueil des drapeaux et des flammes +ni nager sous les yeux horribles des pontons""" + + i += 1 + if q == i: + # This text is under public domain + # Le Masque + # Arthur Rembaud + return """contemplons ce trésor de grâces florentines +dans l'ondulation de ce corps musculeux +l'elégance et la force abondent soeurs divines +cette femme morceau vraiment miraculeux +divinement robuste adorablement mince +est faite pour trôner sur des lits somptueux +et charmer les loisirs d'un pontife ou d'un prince + +aussi vois ce souris fin et voluptueux +ou la fatuité promene son extase +ce long regard sournois langoureux et moqueur +ce visage mignard tout encadré de gaze +dont chaque trait nous dit avec un air vainqueur +«la volupté m'appelle et l'amour me couronne» +a cet etre doué de tant de majesté +vois quel charme excitant la gentillesse donne +approchons et tournons autour de sa beauté + +ô blaspheme de l'art ô surprise fatale +la femme au corps divin promettant le bonheur +par le haut se termine en monstre bicéphale + +mais non ce n'est qu'un masque un décor suborneur +ce visage éclairé d'une exquise grimace +et regarde voici crispée atrocement +la véritable tete et la sincere face +renversée a l'abri de la face qui ment +pauvre grande beauté le magnifique fleuve +de tes pleurs aboutit dans mon coeur soucieux +ton mensonge m'enivre et mon âme s'abreuve +aux flots que la douleur fait jaillir de tes yeux + +mais pourquoi pleure-t-elle elle beauté parfaite +qui mettrait a ses pieds le genre humain vaincu +quel mal mystérieux ronge son flanc d'athlete + +elle pleure insensé parce qu'elle a vécu +et parce qu'elle vit mais ce qu'elle déplore +surtout ce qui la fait frémir jusqu'aux genoux +c'est que demain hélas il faudra vivre encore +demain apres-demain et toujours comme nous""" + + i += 1 + if q == i: + # This text is under public domain + # Nagyon fáj + # József Attila + return """kivül belõl +leselkedõ halál elõl +mint lukba megriadt egérke + +amíg hevülsz +az asszonyhoz úgy menekülsz +hogy óvjon karja öle térde + +nemcsak a lágy +meleg öl csal nemcsak a vágy +de odataszít a muszáj is + +ezért ölel +minden ami asszonyra lel +míg el nem fehérül a száj is + +kettõs teher +s kettõs kincs hogy szeretni kell +ki szeret s párra nem találhat + +oly hontalan +mint amilyen gyámoltalan +a szükségét végzõ vadállat + +nincsen egyéb +menedékünk a kés hegyét +bár anyádnak szegezd te bátor + +és lásd akadt +nõ ki érti e szavakat +de mégis ellökött magától + +nincsen helyem +így élõk közt zúg a fejem +gondom s fájdalmam kicifrázva + +mint a gyerek +kezében a csörgõ csereg +ha magára hagyottan rázza + +mit kellene +tenni érte és ellene +nem szégyellem ha kitalálom + +hisz kitaszít +a világ így is olyat akit +kábít a nap rettent az álom + +a kultúra +úgy hull le rólam mint ruha +másról a boldog szerelemben + +de az hol áll +hogy nézze mint dobál halál +s még egyedül kelljen szenvednem + +a csecsemõ +is szenvedi ha szül a nõ +páros kínt enyhíthet alázat + +de énnekem +pénzt hoz fájdalmas énekem +s hozzám szegõdik a gyalázat + +segítsetek +ti kisfiúk a szemetek +pattanjon meg ott õ ahol jár + +ártatlanok +csizmák alatt sikongjatok +és mondjátok neki¨nagyon fáj + +ti hû ebek +kerék alá kerüljetek +s ugassátok neki nagyon fáj + +nõk terhetek +viselõk elvetéljetek +és sírjátok neki nagyon fáj + +ép emberek +bukjatok öszetörjetek +s motyogjatok neki nagyon fáj + +ti férfiak +egymást megtépve nõ miatt +ne hallgassátok el nagyon fáj + +lovak bikák +kiket hogy húzzatok igát +herélnek ríjjátok nagyon fáj + +néma halak +horgot kapjatok jég alatt +és tátogjatok rá nagyon fáj + +elevenek +minden mi kíntól megremeg +égjen hol laktok kert vadon táj + +s ágya körül +üszkösen ha elszenderül +vakogjatok velem nagyon fáj + +hallja míg él +azt tagadta meg amit ér +elvonta puszta kénye végett + +kivül belõl +menekülõ élõ elõl +a legutolsó menedéket""" + + i += 1 + if q == i: + # This text is under public domain + # Ómagyar-Mária siralom + # Ismeretlen + return """volek syrolm thudothlon syrolmol sepedyk buol ozuk epedek walasth +vylagumtul sydou fyodumtul ezes urumemtuul o en eses urodum eggen yg fyodum syrou aniath +thekunched buabeleul kyniuhhad scemem kunuel arad en iunhum buol farad the +werud hullothya en iunhum olelothya vylag uilaga viragnac uiraga keseruen +kynzathul uos scegegkel werethul vh nequem en fyon ezes mezuul scegenul +scepsegud wirud hioll wyzeul syrolmom fuhazatum therthetyk kyul en iumhumnok +bel bua qui sumha nym kyul hyul wegh halal engumet eggedum illen maraggun +urodum kyth wylag felleyn o ygoz symeonnok bezzeg scouuo ere en erzem ez +buthuruth kyt niha egyre tuled ualmun de num ualallal hul yg kynzassal fyom +halallal sydou myth thez turuentelen fyom merth hol byuntelen fugwa huztuzwa +wklelue kethwe ulud keguggethuk fyomnok ne leg kegulm mogomnok owog halal +kynaal anyath ezes fyaal egembelu ullyetuk""" + + i += 1 + if q == i: + # This text is under public domain + # Robinsono Kruso (Esperanto) + # Daniel Defoe + return """mi naskigxis en jorko anglujo je marto kiu estas la +sesjarrego de la regxo karolo la unua infane mi sentadis grandan +deziron por pasigi mian vivon sur la maro kaj pliagxante la deziro +plifortigxis gxis fine mi forlasis mian lernejon kaj hejmon kaj +piede mi trovis mian vojon al hull kie mi baldaux trovis okupadon sur +sxipo + +post kiam ni velveturis kelke da tagoj okazis ventego kaj kvinanokte +la sxipo enfendigxis cxiuj al la pumpiloj rapidis la sxipon ni sentis +gxemi en cxiuj siaj tabuloj kaj gxian trabajxon ektremi de la antauxa gxis +la posta parto kaj baldaux klarigxis ke ne estas ia espero por gxi kaj +ke cxio kion ni povas fari estas savi niajn vivojn + +unue ni pafadis pafilegojn por venigi helpon kaj post kelke da +tempo sxipo kusxante ne malproksime alsendis boaton por helpi nin sed +la maro estis tro maltrankvila por gxi restadi sxipflanke tial ni +eljxetis sxnuregon kiun la boatanoj ekkaptis kaj firme fiksis kaj +tiamaniere ni cxiuj enboatigxis + +tamen vanigxis en tia maltrankvila maro por peni albordigxi la sxipon +kiu alsendis la virojn aux aluzi la remilojn de la boato kaj ni ne +povis ion fari krom gxin lasi peligxi teron + +duonhore nia sxipo trafis rifon kaj subakvigxis kaj gxin ni ne vidis +plu tre malrapide ni alproksimigxis teron kiun iafoje ni vidis kiam +ajn la boato levigxis sur la supro de ia alta ondo kaj tie ni vidis +homojn kurante amase tien kaj reen havante unu celon savi nin + +fine gxojege ni surterigxis kie bonsxance ni renkontis amikojn kiuj +donis al ni helpon por reveturi al hull kaj se tiam mi havus la +bonan sencon por iri hejmon estus pli bone por mi + +la viro kies sxipo subakvigxis diris kun grava mieno junulo ne iru +plu surmaron tiu ne estas la vivmaniero por vi kial do sinjoro +vi mem iros plu surmaron tiu estas alia afero mi estas elnutrita +por la maro sed vi ne estas vi venis sur mian sxipon por eltrovi la +staton de vivo surmara kaj vi povas diveni tion kio okazos al vi se +vi ne reiros hejmon dio ne benos vin kaj eble vi kauxzis tiun-cxi +tutan malbonon al ni + +mi ne parolis alian vorton al li kiun vojon li iris mi nek scias +nek deziris sciigxi cxar mi estis ofendita pro tiu-cxi malgxentila +parolado mi multe pensis cxu iri hejmon aux cxu iradi surmaron honto +detenis min pri iri hejmon kaj mi ne povis decidi la vivkuron kiun +mi estis ironta + +kiel estis mia sorto travive cxiam elekti la plej malbonon tiel same +mi nun faris mi havis oron en mia monujo kaj bonan vestajxon sur mia +korpo sed surmaron mi ree iris + +sed nun mi havis pli malbonan sxancon ol iam cxar kiam ni estis tre +malproksime enmaro kelke da turkoj en sxipeto plencxase alproksimigxis +al ni ni levis tiom da veloj kiom niaj velstangoj povis elporti por +ke ni forkuru de ili tamen malgraux tio ni vidis ke niaj malamikoj +pli kaj pli alproksimigxis kaj certigxis ke baldaux ili atingos nian +sxipon + +fine ili atingis nin sed ni direktis niajn pafilegojn sur ilin kio +kauxzis portempe ke ili deflanku sian vojon sed ili dauxrigis pafadon +sur ni tiel longe kiel ili estis en pafspaco proksimigxante la duan +fojon kelkaj viroj atingis la ferdekon de nia sxipo kaj ektrancxis la +velojn kaj ekfaris cxiuspecajn difektajxojn tial post kiam dek el +niaj sxipanoj kusxas mortitaj kaj la plimulto el la ceteraj havas +vundojn ni kapitulacis + +la cxefo de la turkoj prenis min kiel sian rabajxon al haveno okupita +de mauxroj li ne agis al mi tiel malbone kiel mi lin unue jugxis sed +li min laborigis kun la ceteraj de siaj sklavoj tio estis sxangxo en +mia vivo kiun mi neniam antauxvidis ho ve kiom mia koro malgxojis +pensante pri tiuj kiujn mi lasis hejme al kiuj mi ne montris tiom da +komplezemo kiom diri adiauxi kiam mi iris surmaron aux sciigi tion +kion mi intencas fari + +tamen cxio kion mi travivis tiam estas nur antauxgusto de la penadoj +kaj zorgoj kiujn de tiam estis mia sorto suferi + +unue mi pensis ke la turko kunprenos min kun si kiam li ree iros +surmaron kaj ke mi iel povos liberigxi sed la espero nelonge dauxris +cxar tiatempe li lasis min surtere por prizorgi liajn rikoltojn +tiamaniere mi vivis du jarojn tamen la turko konante kaj vidante min +plu min pli kaj pli liberigis li unufoje aux dufoje cxiusemajne +veturis en sia boato por kapti iajn platfisxojn kaj iafoje li +kunprenis min kaj knabon kun si cxar ni estas rapidaj cxe tia sporto +kaj tial li pli kaj pli sxatis min + +unu tagon la turko elsendis min viron kaj knabon boate por kapti +kelke da fisxoj surmare okazas tia densa nebulo ke dekduhore ni ne +povas vidi la teron kvankam ni ne estas pli ol duonmejlon 00 +metrojn de la terbordo kaj morgauxtage kiam la suno levigxis nia +boato estas enmaro almenaux dek mejlojn kilometrojn de la +terbordo la vento vigle blovis kaj ni cxiuj tre bezonis nutrajxon sed +fine per la helpo de remiloj kaj veloj ni sendangxere reatingis la +terbordon + +kiam la turko sciigxis kiamaniere ni vojperdis li diris ke de nun +kiam li velveturos li prenos boaton kiu enhavos cxion kion ni +bezonus se ni longatempe estus detenataj surmare tial li farigis +grandan kajuton en la longboato de sia sxipo kiel ankaux cxambron por ni +sklavoj unu tagon li min sendis por ke mi ordigu la boaton pro tio +ke li havas du amikojn kiuj intencas veturi kun li por fisxkapti sed +kiam la tempo alvenis ili ne veturas tial li sendis min viron kaj +knabon -- kies nomo estas zuro -- por kapti kelke da fisxoj por la +gastoj kiuj estas vespermangxontaj kun li + +subite eniris en mian kapon la ideo ke nun estas bona okazo boate +forkuri kaj liberigxi tial mi tuj prenis tiom da nutrajxo kiom mi +povas havigi kaj mi diris al la viro ke estus tro malrespekte +mangxante la panon metitan en la boaton por la turko li diris ke li +pensas tiel same tial li alportis sakon da rizo kaj kelke da ruskoj +kukoj + +dum la viro estis surtere mi provizis iom da vino pecegon da vakso +segilon hakilon fosilon iom da sxnurego kaj cxiuspecajn objektojn +kiuj eble estos utilaj al ni mi sciis kie trovigxas la vinkesto de la +turko kaj mi gxin metis surboaton dum la viro estas surtere per alia +ruzo mi havigis cxion kion mi bezonis mi diris al la knabo la +pafiloj de la turko estas en la boato sed ne trovigxas ia pafajxo cxu +vi pensas ke vi povas havigi iom da gxi vi scias kie gxi estas +konservata kaj eble ni volos pafi birdon aux du li do alportis kesto +kaj saketon kiuj enhavas cxion kion ni eble bezonas por la pafiloj +tiujn-cxi mi metis surboaton kaj poste velveturis por fisxkapti + +la vento blovis de la nordo aux nordokcidento tia vento estis malbona +por mi cxar se gxi estus de la sudo mi estus povinta velveturi al la +terbordo de hispanujo tamen de kiu ajn loko la vento blovos mi +estis decidinta forkuri kaj lasi la ceterajn al ilia sorto mi do +mallevis miajn hokfadenojn kvazaux fisxkapti sed mi zorgis ke mi havu +malbonan sukceson kaj kiam la fisxoj mordis mi ilin ne eltiris cxar +mi deziris ke la mauxro ilin ne vidu mi diris al li tiu-cxi loko +estas nebona ni ne kaptos fisxojn tie-cxi ni devas iom antauxen iri +nu la mauxro pensis ke tion fari ne estos malbone li levis la +velojn kaj cxar la direktilo estis en miaj manoj mi elsendis la +boaton unu mejlon aux plu enmaron kaj poste gxin haltigis kvazaux mi +intencas fisxkapti + +nun mi pripensis tiu-cxi estas mia okazo liberigxi tial mi transdonis +la direktilon al la knabo kaj tiam ekprenis la mauxron cxirkaux la +talio kaj eljxetis lin el la boato + +malsupren li falis sed baldaux reaperis por ke li povis nagxi kvazaux +anaso li diris ke li volonte irus cxirkaux la mondo kun mi se mi +enprenus lin + +iom timante ke li surrampos la boatflankon kaj reenigxos perforte mi +direktis mian pafilon sur lin kaj diris vi facile povas nagxi +alteron se vi tion deziras tial rapidigxu tien plie se vi reen +alproksimigxos la boaton vi ricevos kuglon tra la kapo cxar mi de nun +intencas esti libera viro + +tiam li eknagxis kaj sendube sendangxere atingis la terbordon cxar la +maro estis tre trankvila + +unue mi intencis kunpreni la mauxron kun mi kaj nagxigi zuron alteron +sed la mauxro ne estis viro pri kiu mi povis konfidi + +post kiam li forigxis mi diris al zuro se vi jxuros ke vi estos +fidela al mi vi iam farigxos grava viro se vi ne jxuros mi certe +ankaux vin eljxetos el la boato + +la knabo tiel dolcxe ridetis kiam li jxuris resti fidela al mi ke mi +lin ne povis dubi en mia koro + +dum ankoraux ni povis vidi la mauxron survoje alteren ni antauxen iris +enmaron por ke li kaj tiuj kiuj nin vidis de la terbordo kredu ke +ni iros al la influejo de la markolo cxar neniu velveturis al la suda +marbordo cxar tie logxas gento da homoj kiuj laux sciigoj mortigas kaj +mangxas siajn malamikojn + +tiam mi direktis mian veturadon oriente por ke ni lauxlongiru la +marbordon kaj havante favoron venton kaj trankvilan maron ni +morgauxtagmeze estis malapud kaj preter la povo de la turko + +ankoraux mi timis ke mi estus kaptota de la mauxroj tial mi ne volis +iri surteron tage duonlume ni direktis nian boaton alteren kaj +atingis la enfluejon riveretan de kiu mi pensis ni povos nagxi +surteron kaj tiam rigardi la cxirkauxajxojn sed kiam malheligxis la +lumo ni auxdis strangajn sonojn bojojn kriegojn gruntojn +blekadojn la malfelicxa knabo diris ke li ne kuragxas iri surteron +antaux la tagigxo nu mi diris tiuokaze ni atendu sed tage +povas vidi nin la homoj kiuj eble nin pli malhelpos ol sovagxaj +bestoj tiam ni pafilos ilin ridante diris zuro kaj forkurigu +ilin + +mi gxojis vidi ke la knabo montras tiom da gajeco kaj mi donis al li +iom da pano kaj rizo tiunokte ni silente ripozis sed ne longe +dormis cxar post kelke da horoj iaj grandegaj bestoj malsuprenvenis +al la maro por sin bani la malfelicxa knabo ektremis de kapo al +piedoj pro la vidajxo unu el tiuj bestoj alproksimigxis nian boaton +kaj kvankam estis tro mallume por gxin bone vidi ni auxdis gxin blovi +kaj sciis pro gxia bruego ke gxi certe estas granda fine la bruto +tiom alproksimigxis la boaton kiom la longeco de du remiloj tial mi +pafis sur gxin kaj gxi nagxis alteren + +la blekegoj kaj kriegoj kiujn faris bestoj kaj birdoj pro la bruo de +mia pafilo sxajne montris ke ni faris malbonan elekton por surterejo +sed vole ne vole ni devis iri surtere por sercxi fresxan fonton por +ke ni povu plenigi niajn barelojn zuro diris ke li eltrovus cxu la +fontaj akvoj tauxgas por trinki se mi permesus al li preni unu el la +botelegoj kaj ke li gxin reportos plenigitan se la akvo estas bona +kial vi volas iri mi diris kial mi ne estas ironta vi povas +resti en la boato kontrauxe zuro diris se la sovagxuloj venos ili +min mangxu sed vi forkuru mi devis ami la junulon pro la afabla +parolado nu mi diris ni ambaux iros kaj se la sovagxuloj venos +ni ilin mortigu ja ili ne mangxos aux vin aux min + +mi donis al zuro iom da rumo el la kesto de la turko por reforti lin +kaj ni iris surteron la knabo ekiris kun sia pafilo mejlon de la +loko kie ni surteriris kaj li revenis kun leporo kiun li mortpafis +kaj kiun ni gxoje kuiris kaj mangxis laux la bona novajxo kiun li +raportis li eltrovis fonton kaj ne vidis sovagxulojn + +mi divenis ke la promontoro de la verdaj insuloj ne estas +malproksime cxar mi vidis la supron de la granda pinto kiun kiel mi +sciis estas apud ili mia sola espero estis ke lauxlongirante la +terbordon ni trovos sxipon kiu ensxipigos nin kaj tiam kaj ne antaux +tiam mi sentos kvazaux libera viro unuvorte mi konfidis mian sorton +al la sxanco aux renkonti ian sxipon aux morti + +surteron ni ekvidis iujn homojn kiuj staras kaj rigardas nin ili +estis nigraj kaj ne portis vestajxon mi estus irinta surteron al ili +sed zuro -- kiu sciis plej bone -- diris ne vi iru ne vi iru tial +mi direktis la boaton lauxteron por ke mi povu paroli kun ili kaj ili +longaspace iradis laux ni mi ekvidis ke unu havas lancon en mano + +mi faris signojn ke ili alportu iom da nutrajxo al mi kaj ili +siaparte faris signojn ke mi haltu mian boaton tial mi demetis la +supran parton de mia velo kaj haltis tiam du el ili ekforkuris kaj +duonhore revenis kun iom da sekigxita viando kaj ia greno kiu kreskas +en tiu parto de la mondo tion-cxi ni deziregis sed ne sciis kiel +havigi gxin cxar ni ne kuragxis iri surteron al ili nek ili kuragxis +alproksimigxi al ni + +fine ili eltrovis peron sendangxeran por ni cxiuj alportante la +nutrajxon al la marbordo ili gxin demetis kaj tre fortirigis si mem dum +ni gxin prenis ni faris signojn por montri nian dankon ne havante ion +alian kion ni povas doni al ili sed bonsxance ni baldaux kaptis +grandan donacon por ili cxar du sovagxaj bestoj de la sama speco pri +kiu mi jam priparolis venis plencxase de la montetoj al la maro + +ili nagxis kvazaux ili venis por sportigi cxiuj forkuris de ili krom +tiu kiu portas la lancon unu el tiuj bestoj alproksimigxis nian +boaton tial mi gxin atendis kun mia pafilo kaj tuj kiam gxi estis en +pafspaco mi gxin pafis tra la kapo dufoje gxi subakvigxis kaj dufoje gxi +suprenlevigxis kaj poste gxi nagxis alteren kaj falis senviva la viroj +tiom timis pro la pafilbruo kiom ili antauxe timis je la vidajxo de la +bestoj sed kiam mi faris signojn por ke ili venu al la marbordo ili +tuj venis + +ili rapidis al sia rabajxo kaj tordante cxirkaux gxi sxnuregon ili gxin +sendangxere eltiris surteron + +ni nun lasis niajn sovagxulojn kaj iradis dekdu tagojn plu la terbordo +antaux ni etendis sin kvar aux kvin mejlojn aux kilometrojn +bekforme kaj ni devis veturi iom de la terbordo por atingi tiun +terpinton tiel ke ni portempe ne vidis teron + +mi konfidis la direktilon al zuro kaj sidigxis por pripensi tion kion +estos plej bone nun fari kiam subite mi auxdis ke la knabo krias +sxipon kun velo sxipon kun velo li ne montris multe da gxojo je la +vidajxo opiniante ke la sxipo venis por repreni lin sed mi bone +scias laux la sxajno ke gxi ne estas iu el la sxipoj de la turko + +mi levis kiel eble plej multe da veloj por renkonti la sxipon gxiavoje +kaj ordonis al zuro ke li ekpafu pafilon cxar mi esperis ke se tiuj +kiuj estas sur la ferdeko ne povus auxdi la sonon ili vidus la +fumigadon ili ja gxin vidis kaj tuj demetis siajn velojn por ke ni +povu atingi ilin kaj trihore ni estis cxe la sxipflanko la viroj +parolis kun ni per la franca lingvo sed ni ne povis kompreni tion +kion ili diras fine skoto sursxipe diris per mia lingvo kiu vi +estas de kien vi venas mi diris al li iomvorte kiel mi liberigxis +de la mauxroj + +tiam la sxipestro invitis min veni sxipbordon kaj ensxipis min zuron +kaj cxiujn miajn posedajxojn mi diris al li ke li havu cxion kion mi +havas sed li respondis vi estas rericevonta viajn posedajxojn post +kiam ni atingos teron cxar mi por vi nur faris tion kion por mi vi +farus samstate + +li pagis al mi multan monon por mia boato kaj diris ke mi ricevos +egalan monon por zuro se mi lin fordonus sed mi diris al li ke +liberigxinte kun helpo de la knabo mi lin ne volas vendi li diris ke +estas juste kaj prave por mi tiel senti sed se mi decidus fordoni +zuron li estus liberigota dujare tial cxar la sklavo deziris iri mi +nenial diris ne trisemajne mi alvenis al cxiuj sanktuloj golfeto kaj +nun mi estis liberulo + +mi ricevis multan monon por cxiujn miaj posedajxoj kaj kun gxi mi iris +surteron sed mi tute ne sciis kion nun fari fine mi renkontis +viron kies stato estas laux la mia kaj ni ambaux akiris pecon da tero +por gxin prilabori mia farmilaro laux la lia estis malgranda sed ni +produktigis la farmojn suficxe por subteni nin sed ne plu ni bezonis +helpon kaj nun mi eksentis ke mi eraris ellasante la knabon + +mi tute ne sxatis tiun manieron de vivo kion mi pensis cxu mi venis +tian longan vojon por fari tion kion mi lauxbone povus fari hejme kaj +kun miaj parencoj cxirkaux mi kaj pligrandigxis mia malgxojo cxar la +bonamiko kiu min alsxipis tien-cxi intencas nune lasi tiun-cxi +terbordon + +kiam mi estis knabo kaj ekiris surmaron mi metis en la manojn de mia +onklino iom da mono pri kiu mia bonamiko diris ke mi bone farus se +mi gxin elspezus pro mia bieno tial post kiam li revenis hejmon li +alsendis iom da gxi kontante kaj la restajxon kiel tukoj sxtofoj +lanajxoj kaj similajxoj kiujn li acxetis mia onklino tiam metis en +liajn manojn iom da livroj kiel donaco al li por montri sian +dankecon pro cxio kion li faris por mi kaj per tiu mono li afable +acxetis sklavon por mi intertempe mi jam acxetis sklavon tial mi nun +havas du kaj cxio prosperis dum la sekvanta jaro""" + + i += 1 + if q == i: + # This text is under public domain + # The Raven + # Edgar Allan Poe + return """once upon a midnight dreary while i pondered weak and weary +over many a quaint and curious volume of forgotten lore +while i nodded nearly napping suddenly there came a tapping +as of some one gently rapping rapping at my chamber door +tis some visiter i muttered tapping at my chamber door +only this and nothing more + +ah distinctly i remember it was in the bleak december +and each separate dying ember wrought its ghost upon the floor +eagerly i wished the morrow vainly i had sought to borrow +from my books surcease of sorrowsorrow for the lost lenore +for the rare and radiant maiden whom the angels name lenore +nameless here for evermore + +and the silken sad uncertain rustling of each purple curtain +thrilled mefilled me with fantastic terrors never felt before +so that now to still the beating of my heart i stood repeating +tis some visiter entreating entrance at my chamber door +some late visiter entreating entrance at my chamber door +this it is and nothing more + +presently my soul grew stronger hesitating then no longer +sir said i or madam truly your forgiveness i implore +but the fact is i was napping and so gently you came rapping +and so faintly you came tapping tapping at my chamber door +that i scarce was sure i heard youhere i opened wide the door +darkness there and nothing more + +deep into that darkness peering long i stood there wondering fearing +doubting dreaming dreams no mortals ever dared to dream before +but the silence was unbroken and the stillness gave no token +and the only word there spoken was the whispered word lenore +this i whispered and an echo murmured back the word lenore +merely this and nothing more + +back into the chamber turning all my soul within me burning +soon again i heard a tapping something louder than before +surely said i surely that is something at my window lattice +let me see then what thereat is and this mystery explore +let my heart be still a moment and this mystery explore +tis the wind and nothing more + +open here i flung the shutter when with many a flirt and flutter +in there stepped a stately raven of the saintly days of yore +not the least obeisance made he not a minute stopped or stayed he +but with mien of lord or lady perched above my chamber door +perched upon a bust of pallas just above my chamber door +perched and sat and nothing more + +then the ebony bird beguiling my sad fancy into smiling +by the grave and stern decorum of the countenance it wore +though thy crest be shorn and shaven thou i said art sure no craven +ghastly grim and ancient raven wandering from the nightly shore +tell me what thy lordly name is on the nights plutonian shore +quoth the raven nevermore + +much i marvelled this ungainly fowl to hear discourse so plainly +though its answer little meaninglittle relevancy bore +for we cannot help agreeing that no living human being +ever yet was blessed with seeing bird above his chamber door +bird or beast upon the sculptured bust above his chamber door +with such name as nevermore + +but the raven sitting lonely on that placid bust spoke only +that one word as if its soul in that one word he did outpour +nothing farther then he uttered not a feather then he fluttered +till i scarcely more than muttered other friends have flown before +on the morrow he will leave me as my hopes have flown before +then the bird said nevermore + +startled at the stillness broken by reply so aptly spoken +doubtless said i what it utters is its only stock and store +caught from some unhappy master whom unmerciful disaster +followed fast and followed faster till his songs one burden bore +till the dirges of his hope that melancholy burden bore +of nevernevermore + +but the raven still beguiling all my sad soul into smiling +straight i wheeled a cushioned seat in front of bird and bust and door +then upon the velvet sinking i betook myself to linking +fancy unto fancy thinking what this ominous bird of yore +what this grim ungainly ghastly gaunt and ominous bird of yore +meant in croaking nevermore + +this i sat engaged in guessing but no syllable expressing +to the fowl whose fiery eyes now burned into my bosoms core +this and more i sat divining with my head at ease reclining +on the cushions velvet lining that the lamplight gloated oer +but whose velvet violet lining with the lamplight gloating oer +she shall press ah nevermore + +then methought the air grew denser perfumed from an unseen censer +swung by seraphim whose footfalls tinkled on the tufted floor +wretch i cried thy god hath lent theeby these angels he hath sent thee +respiterespite and nepenthe from thy memories of lenore +quaff oh quaff this kind nepenthe and forget this lost lenore +quoth the raven nevermore + +prophet said i thing of evilprophet still if bird or devil +whether tempter sent or whether tempest tossed thee here ashore +desolate yet all undaunted on this desert land enchanted +on this home by horror hauntedtell me truly i implore +is thereis there balm in gileadtell metell me i implore +quoth the raven nevermore + +prophet said i thing of evilprophet still if bird or devil +by that heaven that bends above usby that god we both adore +tell this soul with sorrow laden if within the distant aidenn +it shall clasp a sainted maiden whom the angels name lenore +clasp a rare and radiant maiden whom the angels name lenore +quoth the raven nevermore + +be that our sign of parting bird or fiend i shrieked upstarting +get thee back into the tempest and the nights plutonian shore +leave no black plume as a token of that lie thy soul has spoken +leave my loneliness unbrokenquit the bust above my door +take thy beak from out my heart and take thy form from off my door +quoth the raven nevermore + +and the raven never flitting still is sitting still is sitting +on the pallid bust of pallas just above my chamber door +and his eyes have all the seeming of a demons that is dreaming +and the lamplight oer him streaming throws his shadows on the floor +and my soul from out that shadow that lies floating on the floor +shall be lifted nevermore""" + + i += 1 + if q == i: + # This text is under public domain + # Tierra y Luna + # Federico García Lorca + return """me quedo con el transparente hombrecillo +que come los huevos de la golondrina +me quedo con el nino desnudo +que pisotean los borrachos de brooklyn +con las criaturas mudas que pasan bajo los arcos +con el arroyo de venas ansioso de abrir sus manecitas + +tierra tan sólo tierra +tierra para los manteles estremecidos +para la pupila viciosa de nube +para las heridas recientes y el húmedo pensamiento +tierra para todo lo que huye de la tierra + +no es la ceniza en vilo de las cosas quemadas +ni los muertos que mueven sus lenguas bajo los árboles +es la tierra desnuda que bala por el cielo +y deja atrás los grupos ligeros de ballenas + +es la tierra alegrísima imperturbable nadadora +la que yo encuentro en el nino y en las criaturas que pasan los arcos +viva la tierra de mi pulso y del baile de los helechos +que deja a veces por el aire un duro perfil de faraón + +me quedo con la mujer fría +donde se queman los musgos inocentes +me quedo con los borrachos de brooklyn +que pisan al nino desnudo +me quedo con los signos desgarrados +de la lenta comida de los osos + +pero entonces baja la luna despenada por las escaleras +poniendo las ciudades de hule celeste y talco sensitivo +llenando los pies de mármol la llanura sin recodos +y olvidando bajo las sillas diminutas carcajadas de algodón + +oh diana diana diana vacía +convexa resonancia donde la abeja se vuelve loca +mi amor de paso tránsito larga muerte gustada +nunca la piel ilesa de tu desnudo huido + +es tierra dios mío tierra lo que vengo buscando +embozo de horizonte latido y sepultura +es dolor que se acaba y amor que se consume +torre de sangre abierta con las manos quemadas + +pero la luna subía y bajaba las escaleras +repartiendo lentejas desangradas en los ojos +dando escobazos de plata a los ninos de los muelles +y borrando mi apariencia por el término del aire""" + + i += 1 + if q == i: + # This text is under public domain + # Hemsöborna + # August Strindberg (1912-1921) + return """han kom som ett yrväder en aprilafton och hade +ett höganäskrus i en svångrem om halsen clara +och lotten voro inne med skötekan att hämta +honom på dalarö brygga men det dröjde evigheter +innan de kommo i båt de skulle till handelsman +och ha en tunna tjära och på abeteket och hämta +gråsalva åt grisen och så skulle de på posten och +få ett frimärke och så skulle de ner till fia lövström +i kroken och låna tuppen mot ett halvpund +småtärna till notbygget och sist hade de hamnat på +gästgivaregården där carlsson bjudit på kaffe med +dopp och så kommo de äntligen i båt men carlsson +ville styra och det kunde han inte för han hade +aldrig sett en råseglare förr och därför skrek han +att de skulle hissa focken som inte fanns + +och på tullbryggan stodo lotsar och vaktmästare +och grinade åt manövern när ekan gick över stag +och länsade ner åt saltsäcken + +hörru du har hål i båten skrek en lotslärling +genom vinden stopp till stopp till och medan +carlsson tittade efter hålen hade clara knuffat +undan honom och tagit roret och med årorna lyckades +lotten få ekan opp i vinden igen så att nu strök +det ner åt aspösund med god gång + +carlsson var en liten fyrkantig värmländing med +blå ögon och näsa krokig som en syskonhake livlig +lekfull och nyfiken var han men sjöaffärerna förstod +han inte alls och han var också kallad ut till +hemsö för att ta hand om åker och kreatur som +ingen annan ville ta befattning med sedan gubben +flod gått ur livet och änkan satt ensam vid gården""" + + # if we got this far - let's return this crap (instead of throwing) + return """fubar1 fubar2 fubar3 +foobar1 foobar2 foobar3 foobar4 foobar5 +baroba1 + +raboof1""" + + +def do_parse(): + + usage = """Usage: lorem [-n|-l|-c] N [-q M]? + where + N and M are integers + +Note: If -n -l and/or -c are combined -l has priority over -c that has + priority over -n. + +Examples: lorem -n 10 + Get 10 words of lorem. + + lorem -l 5 + Get 5 lines of lorem + + lorem -c 79 + Get 79 characters of lorem + + lorem -l 5 -q 11 + Get 5 lines of lorem from quote 11 + +License: Copyright (C) 2007 Per Erik Strandberg + This program comes with ABSOLUTELY NO WARRANTY. + + This is free software, and you are welcome to redistribute it + under the GNU GENERAL PUBLIC LICENSE Version 3. +""" + + versioninfo = "%s version %s\n%s\n\n%s" % ( + program, + version, + copyright_text, + license_text, + ) + + parser = OptionParser(usage=usage, version=versioninfo) + + parser.add_option("-n", dest="n", help="Number of Words", default=0) + parser.add_option("-l", dest="l", help="Number of Lines", default=0) + parser.add_option("-c", dest="c", help="Number of Chars", default=0) + parser.add_option("-q", dest="q", help="Quote index (0+)", default=0) + (options, args) = parser.parse_args() + + try: + options.n = int(options.n) + options.l = int(options.l) # noqa E741 + options.c = int(options.c) + options.q = int(options.q) + except BaseException: + parser.error("At least one of the arguments is not an integer.") + + for m in [options.n, options.l, options.c, options.q]: + if m < 0: + parser.error("Negative argument.") + + if options.n == 0 and options.l == 0 and options.c == 0: # noqa E741 + parser.error("No arguments, try 'lorem -n 25'.") + + return (options, args) + + +def do_lorem(n=0, line=0, c=0, q=0): + + lorem = get_lorem(q) + + res = "" + if line != 0: + # Do lines + lorem = lorem.replace("\n\n", "\n") + lines = lorem.split("\n") + + while line: + if line > len(lines): + l1 = len(lines) + line -= l1 + else: + l1 = line + line = 0 + + for i in range(l1): + res += lines[i].strip() + "\n" + + elif c != 0: + # Do chars + chars = lorem + chars = chars.replace("\n", " ") + chars = chars.replace(" ", " ") + + outchars = [] + + while c: + if c > len(chars): + c1 = len(chars) + c -= c1 + outchars.append(chars) + else: + c1 = c + c = 0 + outchars.append(chars[0:c1]) + + res += join(outchars, "") + "\n" + + else: + # Do words + words = lorem.replace("\n\n", "\n") + words = words.replace("\n", " ") + words = words.replace(" ", " ") + words = words.split(" ") + + while n: + if n > len(words): + n1 = len(words) + n -= n1 + else: + n1 = n + n = 0 + + for i in range(n1): + res += words[i] + " " + + return res diff --git a/report_aeroo/demo/parser.py b/report_aeroo/demo/parser.py deleted file mode 100644 index 737a00f..0000000 --- a/report_aeroo/demo/parser.py +++ /dev/null @@ -1,19 +0,0 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -from odoo import api, models - - -class Parser(models.AbstractModel): - _inherit = 'report.report_aeroo.abstract' - - _name = 'report.sample_report' - _description = 'report.sample_report' - - @api.model - def aeroo_report(self, docids, data): - self = self.with_context(test_parser='parser works ok!') - return super(Parser, self).aeroo_report(docids, data) diff --git a/report_aeroo/demo/report_sample.xml b/report_aeroo/demo/report_sample.xml index 2189d88..5a26f10 100644 --- a/report_aeroo/demo/report_sample.xml +++ b/report_aeroo/demo/report_sample.xml @@ -1,18 +1,44 @@ - + + - - Sample Report - ir.actions.report - res.partner - sample_report - aeroo - oo-odt - - report.sample_report - file - report_aeroo/demo/template.odt - + + Sample Report + ir.actions.report + res.partner + sample_report + aeroo + odt + report_aeroo/demo/template.odt + file + o.lang + + + Sample Report (from list view) + ir.actions.report + res.partner + sample_report_multi + aeroo + odt + report_aeroo/demo/template_multi.odt + file + + + + + Sample Spreadsheet + ir.actions.report + res.partner + sample_ods + aeroo + ods + + report_aeroo/demo/template.ods + file + + + + diff --git a/report_aeroo/demo/template.ods b/report_aeroo/demo/template.ods new file mode 100644 index 0000000..ceba4a6 Binary files /dev/null and b/report_aeroo/demo/template.ods differ diff --git a/report_aeroo/demo/template.odt b/report_aeroo/demo/template.odt index 19eb4d3..55d3a9e 100644 Binary files a/report_aeroo/demo/template.odt and b/report_aeroo/demo/template.odt differ diff --git a/report_aeroo/demo/template_multi.odt b/report_aeroo/demo/template_multi.odt new file mode 100644 index 0000000..f33ece1 Binary files /dev/null and b/report_aeroo/demo/template_multi.odt differ diff --git a/report_aeroo/docs_client_lib.py b/report_aeroo/docs_client_lib.py deleted file mode 100755 index af41861..0000000 --- a/report_aeroo/docs_client_lib.py +++ /dev/null @@ -1,143 +0,0 @@ -################################################################################ -# -# Copyright (c) 2009-2014 Alistek ( http://www.alistek.com ) All Rights Reserved. -# General contacts -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This module is GPLv3 or newer and incompatible -# with OpenERP SA "AGPL + Private Use License"! -# -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -################################################################################ - -# Warning: this software is in alpha stage, all interfaces are subject to change -# without prior notice - -import json -import os -import requests -from base64 import b64encode, b64decode -CHUNK_LENGTH = 32 * 1024 -HEADERS = {'content-type': 'application/json'} -DOCSHOST = 'localhost' -DOCSPORT = 8989 - - -class ServerException(Exception): - pass - - -class DOCSConnection(): - - def __init__(self, host=DOCSHOST, port=DOCSPORT, username=None, password=None): - assert isinstance(host, str) and isinstance(port, (str, int)) - self.host = host - if isinstance(port, int): - port = str(port) - self.port = port - self.url = 'http://%s:%s/' % (self.host, self.port) - self.username = username - self.password = password - - def _initpack(self, method): - return { - "jsonrpc": "2.0", - "method": method, - "id": 1, - "params": {'username': self.username, 'password': self.password}, - } - - def test(self, ctd=None): - # ctd stands for crash test dummy file - path = ctd or os.path.join('report_aeroo', 'test_temp.odt') - with open(path, "r") as testfile: - data = testfile.read() - identifier = self.upload(data) - if not identifier: - raise ServerException('Upload failded, no upload identifier ' - 'returned from server.') - conv_result = self.convert(identifier) - if not conv_result: - raise ServerException("Document conversion error.") - join_result = self.join([identifier, identifier]) - if not join_result: - raise ServerException("Document join error.") - return True - - def upload(self, data, filename=False): - assert len(data) > 0 - data = b64encode(data).decode('utf8') - identifier = False - data_size = len(data) - upload_complete = False - for i in range(0, data_size, CHUNK_LENGTH): - chunk = data[i: i + CHUNK_LENGTH] - is_last = (i + CHUNK_LENGTH) >= data_size - payload = self._initpack('upload') - payload['params'].update( - {'data': chunk, 'identifier': identifier, 'is_last': is_last} - ) - response = requests.post( - self.url, data=json.dumps(payload), headers=HEADERS - ).json() - self._checkerror(response) - if 'result' not in response: - break - elif 'identifier' not in response['result']: - break - elif is_last: - upload_complete = True - identifier = identifier or response['result']['identifier'] - return identifier or False - - def convert(self, data=False, identifier=False, in_mime=False, out_mime=False): - payload = self._initpack('convert') - if identifier: - payload['params'].update({'identifier': identifier}) - elif data: - payload['params'].update({'data': b64encode(data).decode('utf8')}) - else: - raise ServerException('Data or identifier must be set') - if in_mime: - payload['params'].update({'in_mime': in_mime}) - if out_mime: - payload['params'].update({'out_mime': out_mime}) - response = requests.post( - self.url, data=json.dumps(payload), headers=HEADERS).json() - self._checkerror(response) - return 'result' in response and b64decode(response['result']) or False - - def join(self, idents, in_mime=False, out_mime=False): - payload = self._initpack('join') - payload['params'].update({'idents': idents}) - if in_mime: - payload['params'].update({'in_mime': in_mime}) - if out_mime: - payload['params'].update({'out_mime': out_mime}) - response = requests.post( - self.url, data=json.dumps(payload), headers=HEADERS).json() - self._checkerror(response) - return 'result' in response and b64decode(response['result']) or False - - def _checkerror(self, response): - if 'error' in response: - raise ServerException(response['error']['message']) diff --git a/report_aeroo/exceptions.py b/report_aeroo/exceptions.py deleted file mode 100644 index e9e1d50..0000000 --- a/report_aeroo/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -from odoo.exceptions import except_orm - - -class ConnectionError(except_orm): - """ Basic connection error. - Example: When try to connect Aeroo DOCS and connection fails.""" - def __init__(self, msg): - super(ConnectionError, self).__init__(msg) - - -class ProgrammingError(except_orm): - """ Basic programming error. - Example: When python code can not be compiled due to some error.""" - def __init__(self, msg): - super(ProgrammingError, self).__init__(msg) diff --git a/report_aeroo/extra_functions.py b/report_aeroo/extra_functions.py new file mode 100755 index 0000000..41b5640 --- /dev/null +++ b/report_aeroo/extra_functions.py @@ -0,0 +1,344 @@ +# Copyright 2008-2014 Alistek +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +import babel.numbers +import babel.dates +import base64 +import itertools +import logging +from babel.core import localedata +from datetime import datetime, date, timedelta +from html2text import html2text +from io import BytesIO +from PIL import Image + +from odoo import fields, _ +from odoo.exceptions import ValidationError + +from .barcode.code128 import get_code +from .barcode.code39 import create_c39 +from .barcode.EANBarCode import EanBarCode +from .barcode.qr import make_qr_code + +logger = logging.getLogger(__name__) + + +class AerooFunctionRegistry(object): + """Class responsible for registering functions usable in aeroo templates.""" + + def __init__(self): + self._functions = {} + + def register(self, func_name, func): + """Register a function inside the registry. + + :param func_name: the name of the function + :param func: the function + """ + if func_name in self._functions: + raise RuntimeError( + "A function named {func_name} is already registered " + "in the Aeroo registry.".format(func_name=func_name) + ) + self._functions[func_name] = func + + def get_functions(self): + """Get all functions registered inside the registry.""" + return dict(self._functions) + + +aeroo_function_registry = AerooFunctionRegistry() + + +def aeroo_util(function_name): + """Register a function as an Aeroo utility. + + :param function_name: the function name available to call the function in Aeroo + """ + + def decorator(func): + aeroo_function_registry.register(function_name, func) + return func + + return decorator + + +@aeroo_util("format_hours") +def format_hours(report, value): + hours = str(int(abs(value) // 1)) + minutes = str(int((abs(value) * 60) % 60)) + padded_hours = _with_padding_zero(hours) + padded_minutes = _with_padding_zero(minutes) + sign = "-" if value < 0 else "" + return f"{sign}{padded_hours}:{padded_minutes}" + + +def _with_padding_zero(value): + return f"0{value}" if len(value) == 1 else value + + +@aeroo_util("format_date") +def format_date(report, value: date, date_format: str): + """Format a date field value into the given format. + + The language of the template is used to format the date. + + :param report: the aeroo report + :param value: the value to format + :param format: the format to use + """ + if not value: + return "" + lang = report._context.get("lang") or "en_US" + return babel.dates.format_date(value, date_format, locale=lang) + + +@aeroo_util("today") +def format_date_today(report, date_format: str = None, delta: timedelta = None): + today_in_timezone = fields.Date.context_today(report) + + if delta is not None: + today_in_timezone += delta + + return format_date(report, value=today_in_timezone, date_format=date_format) + + +@aeroo_util("format_datetime") +def format_datetime(report, value: datetime, datetime_format: str): + """Format a datetime field value into the given format. + + The language of the template is used to format the datetime. + + :param report: the aeroo report + :param value: the value to format + :param format: the format to use + """ + if not value: + return "" + lang = report._context.get("lang") or "en_US" + datetime_in_timezone = fields.Datetime.context_timestamp(report, value) + format_datetime = babel.dates.format_datetime( + datetime_in_timezone, datetime_format, locale=lang + ) + format_datetime = format_datetime.replace("a.m.", "AM").replace("p.m.", "PM") + return format_datetime + + +@aeroo_util("now") +def format_datetime_now(report, datetime_format: str = None, delta: timedelta = None): + timestamp = datetime.now() + + if delta is not None: + timestamp += delta + formatted_datetime_now = format_datetime( + report, value=timestamp, datetime_format=datetime_format + ) + formatted_datetime_now = formatted_datetime_now.replace("a.m.", "AM").replace( + "p.m.", "PM" + ) + + return formatted_datetime_now + + +@aeroo_util("format_decimal") +def format_decimal(report, amount: float, amount_format="#,##0.00"): + """Format an amount in the language of the user. + + :param report: the aeroo report + :param amount: the amount to format + :param amount_format: an optional format to use + """ + lang = report._context.get("lang") or "en_US" + res = babel.numbers.format_decimal(amount, format=amount_format, locale=lang) + res = res.replace("\u202f", "\xa0") + return res + + +def get_locale_from_odoo_lang_and_country(lang: str, country: "res.country"): + locale = "{}_{}".format(lang.split("_")[0], country.code) + + if not localedata.exists(locale): + locale = lang + + return locale + + +@aeroo_util("format_currency") +def format_currency( + report, amount: float, currency=None, amount_format=None, country=None +): + """Format an amount into the given currency in the language of the user. + + :param report: the aeroo report + :param amount: the amount to format + :param currency: the currency object to use (o.currency_id) + :param amount_format: the format to use + """ + context = report._context + + lang = context.get("lang") or "en_US" + country = country or context.get("country") + locale = get_locale_from_odoo_lang_and_country(lang, country) if country else lang + + currency = currency or context.get("currency") + if currency is None: + raise ValidationError( + _( + "The function `format_currency` can not be evaluated " + "without a currency. You must either define a currency" + " in the field `Currency Evaluation` of the Aeroo report " + "or call the function with a currency explicitely." + ) + ) + res = babel.numbers.format_currency( + amount, currency.name, format=amount_format, locale=locale + ) + res = res.replace("\u202f", "\xa0") + return res + + +@aeroo_util("asimage") +def asimage( + report, + field_value: str, + rotate: bool = None, + size_x: int = None, + size_y: int = None, + uom: str = "px", + hold_ratio: bool = False, +): + def size_by_uom(val, uom, dpi): + if uom == "px": + result = str(val / dpi) + "in" + elif uom == "cm": + result = str(val / 2.54) + "in" + elif uom == "in": + result = str(val) + "in" + return result + + if not field_value: + return BytesIO(), "image/png" + + field_value = base64.decodebytes(field_value) + tf = BytesIO(field_value) + tf.seek(0) + im = Image.open(tf) + format = im.format.lower() + dpi_x, dpi_y = map(float, im.info.get("dpi", (96, 96))) + + if rotate is not None: + im = im.rotate(int(rotate)) + tf.seek(0) + im.save(tf, format) + + if hold_ratio: + img_ratio = im.size[0] / float(im.size[1]) # width / height + if size_x and not size_y: + size_y = size_x / img_ratio + elif not size_x and size_y: + size_x = size_y * img_ratio + elif size_x and size_y: + size_y2 = size_x / img_ratio + size_x2 = size_y * img_ratio + if size_y2 > size_y: + size_x = size_x2 + elif size_x2 > size_x: + size_y = size_y2 + + size_x = ( + size_x and size_by_uom(size_x, uom, dpi_x) or str(im.size[0] / dpi_x) + "in" + ) + size_y = ( + size_y and size_by_uom(size_y, uom, dpi_y) or str(im.size[1] / dpi_y) + "in" + ) + return tf, "image/%s" % format, size_x, size_y + + +@aeroo_util("barcode") +def barcode( + report, + code: str, + code_type: str = "ean13", + height: int = 50, + rotate: bool = None, + xw: int = 1, +): + if code: + if code_type.lower() == "ean13": + bar = EanBarCode() + im = bar.getImage(code, height) + elif code_type.lower() == "code128": + im = get_code(code, xw, height) + elif code_type.lower() == "code39": + im = create_c39(height, xw, code) + else: + return BytesIO(), "image/png" + + stream = _stream_image(im) + + if rotate is not None: + im = im.rotate(int(rotate)) + + size_x, size_y = _get_image_size(im) + return stream, "image/png", size_x, size_y + + +@aeroo_util("qrcode") +def qrcode(report, code, size=None): + image = make_qr_code(code) + + if size is None: + size_x, size_y = _get_image_size(image) + else: + size_x, size_y = size, size + + return _stream_image(image), "image/png", size_x, size_y + + +def _stream_image(image): + stream = BytesIO() + image.save(stream, "png") + return stream + + +def _get_image_size(image): + size_x = _to_inches(image.size[0]) + size_y = _to_inches(image.size[1]) + return size_x, size_y + + +def _to_inches(pixels): + return str(pixels / 96.0) + "in" + + +@aeroo_util("html2text") +def format_html2text(report, html: str): + """Convert the given HTML field value into text. + + The bodywidth=0 parameter prevents line breaks after 79 chars. + + :param html: the html string to format into raw text + :return: the raw text + """ + return html2text(html or "", bodywidth=0) + + +@aeroo_util("group_by") +def group_by(report, records, func, sort=None): + """Iterate over records grouped by the given comparator function.""" + sorted_records = records.sorted(key=func) + groupby_items = ( + (key, list(group)) for key, group in itertools.groupby(sorted_records, func) + ) + + sorted_groupby_items = ( + groupby_items + if sort is None + else sorted(groupby_items, key=lambda item: sort(item[0])) + ) + + for key, group in sorted_groupby_items: + grouped_record_ids = [r.id for r in group] + yield key, records.browse(grouped_record_ids) diff --git a/report_aeroo/i18n/es.po b/report_aeroo/i18n/es.po deleted file mode 100644 index 138e086..0000000 --- a/report_aeroo/i18n/es.po +++ /dev/null @@ -1,628 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * report_aeroo -# -# Translators: -# Juan José Scarafía , 2023 -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 16.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-17 20:52+0000\n" -"PO-Revision-Date: 2023-01-13 13:44+0000\n" -"Last-Translator: Juan José Scarafía , 2023\n" -"Language-Team: Spanish (https://app.transifex.com/adhoc/teams/46451/es/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: es\n" -"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer___logo_image -msgid " Logo Image" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/models/report.py:0 -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__deferred__adaptive -#, python-format -msgid "Adaptive" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__report_wizard -msgid "Adds a standard wizard when the report gets invoked." -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_view -msgid "Advanced" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_view -msgid "Aeroo Configuration" -msgstr "" - -#. module: report_aeroo -#: model:ir.actions.act_window,name:report_aeroo.action_docs_config_wizard -#: model:ir.ui.menu,name:report_aeroo.menu_docs_config_wizard -msgid "Aeroo DOCS connection" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/report_parser.py:0 -#, python-format -msgid "" -"Aeroo DOCS error!\n" -"%s" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_search_view -msgid "Aeroo Report" -msgstr "" - -#. module: report_aeroo -#: model:ir.actions.act_window,name:report_aeroo.action_report_stylesheets -#: model:ir.ui.menu,name:report_aeroo.menu_report_stylesheets -msgid "Aeroo Report Stylesheets" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/models/report.py:0 -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__report_type__aeroo -#, python-format -msgid "Aeroo Reports" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_res_company__stylesheet_id -msgid "Aeroo Reports Global Stylesheet" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/report_parser.py:0 -#: code:addons/report_aeroo/report_parser.py:0 -#, python-format -msgid "Aeroo Reports could'nt find report template" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_docs_config_installer -msgid "Apply and Test" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__auth_type -msgid "Authentication" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__charset -msgid "Charset" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_docs_config_installer -msgid "Close" -msgstr "Cerrar" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__code -msgid "Code" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_res_company -msgid "Companies" -msgstr "Compañías" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__compatible_types -msgid "Compatible Mime-Types" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_docs_config_installer -msgid "" -"Configure Aeroo Reports connection to DOCS service and test document " -"conversion." -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_docs_config_installer -msgid "Configure DOCS service connection" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/wizard/installer.py:0 -#, python-format -msgid "Connection to Aeroo DOCS disabled!" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__copies_intercalate -msgid "Copies Intercalate" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/report_parser.py:0 -#, python-format -msgid "Could not connect Aeroo DOCS!" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__create_uid -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__create_uid -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__create_uid -msgid "Created by" -msgstr "Creado por" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__create_date -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__create_date -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__create_date -msgid "Created on" -msgstr "Creado el" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__tml_source__database -msgid "Database" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__deferred -msgid "Deferred" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__deferred -msgid "" -"Deferred (aka Batch) reporting, for reporting on large amount " -"of data." -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__deferred_limit -msgid "Deferred Records Limit" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_docs_config_installer -msgid "Details" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__disable_fallback -msgid "Disable Format Fallback" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__display_name -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__display_name -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__display_name -msgid "Display Name" -msgstr "Mostrar nombre" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__docs_config_installer__state__done -msgid "Done" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__enabled -msgid "Enabled" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__docs_config_installer__state__error -msgid "Error" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__error_details -msgid "Error Details" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__extras -msgid "Extra options" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/wizard/installer.py:0 -#, python-format -msgid "" -"Failure! Connection to DOCS service was not established or convertion to PDF" -" unsuccessful!" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__tml_source__file -msgid "File" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__filter_name -msgid "Filter Name" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_docs_config_installer -msgid "Finish" -msgstr "Finalizar" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__process_sep -msgid "" -"Generate the report for each object separately, then merge " -"reports." -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__styles_mode__global -msgid "Global" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__host -msgid "Host" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__id -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__id -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__id -msgid "ID" -msgstr "ID (identificación)" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__copies_intercalate -msgid "" -"If true, then page order will be like \"1, 2, 3; 1, 2, 3\", if not it will " -"be like \"1, 1; 2, 2; 3, 3\"" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__config_logo -msgid "Image" -msgstr "Imagen" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__docs_config_installer__state__init -msgid "Init" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer____last_update -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes____last_update -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets____last_update -msgid "Last Modified on" -msgstr "Última modificación el" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__write_uid -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__write_uid -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__write_uid -msgid "Last Updated by" -msgstr "Última modificación por" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__write_date -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__write_date -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__write_date -msgid "Last Updated on" -msgstr "Última actualización el" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__msg -msgid "Message" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_report_mimetypes__name -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__name -msgid "Name" -msgstr "Nombre" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/report_parser.py:0 -#, python-format -msgid "No Aeroo Reports template filename provided" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__styles_mode__default -msgid "Not used" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__copies -msgid "Number of Copies" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/models/report.py:0 -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__deferred__off -#, python-format -msgid "Off" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__copies -msgid "Only available if output is a pdf" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_report_stylesheets__report_styles -msgid "OpenOffice.org / LibreOffice stylesheet (.odt)" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__parser_model -msgid "" -"Optional model to be used as parser, if not configured " -"\"report.report_aeroo.abstract\" will be used" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__out_format -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_search_view -msgid "Output Mime-type" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__tml_source__parser -msgid "Parser" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__parser_model -msgid "Parser Model" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/models/report.py:0 -#, python-format -msgid "Parser model %s not found on database." -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__password -msgid "Password" -msgstr "Contraseña" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__port -msgid "Port" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/models/report.py:0 -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__preload_mode__preload -#, python-format -msgid "Preload" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__preload_mode -msgid "Preload Mode" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__process_sep -msgid "Process Separately" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/report_parser.py:0 -#, python-format -msgid "Process_sep not compatible with selected output format" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__disable_fallback -msgid "" -"Raises error on format convertion failure. Prevents returning " -"original report file type if no convertion is available." -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__deferred_limit -msgid "" -"Records limit at which you are invited to start the deferred " -"process." -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__replace_report_id -msgid "Replace Report" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_ir_actions_report -msgid "Report Action" -msgstr "Acción de informe" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_report_mimetypes -msgid "Report Mime-Types" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_report_stylesheets_form -msgid "Report Stylesheet" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_report_stylesheets -#: model_terms:ir.ui.view,arch_db:report_aeroo.view_report_stylesheets_tree -msgid "Report Stylesheets" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__report_type -msgid "Report Type" -msgstr "Tipo de informe" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__report_wizard -msgid "Report Wizard" -msgstr "" - -#. module: report_aeroo -#: model:ir.actions.report,name:report_aeroo.aeroo_sample_report_id -msgid "Sample Report" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__replace_report_id -msgid "Select a report that should be replaced." -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__docs_config_installer__auth_type__simple -msgid "Simple Authentication" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__styles_mode__specified -msgid "Specified" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__state -msgid "State" -msgstr "Estado" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/models/report.py:0 -#: model:ir.model.fields.selection,name:report_aeroo.selection__ir_actions_report__preload_mode__static -#, python-format -msgid "Static" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__styles_mode -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_view -msgid "Stylesheet" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/wizard/installer.py:0 -#, python-format -msgid "" -"Success! Connection to the DOCS service was successfully established and PDF" -" convertion is working." -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_view -msgid "Template" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__report_data -msgid "Template Content" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__in_format -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_search_view -msgid "Template Mime-type" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_view -msgid "Template Path" -msgstr "" - -#. module: report_aeroo -#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_search_view -msgid "Template Source" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__stylesheet_id -#: model:ir.model.fields,field_description:report_aeroo.field_report_stylesheets__report_styles -msgid "Template Stylesheet" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__tml_source -msgid "Template source" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__report_type -msgid "" -"The type of the report that will be rendered, each one having its own " -"rendering method. HTML means the report will be opened directly in your " -"browser PDF means the report will be rendered using Wkhtmltopdf and " -"downloaded by the user." -msgstr "" -"El tipo de informe que se renderizará, cada uno con su propio método de " -"renderización. HTML indica que el informe se abrirá directamente en el " -"navegador, PDF indica que el informe se renderizará usando Wkhtmltopdf y " -"luego será descargado por el usuario." - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/check_deps.py:0 -#, python-format -msgid "Unmet python dependencies!" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_docs_config_installer__username -msgid "Username" -msgstr "" - -#. module: report_aeroo -#. odoo-python -#: code:addons/report_aeroo/check_deps.py:0 -#, python-format -msgid "Warning!" -msgstr "" - -#. module: report_aeroo -#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__wizard_id -msgid "Wizard Action" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_docs_config_installer -msgid "docs_config.installer" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_report_product_template_printer -msgid "report.product_template_printer" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_report_report_aeroo_abstract -msgid "report.report_aeroo.abstract" -msgstr "" - -#. module: report_aeroo -#: model:ir.model,name:report_aeroo.model_report_sample_report -msgid "report.sample_report" -msgstr "" diff --git a/report_aeroo/i18n/fr.po b/report_aeroo/i18n/fr.po new file mode 100644 index 0000000..8746e67 --- /dev/null +++ b/report_aeroo/i18n/fr.po @@ -0,0 +1,556 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * report_aeroo +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-27 18:10+0000\n" +"PO-Revision-Date: 2019-09-27 14:13-0400\n" +"Last-Translator: David Dufresne \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 2.0.6\n" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Add in the 'Print' menu" +msgstr "Ajouter au menu 'Imprimer'" + +#. module: report_aeroo +#: model:ir.model,name:report_aeroo.model_aeroo_filename_line +msgid "Aeroo Filename Line" +msgstr "Ligne de nom de fichier Aeroo" + +#. module: report_aeroo +#: model:res.groups,name:report_aeroo.group_aeroo_manager +msgid "Aeroo Manager" +msgstr "Gestionnaire de rapports Aeroo" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Aeroo Report" +msgstr "Rapport Aeroo" + +#. module: report_aeroo +#: selection:ir.actions.report,report_type:0 +#: model:ir.model.fields,field_description:report_aeroo.field_email_template_preview__aeroo_report_ids +#: model:ir.model.fields,field_description:report_aeroo.field_mail_template__aeroo_report_ids +#: model:ir.ui.menu,name:report_aeroo.menu_ir_action_aeroo_report_admin +#: model:ir.ui.menu,name:report_aeroo.menu_ir_action_aeroo_report_manager +msgid "Aeroo Reports" +msgstr "Rapports Aeroo" + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:441 +#, python-format +msgid "" +"Aeroo Reports do not support generating non-pdf reports in batch. You must " +"select one record at a time." +msgstr "" +"Les rapports Aeroo ne supportent pas l'impression en lot de rapports non-" +"pdf. S.v.p. sélectionner un rapport à la fois." + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_template_data +msgid "Aeroo Template Data" +msgstr "Contenu du modèle Aeroo" + +#. module: report_aeroo +#: model:ir.model,name:report_aeroo.model_aeroo_template_line +msgid "Aeroo Template Line" +msgstr "Ligne de modèle Aeroo" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_template_path +msgid "Aeroo Template Path" +msgstr "Emplacement du modèle Aeroo" + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:511 +#, python-format +msgid "" +"Aeroo reports are wrongly configured. The global parameter report_aeroo." +"{parameter_name} must be defined." +msgstr "" +"Les rapports Aeroo ne sont pas configurés correctement. Le paramètre système " +"report_aeroo.{parameter_name} doit être défini." + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:556 +#, python-format +msgid "At least one record must be selected to generate the report {report}." +msgstr "" +"Au moins un enregistrement doit être sélectionné pour générer le rapport " +"{report}." + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Attachments" +msgstr "Pièces jointes" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__code +msgid "Code" +msgstr "Code" + +#. module: report_aeroo +#: code:addons/report_aeroo/subprocess.py:32 +#, python-format +msgid "Command {command} exited with status {status}." +msgstr "La commande {command} s'est terminée avec le statut {status}." + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__company_id +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__company_id +msgid "Company" +msgstr "Société" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_company_eval +msgid "Company Evaluation" +msgstr "Évaluation de la société" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__compatible_types +msgid "Compatible Mime-Types" +msgstr "Types compatibles" + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:411 +#, python-format +msgid "" +"Could not generate the report %(report)s using the format %(output_format)s. " +"%(error)s" +msgstr "" +"Le rapport %(report)s n'a pas pu être généré avec le format " +"%(output_format)s. %(error)s" + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:456 +#, python-format +msgid "" +"Could not merge the pdf outputs of the report %(report)s.\n" +"\n" +"%(error)s" +msgstr "" +"Les rendus pdf n'ont pas pu être fusionnés pour le rapport %(report)s\n" +"\n" +"%(error)s" + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:148 +#, python-format +msgid "" +"Could not render report {report} for the company {company} in the language " +"{lang}." +msgstr "" +"Le rapport {report} n'a pas pu être généré pour la société {company} dans la " +"langue {lang}." + +#. module: report_aeroo +#: code:addons/report_aeroo/models/ir_actions_report.py:617 +#, python-format +msgid "" +"Could not render the attachment filename for the report {report} in the " +"language {lang}." +msgstr "" +"Le nom de la pièce jointe n'a pas pu être généré pour le rapport {report} " +"dans la langue {lang}." + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_country_eval +msgid "Country Evaluation" +msgstr "Évaluation du pays" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__create_uid +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__create_uid +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__create_date +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__create_date +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_currency_eval +msgid "Currency Evaluation" +msgstr "Évaluation de la devise" + +#. module: report_aeroo +#: selection:ir.actions.report,aeroo_template_source:0 +msgid "Database" +msgstr "Base de données" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_filename_per_lang +msgid "Different Filename per Language" +msgstr "Différent nom de fichier par langue" + +#. module: report_aeroo +#: selection:ir.actions.report,aeroo_template_source:0 +msgid "Different Template per Language / Company" +msgstr "Différent modèle par langue / société" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__display_name +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__display_name +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Display an option on related documents to print this report" +msgstr "" + +#. module: report_aeroo +#: model:ir.model,name:report_aeroo.model_mail_template +msgid "Email Templates" +msgstr "Modèle" + +#. module: report_aeroo +#: selection:ir.actions.report,aeroo_template_source:0 +msgid "File" +msgstr "Fichier" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__template_filename +msgid "File Name" +msgstr "Nom du fichier" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__filename +msgid "Filename" +msgstr "Nom de fichier" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_filename_line_ids +msgid "Filenames by Language" +msgstr "Noms de fichiers par langue" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__filter_name +msgid "Filter Name" +msgstr "Nom du fichier" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Generate Report From Record List" +msgstr "Générer rapport depuis vue liste" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Generic" +msgstr "Générique" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +msgid "Group By" +msgstr "Grouper par" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Groups" +msgstr "Groupes" + +#. module: report_aeroo +#: selection:ir.actions.report,report_type:0 +msgid "HTML" +msgstr "" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__id +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__id +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__id +msgid "ID" +msgstr "ID" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +msgid "Inactive" +msgstr "Inactif" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__lang_id +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__lang_id +msgid "Language" +msgstr "Langue" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_lang_eval +msgid "Language Evaluation" +msgstr "Évaluation de la langue" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line____last_update +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype____last_update +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__write_uid +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__write_uid +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__write_date +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__write_date +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "List Views" +msgstr "Vues Liste" + +#. module: report_aeroo +#: code:addons/report_aeroo/controllers/main.py:82 +#, python-format +msgid "" +"Multiple aeroo reports found with the same name ({report_name}):\n" +"\n" +"{report_display_names}" +msgstr "" +"Plusieurs rapports aeroo existent avec le même nom ({report_name}) :\n" +"\n" +"{report_display_names}" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_mimetype__name +msgid "Name" +msgstr "Nom" + +#. module: report_aeroo +#: code:addons/report_aeroo/controllers/main.py:77 +#, python-format +msgid "No aeroo report found with the name {report_name}." +msgstr "" +"Aucun rapport aeroo n'a été trouvé pour le nom de rapport {report_name}." + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_out_format_id +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +msgid "Output Mime-type" +msgstr "Type de sortie" + +#. module: report_aeroo +#: selection:ir.actions.report,report_type:0 +msgid "PDF" +msgstr "" + +#. module: report_aeroo +#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__aeroo_company_eval +msgid "" +"Python expression used to determine the company of the record being printed " +"in the report." +msgstr "" +"Expression en python utilisée pour déterminer la société de l'objet à " +"imprimer dans le rapport." + +#. module: report_aeroo +#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__aeroo_country_eval +msgid "" +"Python expression used to determine the country of the record being printed " +"in the report." +msgstr "" +"Expression en python utilisée pour déterminer le pays de l'objet à imprimer " +"dans le rapport." + +#. module: report_aeroo +#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__aeroo_currency_eval +msgid "" +"Python expression used to determine the currency of the record being printed " +"in the report." +msgstr "" +"Expression en python utilisée pour déterminer la devise de l'objet à " +"imprimer dans le rapport." + +#. module: report_aeroo +#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__aeroo_lang_eval +msgid "" +"Python expression used to determine the language of the record being printed " +"in the report." +msgstr "" +"Expression en python utilisée pour déterminer le language de l'objet à " +"imprimer dans le rapport." + +#. module: report_aeroo +#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__aeroo_tz_eval +msgid "" +"Python expression used to determine the timezone used for formatting dates " +"and timestamps." +msgstr "" +"Expression en python utilisée pour déterminer la société de l'objet à " +"imprimer dans le rapport." + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Remove from the 'Print' menu" +msgstr "Ajouter au menu 'Imprimer'" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Remove the contextual action related this report" +msgstr "" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__report_id +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__report_id +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +msgid "Report" +msgstr "Rapport" + +#. module: report_aeroo +#: model:ir.model,name:report_aeroo.model_ir_actions_report +msgid "Report Action" +msgstr "Action de rapport" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Report Context" +msgstr "Contexte du rapport" + +#. module: report_aeroo +#: model:ir.model,name:report_aeroo.model_aeroo_mimetype +msgid "Report Mime-Types" +msgstr "Types de fichier générés" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__report_type +msgid "Report Type" +msgstr "Types de fichier générés" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_view_tree +msgid "Report xml" +msgstr "Rapport" + +#. module: report_aeroo +#: model:ir.actions.act_window,name:report_aeroo.action_aeroo_report_xml_tree +msgid "Reports" +msgstr "Rapports" + +#. module: report_aeroo +#: model:ir.actions.report,name:report_aeroo.aeroo_sample_report +msgid "Sample Report" +msgstr "Exemple de rapport" + +#. module: report_aeroo +#: model:ir.actions.report,name:report_aeroo.aeroo_sample_report_multi +msgid "Sample Report (from list view)" +msgstr "Exemple de rapport (depuis vue liste)" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Security" +msgstr "Sécurité" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_filename_line__sequence +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__sequence +msgid "Sequence" +msgstr "Séquence" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_aeroo_template_line__template_data +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Template" +msgstr "Modèle" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Template Content" +msgstr "Contenu du modèle" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_in_format +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +msgid "Template Mime-type" +msgstr "Type de fichier du modèle" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_aeroo_report_xml_search_view +msgid "Template Source" +msgstr "Source du modèle" + +#. module: report_aeroo +#: model_terms:ir.ui.view,arch_db:report_aeroo.act_report_xml_view1 +msgid "Template path" +msgstr "Emplacement du modèle" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_template_source +msgid "Template source" +msgstr "Source du modèle" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_template_line_ids +msgid "Templates by Language" +msgstr "Modèles par langue" + +#. module: report_aeroo +#: selection:ir.actions.report,report_type:0 +msgid "Text" +msgstr "Texte" + +#. module: report_aeroo +#: code:addons/report_aeroo/extra_functions.py:152 +#, python-format +msgid "" +"The function `format_currency` can not be evaluated without a currency. You " +"must either define a currency in the field `Currency Evaluation` of the " +"Aeroo report or call the function with a currency explicitely." +msgstr "" +"La fonction `format_currency` ne peut pas être évaluée sans une devise. Vous " +"devez soit définir une devise dans le champ `Évaluation de la devise` sur le " +"rapport Aeroo ou appeller la fonction avec une devise explicitement." + +#. module: report_aeroo +#: code:addons/report_aeroo/controllers/main.py:47 +#, python-format +msgid "The report name is expected in order to generate an aeroo report." +msgstr "Le nom du rapport est requis pour générer un rapport aeroo." + +#. module: report_aeroo +#: model:ir.model.fields,help:report_aeroo.field_ir_actions_report__report_type +msgid "" +"The type of the report that will be rendered, each one having its own " +"rendering method. HTML means the report will be opened directly in your " +"browser PDF means the report will be rendered using Wkhtmltopdf and " +"downloaded by the user." +msgstr "" + +#. module: report_aeroo +#: code:addons/report_aeroo/subprocess.py:43 +#, python-format +msgid "" +"Timeout ({timeout} seconds) expired while executing the command: {command}" +msgstr "" +"Le délai maximal ({timeout} seconds) s'est écoulé lors de l'exécution de la " +"commande: {command}" + +#. module: report_aeroo +#: model:ir.model.fields,field_description:report_aeroo.field_ir_actions_report__aeroo_tz_eval +msgid "Timezone Evaluation" +msgstr "Évaluation du fuseau horaire" + +#. module: report_aeroo +#: model:ir.actions.act_window,name:report_aeroo.act_translations +msgid "Translations" +msgstr "Traductions" diff --git a/report_aeroo/models/__init__.py b/report_aeroo/models/__init__.py old mode 100755 new mode 100644 index 4c4f242..aa9c1a3 --- a/report_aeroo/models/__init__.py +++ b/report_aeroo/models/__init__.py @@ -1 +1,11 @@ -from . import report +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +from . import ( + aeroo_filename_line, + aeroo_mimetype, + aeroo_template_line, + ir_actions_report, + mail_template, +) diff --git a/report_aeroo/models/aeroo_filename_line.py b/report_aeroo/models/aeroo_filename_line.py new file mode 100644 index 0000000..e8f9bdb --- /dev/null +++ b/report_aeroo/models/aeroo_filename_line.py @@ -0,0 +1,19 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +from odoo import fields, models + + +class AerooFilenameLine(models.Model): + + _name = "aeroo.filename.line" + _order = "sequence" + _description = "Aeroo Filename Line" + + sequence = fields.Integer() + report_id = fields.Many2one( + "ir.actions.report", "Report", required=True, ondelete="cascade" + ) + company_id = fields.Many2one("res.company", "Company") + lang_id = fields.Many2one("res.lang", "Language") + filename = fields.Char(required=True) diff --git a/report_aeroo/models/aeroo_mimetype.py b/report_aeroo/models/aeroo_mimetype.py new file mode 100644 index 0000000..3f7997e --- /dev/null +++ b/report_aeroo/models/aeroo_mimetype.py @@ -0,0 +1,17 @@ +# Copyright 2008-2014 Alistek +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +from odoo import fields, models + + +class ReportMimeType(models.Model): + + _name = "aeroo.mimetype" + _description = "Report Mime-Types" + + name = fields.Char("Name", size=64, required=True, readonly=True) + code = fields.Char("Code", size=16, required=True, readonly=True) + compatible_types = fields.Char("Compatible Mime-Types", size=128, readonly=True) + filter_name = fields.Char("Filter Name", size=128, readonly=True) diff --git a/report_aeroo/models/aeroo_template_line.py b/report_aeroo/models/aeroo_template_line.py new file mode 100644 index 0000000..44336f1 --- /dev/null +++ b/report_aeroo/models/aeroo_template_line.py @@ -0,0 +1,25 @@ +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +import base64 +from odoo import fields, models + + +class AerooTemplateLine(models.Model): + + _name = "aeroo.template.line" + _order = "sequence" + _description = "Aeroo Template Line" + + sequence = fields.Integer() + report_id = fields.Many2one( + "ir.actions.report", "Report", required=True, ondelete="cascade" + ) + company_id = fields.Many2one("res.company", "Company") + lang_id = fields.Many2one("res.lang", "Language") + template_data = fields.Binary("Template", required=True) + template_filename = fields.Char("File Name") + + def get_aeroo_template(self, record): + return base64.b64decode(self.template_data) diff --git a/report_aeroo/models/ir_actions_report.py b/report_aeroo/models/ir_actions_report.py new file mode 100644 index 0000000..48aaa97 --- /dev/null +++ b/report_aeroo/models/ir_actions_report.py @@ -0,0 +1,755 @@ +# Copyright 2008-2014 Alistek +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +import base64 +import datetime +import logging +import os +import subprocess +import traceback +from aeroolib.plugins.opendocument import Template, OOSerializer +from dateutil.relativedelta import relativedelta +from functools import wraps +from io import BytesIO +from genshi.template.base import Context as GenshiContext +from tempfile import NamedTemporaryFile + +from odoo import models, fields, api, tools, _ +from odoo.exceptions import ValidationError +from odoo.tools.safe_eval import safe_eval +from odoo.tools import file_open + +from ..namespace import AerooNamespace +from ..extra_functions import aeroo_function_registry + +_logger = logging.getLogger(__name__) + +try: + # We use a jinja2 sandboxed environment to render mako templates. + # Note that the rendering does not cover all the mako syntax, in particular + # arbitrary Python statements are not accepted, and not all expressions are + # allowed: only "public" attributes (not starting with '_') of objects may + # be accessed. + # This is done on purpose: it prevents incidental or malicious execution of + # Python code that may break the security of the server. + from jinja2.sandbox import SandboxedEnvironment + + mako_template_env = SandboxedEnvironment( + variable_start_string="${", + variable_end_string="}", + line_statement_prefix="%", + trim_blocks=True, # do not output newline after blocks + ) + mako_template_env.globals.update( + { + "str": str, + "datetime": datetime, + "len": len, + "abs": abs, + "min": min, + "max": max, + "sum": sum, + "filter": filter, + "map": map, + "round": round, + } + ) +except ImportError: + _logger.warning("jinja2 not available, templating features will not work!") + + +class IrActionsReport(models.Model): + + _inherit = "ir.actions.report" + + @api.model + def _get_default_aeroo_out_format(self): + return self.env["aeroo.mimetype"].search([("code", "=", "odt")], limit=1) + + @api.model + def _get_in_aeroo_mimetypes(self): + types = self.env["aeroo.mimetype"].search([]) + return ( + [(t.code, t.name) for t in types] + if types + else [("odt", "odt"), ("ods", "ods")] + ) + + active = fields.Boolean(default=True) + report_type = fields.Selection( + selection_add=[("aeroo", "Aeroo Reports")], + ondelete={"aeroo": "cascade"}, + ) + aeroo_in_format = fields.Selection( + selection="_get_in_aeroo_mimetypes", + string="Template Mime-type", + prefetch=False, + default=lambda self: "odt", + ) + aeroo_out_format_id = fields.Many2one( + "aeroo.mimetype", + "Output Mime-type", + prefetch=False, + default=_get_default_aeroo_out_format, + ) + aeroo_template_source = fields.Selection( + [ + ("database", "Database"), + ("file", "File"), + ("lines", "Different Template per Language / Company"), + ], + prefetch=False, + string="Template source", + default="database", + ) + aeroo_template_data = fields.Binary(prefetch=False) + aeroo_template_path = fields.Char(prefetch=False) + aeroo_template_line_ids = fields.One2many( + "aeroo.template.line", "report_id", "Templates by Language", prefetch=False + ) + aeroo_lang_eval = fields.Char( + "Language Evaluation", + help="Python expression used to determine the language " + "of the record being printed in the report.", + prefetch=False, + default="user.lang", + ) + aeroo_tz_eval = fields.Char( + "Timezone Evaluation", + help="Python expression used to determine the timezone " + "used for formatting dates and timestamps.", + prefetch=False, + default="user.tz", + ) + aeroo_company_eval = fields.Char( + "Company Evaluation", + help="Python expression used to determine the company " + "of the record being printed in the report.", + prefetch=False, + default="user.company_id", + ) + aeroo_country_eval = fields.Char( + "Country Evaluation", + help="Python expression used to determine the country " + "of the record being printed in the report.", + prefetch=False, + default="user.company_id.country_id", + ) + aeroo_currency_eval = fields.Char( + "Currency Evaluation", + help="Python expression used to determine the currency " + "of the record being printed in the report.", + prefetch=False, + default="user.company_id.currency_id", + ) + + def report_action(self, docids, data=None, config=True): + res = super().report_action(docids, data=data, config=config) + res["id"] = self.id + return res + + def read(self, fields=None, load="_classic_read"): + if not fields: + fields = [k for k, v in self._fields.items() if v.type != "binary"] + + return super().read(fields, load) + + def _get_aeroo_template(self, record): + """Get an aeroo template for the given record. + + There are 3 ways to store the aeroo template: + + 1- a single template stored in the file system + 2- a single template stored in the database + 3- one template per combination (lang, company) stored in the database + + :param record: the record for which to generate the report + :return: the template's binary file + """ + if self.aeroo_template_source == "file": + return self._get_aeroo_template_from_file() + + if self.aeroo_template_source == "database": + return self._get_aeroo_template_from_database() + + return self._get_aeroo_template_from_lines(record) + + def _get_aeroo_template_from_file(self): + """Get an aeroo template from a file.""" + with file_open(self.aeroo_template_path, "rb") as file: + return file.read() + + def _get_aeroo_template_from_database(self): + """Get an aeroo template stored in the database.""" + return base64.b64decode(self.aeroo_template_data) + + def _get_aeroo_template_from_lines(self, record): + """Get an aeroo template from the template lines. + + :param record: the record for which to generate the report + :return: the template's binary file + """ + template_line = self._get_aeroo_template_line(record) + return template_line.get_aeroo_template(record) + + def _get_aeroo_template_line(self, record): + """Get an aeroo template line matching the given record. + + An aeroo report can have different templates per company + and per language. + + :param record: the record for which to generate the report + :return: the template's binary file + """ + lang = self._get_aeroo_lang(record) + company = self._get_aeroo_company(record) + + def line_matches_lang(line): + return not line.lang_id or line.lang_id.code == lang + + def line_matches_company(line): + return not line.company_id or line.company_id == company + + line = next( + ( + line + for line in self.aeroo_template_line_ids + if line_matches_lang(line) and line_matches_company(line) + ), + None, + ) + + if line is None: + raise ValidationError( + _( + "Could not render report {report} for the " + "company {company} in the language {lang}." + ).format( + report=self.name, + company=company.name, + lang=lang, + ) + ) + + return line + + def _get_aeroo_variable_eval_context(self, record): + return {"o": record, "user": self.env.user} + + def _get_aeroo_lang(self, record): + """Get the lang to use in the report for a given record. + + :rtype: res.company + """ + lang = ( + safe_eval( + self.aeroo_lang_eval, self._get_aeroo_variable_eval_context(record) + ) + if self.aeroo_lang_eval + else None + ) + return lang or "en_US" + + def _get_aeroo_timezone(self, record): + """Get the timezone to use in the report for a given record. + + :rtype: res.company + """ + return ( + safe_eval(self.aeroo_tz_eval, self._get_aeroo_variable_eval_context(record)) + if self.aeroo_tz_eval + else None + ) + + def _get_aeroo_company(self, record): + """Get the company to use in the report for a given record. + + The company is used if the template of the report is different + per company. + + :rtype: res.company + """ + return ( + safe_eval( + self.aeroo_company_eval, self._get_aeroo_variable_eval_context(record) + ) + if self.aeroo_company_eval + else self.env.user.company_id + ) + + def _get_aeroo_country(self, record): + """Get the country to use in the report for a given record. + + The country is used if the template of the report is different + per country. + + :rtype: res.country + """ + return ( + safe_eval( + self.aeroo_country_eval, self._get_aeroo_variable_eval_context(record) + ) + if self.aeroo_country_eval + else None + ) + + def _get_aeroo_currency(self, record): + """Get the currency to use in the report for a given record. + + The currency is used if the template of the report is different + per currency. + + :rtype: res.currency + """ + return ( + safe_eval( + self.aeroo_currency_eval, self._get_aeroo_variable_eval_context(record) + ) + if self.aeroo_currency_eval + else None + ) + + def _get_aeroo_context(self, record): + """Get the rendering context of an aeroo report.""" + return { + "lang": self._get_aeroo_lang(record), + "tz": self._get_aeroo_timezone(record), + "country": self._get_aeroo_country(record), + "currency": self._get_aeroo_currency(record), + "relativedelta": relativedelta, + } + + def _get_aeroo_libreoffice_timeout(self): + """Get the timeout of the Libreoffice process in seconds.""" + return 60 + + def _render_aeroo(self, doc_ids, data=None, force_output_format=None): + """Render an aeroo report. + + If doc_ids contains more than one record id, the report will + be generated individually for each record. Then, all pdf outputs + will be merged together. + + :param list doc_ids: the ids of the records. + :param dict data: the data to send to the report as context. + :param str force_output_format: whether to force a given output report format. + If not given the standard output format defined on the report is used. + """ + output_format = force_output_format or self.aeroo_out_format_id.code + + data = data or {} + + if len(doc_ids) > 1: + return self._render_aeroo_multi(doc_ids, data, output_format) + + record = self.env[self.model].browse(doc_ids[0]) + report_context = self._get_aeroo_context(record) + self = self.with_context(**report_context) + + # Check if an attachment already exists + attachment_output = self._find_aeroo_report_attachment(record, output_format) + if attachment_output: + return attachment_output, output_format + + template = self._get_aeroo_template(record) + + # Render the report + current_report_data = dict( + data, + o=record.with_context(**report_context), + company=self._get_aeroo_company(record), + **report_context + ) + output = self._render_aeroo_content( + template, current_report_data, output_format + ) + + # Generate the attachment + if self.attachment_use: + self._create_aeroo_attachment(record, output, output_format) + + return output, output_format + + def _render_aeroo_content(self, template, data, output_format): + """Generate the aeroo report binary from the template. + + :param template: the Libreoffice template to use + :param data: the data used for rendering the report + :param output_format: the output format + :return: the report's binary data + """ + # The first given record is + template_io = BytesIO() + template_io.write(template) + serializer = OOSerializer(template_io) + + report_context = GenshiContext(**data) + report_context.update(self._get_aeroo_extra_functions()) + report_context["t"] = AerooNamespace() + + output = ( + Template(source=template_io, serializer=serializer) + .generate(report_context) + .render() + .getvalue() + ) + + if self.aeroo_in_format != output_format: + output = self._convert_aeroo_report(output, output_format) + + return output + + def _get_aeroo_extra_functions(self): + """Get a dictionnary of extra functions available inside an aeroo template.""" + return { + k: self._wrap_aeroo_function(v) + for k, v in aeroo_function_registry.get_functions().items() + } + + def _wrap_aeroo_function(self, func): + """Wrap an extra function for the current aeroo report. + + The wrapping automatically adds the report as first parameter of the function. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + return func(self, *args, **kwargs) + + return wrapper + + def get_aeroo_filename(self, record, output_format): + """Get the attachement filename for the generated report. + + :param record: the record for which to generate the report + :return: the filename + """ + if self.attachment: + filename = self._eval_aeroo_attachment_filename(self.attachment, record) + return ".".join((filename, output_format)) + else: + return ".".join((self.name, output_format)) + + def _eval_aeroo_attachment_filename(self, filename, record): + """Evaluate the given attachment filename for the given record. + + :param filename: the filename mako template + :param record: the record for which to evaluate the filename + :return: the rendered attachment filename + """ + template = mako_template_env.from_string(tools.ustr(filename)) + context = {"o": record.with_context()} + context.update(self._get_aeroo_extra_functions()) + return template.render(context) + + def _find_aeroo_report_attachment(self, record, output_format): + """Find an attachment of the Aeroo report on the given record. + + If the report is stored as an attachment, it will be generated + only once for each record. + + If the report as already been generated, this method returns + the binary data stored in the attachment. Otherwise, it returns None. + + :param record: the record for which to find the attachement + :return: the report's binary data or None + """ + if self.attachment_use: + filename = self.get_aeroo_filename(record, output_format) + attachment = self.env["ir.attachment"].search( + [ + ("res_id", "=", record.id), + ("res_model", "=", record._name), + ("name", "=", filename), + ], + limit=1, + ) + if attachment: + return base64.decodebytes(attachment.datas) + return None + + def _create_aeroo_attachment(self, record, file_data, output_format): + """Save the generated aeroo report as attachment. + + :param record: the record used to generate the report + :param file_data: the generated report's binary file + :return: the generated attachment + """ + filename = self.get_aeroo_filename(record, output_format) + return self.env["ir.attachment"].create( + { + "name": filename, + "datas": base64.encodebytes(file_data), + "res_model": record._name, + "res_id": record.id, + } + ) + + def _convert_aeroo_report(self, output, output_format): + """Convert a generated aeroo report to the output format. + + :param string output: the aeroo data to convert. + :return: the content of the generated report + :rtype: bytes + """ + in_format = self.aeroo_in_format + + temp_file = generate_temporary_file(in_format, output) + filedir, filename = os.path.split(temp_file.name) + + cmd = [ + "libreoffice", + "--headless", + "--convert-to", + output_format, + "--outdir", + filedir, + temp_file.name, + ] + + timeout = self._get_aeroo_libreoffice_timeout() + + try: + subprocess.call(cmd, timeout=timeout) + except Exception as exc: + os.remove(temp_file.name) + raise ValidationError( + _( + "Could not generate the report %(report)s " + "using the format %(output_format)s. " + "%(error)s" + ) + % { + "report": self.name, + "output_format": output_format, + "error": exc, + } + ) + + output_file = temp_file.name[:-3] + output_format + with open(output_file, "rb") as f: + output = f.read() + + os.remove(temp_file.name) + os.remove(output_file) + + return output + + def _render_aeroo_multi(self, doc_ids, data, output_format): + """Render an aeroo report for multiple records at the same time. + + All reports are generated individually and merged together using pdftk. + Only reports in pdf formats are supported. + + :param list doc_ids: the ids of the records. + :param dict data: the data to send to the report as context. + :return: the content of the merged pdf reports + :rtype: bytes + """ + if output_format != "pdf": + raise ValidationError( + _( + "Aeroo Reports do not support generating non-pdf " + "reports in batch. You must select one record at a time." + ) + ) + + input_files = [] + + for record_id in doc_ids: + report = self._render_aeroo([record_id], data) + temp_file = generate_temporary_file(output_format, report[0]) + input_files.append(temp_file.name) + + try: + return self._merge_aeroo_pdf(input_files), "pdf" + except Exception as exc: + traceback.print_exc() + raise ValidationError( + _( + "Could not merge the pdf outputs of the report %(report)s." + "\n\n%(error)s" + ) + % { + "report": self.name, + "error": exc, + } + ) + finally: + for file in input_files: + os.remove(file) + + def _merge_aeroo_pdf(self, input_files): + """Merge the given pdf files together using pdftk. + + :param list input_files: the paths to the pdf files to merge. + :return: the content of the merged pdf reports + :rtype: bytes + """ + output_file = generate_temporary_file("pdf") + + cmd = ["pdfunite", *input_files, output_file.name] + timeout = self._get_aeroo_libreoffice_timeout() + + try: + subprocess.call(cmd, timeout=timeout) + except BaseException: + os.remove(output_file.name) + raise + + with open(output_file.name, "rb") as f: + output = f.read() + + os.remove(output_file.name) + + return output + + +class IrActionsReportWithSudo(models.Model): + + _inherit = "ir.actions.report" + + def _get_aeroo_template(self, record): + """Prevent access rights from impacting the aeroo template selection.""" + self = self.sudo() + record = record.sudo() + return super()._get_aeroo_template(record) + + +class AerooReportsGeneratedFromListViews(models.Model): + """Enable rendering aeroo reports from a list view. + + Instead of generating one report per record and merging the rendered pdf, + a single report is generated. + + A variable `objects` is passed to the renderer instead of a variable `o`. + This new variable contains the recordset for which to render the report. + """ + + _inherit = "ir.actions.report" + + def _render_aeroo(self, doc_ids, data=None, force_output_format=None): + data = data or {} + if self.multi: + return self._render_aeroo_from_list_of_records( + doc_ids, data, force_output_format + ) + else: + return super()._render_aeroo( + doc_ids=doc_ids, + data=data, + force_output_format=force_output_format, + ) + + def _render_aeroo_from_list_of_records( + self, doc_ids, data=None, force_output_format=None + ): + """Render an aeroo report for a list of record ids. + + :param list doc_ids: the ids of the records. + :param dict data: the data to send to the report as context. + :param str force_output_format: whether to force a given output report format. + If not given the standard output format defined on the report is used. + """ + if len(doc_ids) == 0: + raise ValidationError( + _( + "At least one record must be selected to generate the report {report}." + ).format(report=self.name) + ) + + output_format = force_output_format or self.aeroo_out_format_id.code + + if data is None: + data = {} + + records = self.env[self.model].browse(doc_ids) + + template = self._get_aeroo_template(records[0]) + report_context = self._get_aeroo_context(records[0]) + report_data = dict( + data, + objects=records, + company=self._get_aeroo_company(records[0]), + **report_context + ) + + # Render the report + output = self.with_context(**report_context)._render_aeroo_content( + template, report_data, output_format + ) + + return output, output_format + + def _onchange_is_aeroo_list_report_set_multi(self): + if self.is_aeroo_list_report: + self.multi = True + + +class AerooReportsWithAttachmentFilenamePerLang(models.Model): + """Enable one attachment filename per language.""" + + _inherit = "ir.actions.report" + + aeroo_filename_per_lang = fields.Boolean( + "Different Filename per Language", + prefetch=False, + ) + aeroo_filename_line_ids = fields.One2many( + "aeroo.filename.line", "report_id", "Filenames by Language" + ) + + def get_aeroo_filename(self, record, output_format): + """Get the attachement filename for the generated report. + + :param record: the record for which to generate the freport + :return: the filename + """ + if not self.aeroo_filename_per_lang: + return super().get_aeroo_filename(record, output_format) + + mako_filename = self._get_aeroo_filename_from_lang(record) + rendered_filename = self._eval_aeroo_attachment_filename(mako_filename, record) + return ".".join((rendered_filename, output_format)) + + def _get_aeroo_filename_from_lang(self, record): + """Get the attachment filename for the record based on the rendering language. + + :param record: the record for which to generate the file name + :return: the filename mako template + """ + lang = self._get_aeroo_lang(record) + + def line_matches_lang(line): + return line.lang_id.code == lang + + line = next( + (line for line in self.aeroo_filename_line_ids if line_matches_lang(line)), + None, + ) + + if line is None: + raise ValidationError( + _( + "Could not render the attachment filename for the report " + "{report} in the language {lang}." + ).format(report=self.name, lang=lang) + ) + + return line.filename + + +def generate_temporary_file(format, data=None): + """Generate a temporary file containing the given data. + + :param string format: the extension of the file to create + :param bytes data: the data to write in the file + """ + temp_file = NamedTemporaryFile(suffix=".%s" % format, delete=False) + temp_file.close() + if data is not None: + with open(temp_file.name, "wb") as f: + f.write(data) + return temp_file diff --git a/report_aeroo/models/mail_template.py b/report_aeroo/models/mail_template.py new file mode 100644 index 0000000..d6ca788 --- /dev/null +++ b/report_aeroo/models/mail_template.py @@ -0,0 +1,53 @@ +# Copyright 2008-2014 Alistek +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License GPL-3.0 or later (http://www.gnu.org/licenses/gpl). + +import base64 +from odoo import fields, models + + +class MailTemplate(models.Model): + + _inherit = "mail.template" + + aeroo_report_ids = fields.Many2many( + "ir.actions.report", + "mail_template_aeroo_report_rel", + "mail_template_id", + "aeroo_report_id", + string="Aeroo Reports", + domain="[('model', '=', model), ('report_type', '=', 'aeroo'), ('multi', '=', False)]", + ) + + def generate_email(self, res_ids, fields): + """Add aeroo reports to the generated emails.""" + results = super().generate_email(res_ids, fields=fields) + + multi_mode = True + if isinstance(res_ids, int): + res_ids = [res_ids] + multi_mode = False + + # When the super method receives a single record, + # it returns a single dictionnary of values. + if not multi_mode: + results = {res_ids[0]: results} + + for res_id in res_ids: + values = results[res_id] + + for aeroo_report in self.aeroo_report_ids: + content, content_type = aeroo_report._render_aeroo([res_id], {}) + content = base64.b64encode(content) + + record = self.env[self.model].browse(res_id) + output_format = aeroo_report.aeroo_out_format_id.code + file_name = aeroo_report.get_aeroo_filename(record, output_format) + + if "attachments" not in values: + values["attachments"] = [] + + values["attachments"].append((file_name, content)) + + return multi_mode and results or results[res_ids[0]] diff --git a/report_aeroo/models/report.py b/report_aeroo/models/report.py deleted file mode 100644 index f526936..0000000 --- a/report_aeroo/models/report.py +++ /dev/null @@ -1,262 +0,0 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -import encodings -import binascii -from base64 import b64decode -import logging - -from odoo import api, fields, models, _ -from odoo.exceptions import UserError -from odoo.tools import file_open - -_logger = logging.getLogger(__name__) - - -class ReportStylesheets(models.Model): - ''' - Aeroo Report Stylesheets - ''' - _name = 'report.stylesheets' - _description = 'Report Stylesheets' - - name = fields.Char('Name', size=64, required=True) - report_styles = fields.Binary('Template Stylesheet', - help='OpenOffice.org / LibreOffice stylesheet (.odt)') - - -class ResCompany(models.Model): - _name = 'res.company' - _inherit = 'res.company' - - stylesheet_id = fields.Many2one('report.stylesheets', - 'Aeroo Reports Global Stylesheet') - - -class ReportMimetypes(models.Model): - ''' - Aeroo Report Mime-Type - ''' - _name = 'report.mimetypes' - _description = 'Report Mime-Types' - - name = fields.Char('Name', size=64, required=True, readonly=True) - code = fields.Char('Code', size=16, required=True, readonly=True) - compatible_types = fields.Char('Compatible Mime-Types', size=128, readonly=True) - filter_name = fields.Char('Filter Name', size=128, readonly=True) - - -class ReportAeroo(models.Model): - _inherit = 'ir.actions.report' - - @api.model - def _render_aeroo(self, report_ref, docids, data=None): - report = self._get_report(report_ref) - report_parser = self.env[report.parser_model or 'report.report_aeroo.abstract'] - return report_parser.with_context( - active_model=report.model, report_name=report.report_name - ).aeroo_report(docids, data) - - @api.model - def _get_report_from_name(self, report_name): - res = super()._get_report_from_name(report_name) - if res: - return res - report_obj = self.env['ir.actions.report'] - conditions = [('report_type', 'in', ['aeroo']), - ('report_name', '=', report_name)] - context = self.env['res.users'].context_get() - return report_obj.with_context(context).search(conditions, limit=1) - - def _read_template(self): - self.ensure_one() - fp = None - data = None - try: - fp = file_open(self.report_file, mode='rb') - data = fp.read() - except IOError as e: - if e.errno == 13: # Permission denied on the template file - raise UserError(_(e.strerror), e.filename) - else: - _logger.exception( - "Error in '_read_template' method", exc_info=True) - except Exception as e: - _logger.exception( - "Error in '_read_template' method \n %s" % str(e), exc_info=True) - fp = False - data = False - finally: - if fp is not None: - fp.close() - return data - - @api.model - def _get_encodings(self): - lst = list(set(encodings._aliases.values())) - lst.sort() - return zip(lst, lst) - - @api.model - def _get_default_outformat(self): - res = self.env['report.mimetypes'].search([('code', '=', 'oo-odt')]) - return res and res[0].id or False - - def _get_extras(recs): - result = [] - if recs.aeroo_docs_enabled(): - result.append('aeroo_ooo') - # Check deferred_processing module - recs.env.cr.execute("SELECT id, state FROM ir_module_module WHERE \ - name='deferred_processing'") - deferred_proc_module = recs.env.cr.dictfetchone() - if (deferred_proc_module and deferred_proc_module['state'] in - ('installed', 'to upgrade')): - result.append('deferred_processing') - result = ','.join(result) - for rec in recs: - rec.extras = result - - @api.model - def aeroo_docs_enabled(self): - ''' - Check if Aeroo DOCS connection is enabled - ''' - icp = self.env['ir.config_parameter'].sudo() - enabled = icp.get_param('aeroo.docs_enabled') - return enabled == 'True' and True or False - - @api.model - def _get_in_mimetypes(self): - mime_obj = self.env['report.mimetypes'] - domain = ( - self.env.context.get('allformats') and [] or [('filter_name', '=', False)] - ) - res = mime_obj.search(domain).read(['code', 'name']) - return [(r['code'], r['name']) for r in res] - - charset = fields.Selection('_get_encodings', string='Charset', - required=True, default='utf_8') - styles_mode = fields.Selection([ - ('default', 'Not used'), - ('global', 'Global'), - ('specified', 'Specified'), - ], string='Stylesheet', default='default') - stylesheet_id = fields.Many2one('report.stylesheets', 'Template Stylesheet') - preload_mode = fields.Selection([ - ('static', _('Static')), - ('preload', _('Preload')), - ], string='Preload Mode', default='static') - tml_source = fields.Selection([ - ('database', 'Database'), - ('file', 'File'), - ('parser', 'Parser'), - ('attachment', 'Attachment'), - ], string='Template source', default='database', index=True) - attachment_id = fields.Many2one( - 'ir.attachment', - domain=[("res_model", "=", "report.aeroo")], - ondelete='set null' - ) - parser_model = fields.Char( - help="Optional model to be used as parser, \ - if not configured 'report.report_aeroo.abstract' will be used" - ) - report_type = fields.Selection( - selection_add=[('aeroo', _('Aeroo Reports'))], - ondelete={'aeroo': 'cascade'} - ) - process_sep = fields.Boolean( - 'Process Separately', - help='Generate the report for each object separately, \ - then merge reports.' - ) - in_format = fields.Selection( - selection='_get_in_mimetypes', - string='Template Mime-type' - ) - out_format = fields.Many2one( - 'report.mimetypes', - 'Output Mime-type', - default=_get_default_outformat - ) - report_wizard = fields.Boolean( - 'Report Wizard', - help='Adds a standard wizard when the report gets invoked.' - ) - copies = fields.Integer( - string='Number of Copies', - default=1, - help='Only available if output is a pdf') - copies_intercalate = fields.Boolean( - help='If true, then page order will be like "1, 2, 3; 1, 2, 3", if ' - 'not it will be like "1, 1; 2, 2; 3, 3"') - disable_fallback = fields.Boolean( - 'Disable Format Fallback', - help='Raises error on format convertion failure. Prevents returning \ - original report file type if no convertion is available.' - ) - extras = fields.Char('Extra options', compute='_get_extras', size=256) - deferred = fields.Selection( - [('off', _('Off')), - ('adaptive', _('Adaptive'))], - 'Deferred', - help='Deferred (aka Batch) reporting, for reporting on large amount of data.', - default='off' - ) - deferred_limit = fields.Integer( - 'Deferred Records Limit', - help='Records limit at which you are invited to start the deferred process.', - default=80 - ) - replace_report_id = fields.Many2one( - 'ir.actions.report', - 'Replace Report', - help='Select a report that should be replaced.' - ) - wizard_id = fields.Many2one('ir.actions.act_window', 'Wizard Action') - report_data = fields.Binary(string='Template Content', attachment=True) - - @api.constrains('parser_model') - def _check_parser_model(self): - for rec in self.filtered('parser_model'): - if not rec.env['ir.model'].search( - [('name', '=', rec.parser_model)], limit=1): - raise UserError( - _('Parser model %s not found on database.') % (rec.parser_model) - ) - - def read(self, fields=None, load='_classic_read'): - # ugly hack to avoid report being read when - # we enter a view with report added on print menu - if not fields: - fields = list(self._fields) - fields.remove('report_data') - if 'background_image' in fields: - fields.remove('background_image') - if 'logo' in fields: - fields.remove('logo') - return super().read(fields, load=load) - - @api.onchange('in_format') - def onchange_in_format(self): - # TODO get first available format - self.out_format = False - - def write(self, vals): - - # TODO remove or adapt, it shouldn't be necessary - # if vals.get('report_type') and \ - # orec['report_type'] != vals['report_type']: - # raise UserError(_("Changing report type not allowed!")) - - if 'report_data' in vals and vals['report_data']: - try: - b64decode(vals['report_data']) - except binascii.Error: - vals['report_data'] = False - - return super(ReportAeroo, self).write(vals) diff --git a/report_aeroo/namespace.py b/report_aeroo/namespace.py new file mode 100644 index 0000000..2e4dc5e --- /dev/null +++ b/report_aeroo/namespace.py @@ -0,0 +1,8 @@ + +from types import SimpleNamespace + + +class AerooNamespace(SimpleNamespace): + + def update(self, values): + self.__dict__.update(values) diff --git a/report_aeroo/report/__init__.py b/report_aeroo/report/__init__.py deleted file mode 100644 index 22e4152..0000000 --- a/report_aeroo/report/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Part of Aeroo. See LICENSE file for full copyright and licensing details. - -from . import test_aeroo_report_file diff --git a/report_aeroo/report/test.odt b/report_aeroo/report/test.odt deleted file mode 100644 index a79f7ab..0000000 Binary files a/report_aeroo/report/test.odt and /dev/null differ diff --git a/report_aeroo/report/test_aeroo_report_file.py b/report_aeroo/report/test_aeroo_report_file.py deleted file mode 100644 index e6d5627..0000000 --- a/report_aeroo/report/test_aeroo_report_file.py +++ /dev/null @@ -1,18 +0,0 @@ - -from odoo import api, models - - -class TestAerooReport(models.AbstractModel): - _inherit = 'report.report_aeroo.abstract' - _name = 'report.product_template_printer' - _description = 'report.product_template_printer' - - @api.model - def get_report_values(self, docids, data=None): - report = self.env['ir.actions.report']._get_report_from_name(self._name) - selected_companies = self.env['res.company'].browse(docids) - return { - 'doc_ids': docids, - 'doc_model': report.model, - 'docs': selected_companies, - } diff --git a/report_aeroo/report_parser.py b/report_aeroo/report_parser.py deleted file mode 100644 index f74030c..0000000 --- a/report_aeroo/report_parser.py +++ /dev/null @@ -1,601 +0,0 @@ -# -*- encoding: utf-8 -*- -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -import logging -from io import BytesIO -from PIL import Image -from base64 import b64decode -import time -import datetime -import base64 -from aeroolib.plugins.opendocument import Template, OOSerializer, _filter -from aeroolib import __version__ as aeroolib_version -from currency2text import supported_language -from .docs_client_lib import DOCSConnection -from .exceptions import ConnectionError -from PyPDF2 import PdfFileWriter, PdfFileReader - -from genshi.template.eval import StrictLookup - -from odoo import release as odoo_release -from odoo import api, models, fields, _ -from odoo import tools as tools -from odoo.tools import frozendict -from odoo.tools.misc import formatLang as odoo_fl -from odoo.tools.misc import format_date as odoo_fd -from odoo.tools.safe_eval import safe_eval, time as safeval_time -from odoo.modules.module import load_manifest -from odoo.tools.misc import posix_to_ldml -from odoo.tools.misc import get_lang -from odoo.exceptions import MissingError -# for format_datetime -from odoo.tools.misc import DATE_LENGTH -import babel.dates -import pytz - - -def format_datetime(env, value, lang_code=False, date_format=False, - tz='America/Argentina/Buenos_Aires'): - ''' - This is an adaptation of odoo format_date method but to format datetimes - TODO we should move it to another plase or make it simpler - - :param env: an environment. - :param date, datetime or string value: the date to format. - :param string lang_code: the lang code, if not specified it is extracted from - the environment context. - :param string date_format: the format or the date (LDML format), if not - specified the default format of the lang. - :return: date formatted in the specified format. - :rtype: string - ''' - if not value: - return '' - if isinstance(value, str): - if len(value) < DATE_LENGTH: - return '' - if len(value) > DATE_LENGTH: - # a datetime, convert to correct timezone - value = fields.Datetime.from_string(value) - value = fields.Datetime.context_timestamp(env['res.lang'], value) - else: - value = fields.Datetime.from_string(value) - - lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US') - locale = babel.Locale.parse(lang.code) - if not date_format: - date_format = posix_to_ldml( - '%s %s' % (lang.date_format, lang.time_format), locale=locale - ) - - return babel.dates.format_datetime(value, format=date_format, - locale=locale, tzinfo=tz) - - -_logger = logging.getLogger(__name__) - -mime_dict = {'oo-odt': 'odt', - 'oo-ods': 'ods', - 'oo-pdf': 'pdf', - 'oo-doc': 'doc', - 'oo-xls': 'xls', - 'oo-csv': 'csv', - } - - -class ReportAerooAbstract(models.AbstractModel): - _name = 'report.report_aeroo.abstract' - _description = 'report.report_aeroo.abstract' - - def __filter(self, val): - if isinstance(val, models.BaseModel) and val: - return val.name_get()[0][1] - return _filter(val) - - # Extra Functions ========================================================== - def myset(self, pair): - if isinstance(pair, dict): - self.env.localcontext['storage'].update(pair) - return False - - def myget(self, key): - if key in self.env.localcontext['storage'] and self.env.localcontext[ - 'storage'][key]: - return self.env.localcontext['storage'][key] - return False - - def partner_address(self, partner): - # for backward compatibility - ret = '' - if partner.street: - ret += partner.street - if partner.street2: - if partner.street: - ret += ' - ' + partner.street2 - else: - ret += partner.street2 - if ret != '': - ret += '. ' - - if partner.zip: - ret += '(' + partner.zip + ')' - if partner.city: - if partner.zip: - ret += ' ' + partner.city - else: - ret += partner.city - if partner.state_id: - if partner.city: - ret += ' - ' + partner.state_id.name - else: - ret += partner.state_id.name - if partner.zip or partner.city or partner.state_id: - ret += '. ' - - if partner.country_id: - ret += partner.country_id.name + '.' - - return ret - - def _asarray(self, attr, field): - """ - Returns named field from all objects as a list. - """ - expr = "for o in objects:\n\tvalue_list.append(o.%s)" % field - localspace = {'objects': attr, 'value_list': []} - exec(expr, localspace) - return localspace['value_list'] - - def _average(self, attr, field): - """ - Returns average (arithmetic mean) of fields from all objects in a list. - """ - expr = "for o in objects:\n\tvalue_list.append(o.%s)" % field - localspace = {'objects': attr, 'value_list': []} - exec(expr, localspace) - x = sum(localspace['value_list']) - y = len(localspace['value_list']) - return float(x) / float(y) - - def _asimage(self, field_value, rotate=None, size_x=None, - size_y=None, dpix=96, dpiy=96, - uom='px', hold_ratio=False): - """ - Prepare image for inserting into OpenOffice.org document - """ - def size_by_uom(val, uom, dpi): - if uom == 'px': - result = str(val / dpi) + 'in' - elif uom == 'cm': - result = str(val / 2.54) + 'in' - elif uom == 'in': - result = str(val) + 'in' - return result - ############################################## - if not field_value: - return BytesIO(), 'image/png' - field_value = b64decode(field_value) - tf = BytesIO(field_value) - tf.seek(0) - im = Image.open(tf) - format = im.format.lower() - # usamos dpi fijos porque si no en determinados casos nos achica o - # agranda mucho las imagenes en los reportes (al menos el logo) - # dpi_x, dpi_y = map(float, im.info.get('dpi', (96, 96))) - dpi_x, dpi_y = map(float, (dpix, dpiy)) - try: - if rotate is not None: - im = im.rotate(int(rotate)) - tf.seek(0) - im.save(tf, format) - except Exception as e: - _logger.exception("Error in '_asimage' method \n %s" % str(e)) - - if hold_ratio: - img_ratio = im.size[0] / float(im.size[1]) - if size_x and not size_y: - size_y = size_x / img_ratio - elif not size_x and size_y: - size_x = size_y * img_ratio - elif size_x and size_y: - size_y2 = size_x / img_ratio - size_x2 = size_y * img_ratio - if size_y2 > size_y: - size_x = size_x2 - elif size_x2 > size_x: - size_y = size_y2 - - size_x = size_x and size_by_uom(size_x, uom, dpi_x) \ - or str(im.size[0] / dpi_x) + 'in' - size_y = size_y and size_by_uom(size_y, uom, dpi_y) \ - or str(im.size[1] / dpi_y) + 'in' - return tf, 'image/%s' % format, size_x, size_y - - def _currency_to_text(self, currency): - def c_to_text(sum, currency=currency, language=None): - lang = supported_language.get(language or self._get_lang()) - return str(lang.currency_to_text(sum, currency), "UTF-8") - return c_to_text - - def _get_selection_items(self, kind='items'): - def get_selection_item(obj, field, value=None): - try: - # TODO how to check for list of objects in new API? - if isinstance(obj, models.AbstractModel): - obj = obj[0] - if isinstance(obj, str): - model = obj - field_val = value - else: - model = obj._name - field_val = getattr(obj, field) - val = self.env[model].fields_get(allfields=[field] - )[field]['selection'] - if kind == 'item': - if field_val: - return dict(val)[field_val] - elif kind == 'items': - return val - return '' - except Exception as e: - _logger.exception( - "Error in '_get_selection_item' method \n %s" % str(e), exc_info=True) - return '' - return get_selection_item - - def _get_log(self, obj, field=None): - if field: - return obj.get_metadata()[0][field] - else: - return obj.get_metadata()[0] - - # / Extra Functions ======================================================== - - def get_docs_conn(self): - icp = self.env.get('ir.config_parameter').sudo() - icpgp = icp.get_param - docs_host = icpgp('aeroo.docs_host') or 'localhost' - docs_port = icpgp('aeroo.docs_port') or '8989' - # docs_auth_type = icpgp('aeroo.docs_auth_type') or False - docs_username = icpgp('aeroo.docs_username') or 'anonymous' - docs_password = icpgp('aeroo.docs_password') or 'anonymous' - return DOCSConnection( - docs_host, docs_port, username=docs_username, - password=docs_password) - - def _generate_doc(self, data, report): - docs = self.get_docs_conn() - # token = docs.upload(data) - if report.out_format.code == 'oo-dbf': - data = docs.convert( - # identifier=token - data=data - ) # TODO What format? - else: - data = docs.convert( - # identifier=token, - data=data, - out_mime=mime_dict[report.out_format.code], - in_mime=mime_dict[report.in_format] - ) - - # TODO this copies method could go to a generic module because it just - # manipulates the outgoing pdf report - if mime_dict[report.out_format.code] == 'pdf' and report.copies > 1: - output = PdfFileWriter() - reader = PdfFileReader(BytesIO(data)) - copies_intercalate = report.copies_intercalate - copies = report.copies - if copies_intercalate: - for copy in range(copies): - for page in range(reader.getNumPages()): - output.addPage(reader.getPage(page)) - else: - for page in range(reader.getNumPages()): - for copy in range(copies): - output.addPage(reader.getPage(page)) - s = BytesIO() - output.write(s) - data = s.getvalue() - - return data - - def _get_lang(self, source='current'): - if source == 'current': - return self.env.context['lang'] or self.env.context['user_lang'] - elif source == 'company': - return self.env.company.partner_id.lang - elif source == 'user': - return self.env.context['user_lang'] - - def _set_lang(self, lang, obj=None): - self.env.localcontext.update(lang=lang) - if obj is None and 'objects' in self.env.localcontext: - obj = self.env.localcontext['objects'] - if obj and obj.env.context['lang'] != lang: - ctx_copy = dict(self.env.context) - ctx_copy.update(lang=lang) - obj.env.context = frozendict(ctx_copy) - # desactivamos el invalidate_cache porque rompe mail compose cuando - # el idioma del partner de la compañia del usuario - # (obj.env.context['lang']) es distinta al idioma del modelo (lang) - # y tampoco vimos necesidad de este invalidate_cache por ahora - # obj.invalidate_cache() - - def _format_lang( - self, value, digits=None, grouping=True, monetary=False, dp=False, - currency_obj=False, date=False, date_time=False, lang_code=False, - date_format=False): - """ We add date and date_time for backwards compatibility. Odoo has - split the method in two (formatlang and format_date) - """ - if date: - # we force the timezone of the user if the value is datetime - if isinstance(value, (datetime.datetime)): - value = value.astimezone(pytz.timezone(self.env.user.tz or 'UTC')) - return odoo_fd(self.env, value, lang_code=lang_code, - date_format=date_format) - elif date_time: - return format_datetime(self.env, value, lang_code=lang_code, - date_format=date_format, tz=self.env.user.tz) - return odoo_fl( - self.env, value, digits, grouping, monetary, dp, currency_obj) - - def _set_objects(self, model, docids): - _logger.log( - 25, 'AEROO setobjects======================= %s - %s', - model, docids) - lctx = self.env.localcontext - lang = lctx['lang'] - objects = None - env_lang = self.env.user.lang or get_lang(self.env).code - if env_lang != lang: - ctx_copy = dict(self.env.context) - ctx_copy.update(lang=lang) - objects = self.env.get(model).with_context(**ctx_copy).browse(docids) - else: - objects = self.env.get(model).browse(docids) - lctx['objects'] = objects - lctx['o'] = objects and objects[0] or None - _logger.log( - 25, 'AEROO setobjects======================= %s', lang) - - def test(self, obj): - _logger.exception( - 'AEROO TEST1======================= %s - %s' % - (type(obj), - id(obj))) - _logger.exception('AEROO TEST2======================= %s' % (obj,)) - - def get_other_template(self, model, rec_id): - if not hasattr(self, 'get_template'): - return False - record = self.env.get(model).browse(rec_id) - template = self.get_template(record) - return template - - def get_stylesheet(self, report): - style_io = None - if report.styles_mode != 'default': - if report.styles_mode == 'global': - styles = self.env.company.stylesheet_id - elif report.styles_mode == 'specified': - styles = report.stylesheet_id - if styles: - style_io = b64decode(styles.report_styles or False) - return style_io - - def complex_report(self, docids, data, report, ctx): - """ Returns an aeroo report generated by aeroolib - """ - - self.env.model = ctx.get('active_model', False) - self.env.report = report - - def barcode( - barcode_type, value, width=600, height=100, dpi_x=96, dpi_y=96, - humanreadable=0): - # TODO check that asimage and barcode both accepts width and height - img = self.env['ir.actions.report'].barcode( - barcode_type, value, width=width, height=height, - humanreadable=humanreadable) - return self._asimage(base64.b64encode(img), dpix=dpi_x, dpiy=dpi_y) - self.env.localcontext = { - 'myset': self.myset, - 'myget': self.myget, - 'partner_address': self.partner_address, - 'storage': {}, - 'user': self.env.user, - 'user_lang': ctx.get('lang', self.env.user.lang), - 'data': data, - - 'time': time, - 'datetime': datetime, - 'average': self._average, - 'currency_to_text': self._currency_to_text, - 'asimage': self._asimage, - 'get_selection_item': self._get_selection_items('item'), - 'get_selection_items': self._get_selection_items(), - 'get_log': self._get_log, - 'asarray': self._asarray, - - '__filter': self.__filter, # Don't use in the report template! - 'getLang': self._get_lang, - 'setLang': self._set_lang, - 'formatLang': self._format_lang, - 'test': self.test, - 'fields': fields, - 'company': self.env.company, - 'barcode': barcode, - 'tools': tools, - } - self.env.localcontext.update(ctx) - self._set_lang(self.env.company.partner_id.lang) - self._set_objects(self.env.model, docids) - - file_data = None - if report.tml_source == 'database': - if not report.report_data or report.report_data == 'False': - # TODO log report ID etc. - raise MissingError( - _("Aeroo Reports could'nt find report template")) - file_data = b64decode(report.report_data) - elif report.tml_source == 'file': - if not report.report_file or report.report_file == 'False': - # TODO log report ID etc. - raise MissingError( - _("No Aeroo Reports template filename provided")) - file_data = report._read_template() - elif report.tml_source == 'attachment': - file_data = b64decode(report.attachment_id.datas) - else: - rec_id = ctx.get('active_id', data.get('id')) or data.get('id') - file_data = self.get_other_template(self.env.model, rec_id) - - if not file_data: - # TODO log report ID etc. - raise MissingError(_("Aeroo Reports could'nt find report template")) - - template_io = BytesIO(file_data) - if report.styles_mode == 'default': - serializer = OOSerializer(template_io) - else: - style_io = BytesIO(self.get_stylesheet(report)) - serializer = OOSerializer(template_io, oo_styles=style_io) - - basic = Template(source=template_io, - serializer=serializer, - lookup=StrictLookup - ) - - # Add metadata - ser = basic.Serializer - model_obj = self.env.get('ir.model') - model_name = model_obj.sudo().search([('model', '=', self.env.model)])[0].name - ser.add_title(model_name) - - user_name = self.env.user.name - ser.add_creation_user(user_name) - - module_info = load_manifest('report_aeroo') - version = module_info['version'] - ser.add_generator_info('Aeroo Lib/%s Aeroo Reports/%s' - % (aeroolib_version, version)) - ser.add_custom_property('Aeroo Reports %s' % version, 'Generator') - ser.add_custom_property('Odoo %s' % odoo_release.version, 'Software') - ser.add_custom_property(module_info['website'], 'URL') - ser.add_creation_date(time.strftime('%Y-%m-%dT%H:%M:%S')) - - file_data = basic.generate(**self.env.localcontext).render().getvalue() - code = mime_dict[report.in_format] - - return file_data, code - - def simple_report(self, docids, data, report, ctx, output='raw'): - pass - - def single_report(self, docids, data, report, ctx): - code = report.out_format.code - mime_dict[code] - if code.startswith('oo-'): - return self.complex_report(docids, data, report, ctx) - elif code == 'genshi-raw': - return self.simple_report(docids, data, report, ctx, output='raw') - - def assemble_tasks(self, docids, data, report, ctx): - code = report.out_format.code - result = self.single_report(docids, data, report, ctx) - return_filename = self._context.get('return_filename') - - print_report_name = 'report' - if report.print_report_name and not len(docids) > 1: - obj = self.env[report.model].browse(docids) - print_report_name = safe_eval( - report.print_report_name, {'object': obj, 'time': safeval_time}) - - if report.in_format == code: - filename = '%s.%s' % ( - print_report_name, mime_dict[report.in_format]) - return (return_filename and (result[0], result[1], filename) - or (result[0], result[1])) - else: - try: - result = self._generate_doc(result[0], report) - filename = '%s.%s' % ( - print_report_name, mime_dict[report.out_format.code]) - return (return_filename and (result, mime_dict[code], filename) - or (result, mime_dict[code])) - except Exception as e: - _logger.exception(_("Aeroo DOCS error!\n%s") % str(e)) - if report.disable_fallback: - result = None - _logger.exception(e[0]) - raise ConnectionError(_('Could not connect Aeroo DOCS!')) - # only if fallback - filename = '%s.%s' % (print_report_name, mime_dict[report.in_format]) - return (return_filename and (result[0], result[1], filename) - or (result[0], result[1])) - - @api.model - def aeroo_report(self, docids, data): - report_name = self._context.get('report_name') - report = self.env['ir.actions.report']._get_report_from_name(report_name) - # TODO - # _logger.info("Start Aeroo Reports %s (%s)" % (name, ctx.get('active_model')), - # logging.INFO) # debug mode - - if 'tz' not in self._context: - self = self.with_context(tz=self.env.user.tz) - - # TODO we should propagate context in the proper way, just with self - - # agregamos el process_sep aca ya que necesitamos el doc convertido - # para poder unirlos - if report.process_sep and len(docids) > 1: - # por ahora solo soportamos process_sep para pdf, en version - # anterior tambien soportaba algun otro - code = report.out_format.code - if code != 'oo-pdf': - raise MissingError(_( - 'Process_sep not compatible with selected output format')) - - results = [] - for docid in docids: - results.append( - self.assemble_tasks([docid], data, report, self._context)) - output = PdfFileWriter() - for r in results: - reader = PdfFileReader(BytesIO(r[0])) - for page in range(reader.getNumPages()): - output.addPage(reader.getPage(page)) - s = BytesIO() - output.write(s) - data = s.getvalue() - res = self._context.get('return_filename') and\ - (data, results[0][1], results[0][2]) or (data, results[0][1]) - else: - res = self.assemble_tasks(docids, data, report, self._context) - # TODO - # _logger.info("End Aeroo Reports %s (%s), total elapsed time: %s" - # % (name, model), time() - aeroo_print.start_total_time), logging.INFO) - - return res - - # @api.model - # def get_report_values(self, docids, data=None): - # # report = self.env['ir.actions.report']._get_report_from_name( - # # 'account_test.report_accounttest') - # # records = self.env['accounting.assert.test'].browse(self.ids) - # return { - # # 'doc_ids': self._ids, - # 'doc_ids': docids, - # # 'doc_model': report.model, - # # 'docs': records, - # 'data': data, - # # 'execute_code': self.execute_code, - # # 'datetime': datetime - # } diff --git a/report_aeroo/security/ir.model.access.csv b/report_aeroo/security/ir.model.access.csv old mode 100755 new mode 100644 index f885f82..5b404d5 --- a/report_aeroo/security/ir.model.access.csv +++ b/report_aeroo/security/ir.model.access.csv @@ -1,7 +1,9 @@ -"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"report_stylesheets","report.stylesheets","model_report_stylesheets","base.group_system",1,1,1,1 -"report_stylesheets_user","report.stylesheets.user","model_report_stylesheets",,1,0,0,0 -"report_mimetypes_system","report.mimetypes.system","model_report_mimetypes","base.group_system",1,1,1,1 -"report_mimetypes_user","report.mimetypes.user","model_report_mimetypes","base.group_user",1,0,0,0 -"docs_config_installer_user","docs.config.installer.user","model_docs_config_installer","base.group_user",1,1,1,1 - +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +aeroo_mimetype_system,aeroo.mimetype.system,model_aeroo_mimetype,base.group_system,1,1,1,1 +aeroo_mimetype_user,aeroo.mimetype.user,model_aeroo_mimetype,base.group_user,1,0,0,0 +manage_ir_actions_report_xml,ir_actions_report_xml,model_ir_actions_report,group_aeroo_manager,1,1,1,1 +manage_ir_actions_todo,ir_actions_todo,base.model_ir_actions_todo,group_aeroo_manager,1,1,1,1 +manage_aeroo_template_line,aeroo_template_line,model_aeroo_template_line,group_aeroo_manager,1,1,1,1 +manage_aeroo_filename_line,aeroo_filename_line,model_aeroo_filename_line,group_aeroo_manager,1,1,1,1 +manage_aeroo_template_line_user,aeroo_template_line,model_aeroo_template_line,base.group_user,1,0,0,0 +manage_aeroo_filename_line_user,aeroo_filename_line,model_aeroo_filename_line,base.group_user,1,0,0,0 diff --git a/report_aeroo/security/security.xml b/report_aeroo/security/security.xml new file mode 100644 index 0000000..26bb13d --- /dev/null +++ b/report_aeroo/security/security.xml @@ -0,0 +1,12 @@ + + + + + Aeroo Manager + + + + + + + diff --git a/report_aeroo/static/description/adhoc.png b/report_aeroo/static/description/adhoc.png deleted file mode 100644 index 1b946c6..0000000 Binary files a/report_aeroo/static/description/adhoc.png and /dev/null differ diff --git a/report_aeroo/static/description/aeroo_report_menu.png b/report_aeroo/static/description/aeroo_report_menu.png new file mode 100644 index 0000000..dffc58f Binary files /dev/null and b/report_aeroo/static/description/aeroo_report_menu.png differ diff --git a/report_aeroo/static/description/besco.png b/report_aeroo/static/description/besco.png deleted file mode 100644 index b886fbf..0000000 Binary files a/report_aeroo/static/description/besco.png and /dev/null differ diff --git a/report_aeroo/static/description/cysfuturo.png b/report_aeroo/static/description/cysfuturo.png deleted file mode 100644 index f4fdfa9..0000000 Binary files a/report_aeroo/static/description/cysfuturo.png and /dev/null differ diff --git a/report_aeroo/static/description/default_filename.png b/report_aeroo/static/description/default_filename.png new file mode 100644 index 0000000..bbc3563 Binary files /dev/null and b/report_aeroo/static/description/default_filename.png differ diff --git a/report_aeroo/static/description/email_template_form.png b/report_aeroo/static/description/email_template_form.png new file mode 100644 index 0000000..de63f31 Binary files /dev/null and b/report_aeroo/static/description/email_template_form.png differ diff --git a/report_aeroo/static/description/filename.png b/report_aeroo/static/description/filename.png new file mode 100644 index 0000000..6ec0b9f Binary files /dev/null and b/report_aeroo/static/description/filename.png differ diff --git a/report_aeroo/static/description/filename_per_language.png b/report_aeroo/static/description/filename_per_language.png new file mode 100644 index 0000000..81ac201 Binary files /dev/null and b/report_aeroo/static/description/filename_per_language.png differ diff --git a/report_aeroo/static/description/flectra.png b/report_aeroo/static/description/flectra.png deleted file mode 100644 index a8abecb..0000000 Binary files a/report_aeroo/static/description/flectra.png and /dev/null differ diff --git a/report_aeroo/static/description/form_print_menu.png b/report_aeroo/static/description/form_print_menu.png new file mode 100644 index 0000000..ba89aa1 Binary files /dev/null and b/report_aeroo/static/description/form_print_menu.png differ diff --git a/report_aeroo/static/description/icon.png b/report_aeroo/static/description/icon.png old mode 100755 new mode 100644 index d2a677c..92a86b1 Binary files a/report_aeroo/static/description/icon.png and b/report_aeroo/static/description/icon.png differ diff --git a/report_aeroo/static/description/index.html b/report_aeroo/static/description/index.html deleted file mode 100644 index b9053ea..0000000 --- a/report_aeroo/static/description/index.html +++ /dev/null @@ -1,89 +0,0 @@ -
-
-

Enterprise grade reporting solution for Odoo

-
-
-
-
-

Sponsors of Aeroo Reports port for Odoo v11

-

Special thanks goes to

-
- ADHOC S.A. -
-
- Flectra HQ -
- CYSFuturo - BESCO - Serpent CS -
-
-
-
-
-

The most versatile reporting engine

-
-

Aeroo Reports for Odoo is a comprehensive and versatile reporting engine based on Aeroo Library.

-

It supports most of the current and leacy business document formats. Being it printable invoice, personalized HTML content for e-mail marketing or just an inventory labels - Aeroo Reports can do them all.

-

Even more, using RAW reporting option, you can create reports for your custom document format, that gives full advantage of integrating bost office & industrial printing hardware and software.

-
-
-

Batteries Included

-

Developing new reports is as easy as using mainstream office packages - OpenOffice.org/LibreOffice. That means, use them as WYSIWYG template editor.

-

For more information on how this technology differs from other reporting options, please reference reporting engine comparison matrix: - Odoo reporting engines comparison matrix. -

-

-
-
-
-
-
-

Other Modules:

-
    -
  • - Aeroo Reports Direct Print
  • -
  • - Aeroo Reports Prinscreen
  • -
  • - Aeroo Reports demo
  • -
-

Output formats:

-
    -
  • - Open Document Format (ODF) - .odt, .ods;
  • -
  • - Any ASCII based formats, like HTML, CSV, etc.
  • -
  • - using Aeroo DOCS - PDF, DOC, XLS, CSV.
  • -
-

Input - Output format pairs:

-
    -
  • - odt - odt/doc/pdf;
  • -
  • - ods - ods/xls/pdf/csv;
  • -
  • - html - html;
  • -
  • - raw - raw;
  • -
-
-
-

Features:

-
    -
  • - Add reports from UI "on the fly";
  • -
  • - Install reports from module;
  • -
  • - Dynamic template load/unload;
  • -
  • - Extra Functions;
  • -
  • - Same button- different templates;
  • -
  • - Powerful stylesheet system;
  • -
  • - Global or local stylesheets;
  • -
  • - User defined parsers;
  • -
  • - Report deactivation;
  • -
  • - Optional format fallback;
  • -
  • - Add/Remove print button;
  • -
  • - Test report wizard;
  • -
  • - Translatable reports;
  • -
  • - Translation export;
  • -
  • - Number of copies;
  • -
  • - Universal Report wizard;
  • -
  • - Override report file extension;
  • -
  • - Select Input/Output format;
  • -
-
-
-
- -
-
diff --git a/report_aeroo/static/description/invoice_print_button.png b/report_aeroo/static/description/invoice_print_button.png new file mode 100644 index 0000000..18a0d36 Binary files /dev/null and b/report_aeroo/static/description/invoice_print_button.png differ diff --git a/report_aeroo/static/description/invoice_url_model.png b/report_aeroo/static/description/invoice_url_model.png new file mode 100644 index 0000000..59278de Binary files /dev/null and b/report_aeroo/static/description/invoice_url_model.png differ diff --git a/report_aeroo/static/description/libreoffice_calc_insert_link.png b/report_aeroo/static/description/libreoffice_calc_insert_link.png new file mode 100644 index 0000000..fd3da3f Binary files /dev/null and b/report_aeroo/static/description/libreoffice_calc_insert_link.png differ diff --git a/report_aeroo/static/description/libreoffice_calc_insert_link_2.png b/report_aeroo/static/description/libreoffice_calc_insert_link_2.png new file mode 100644 index 0000000..c5cfc76 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_calc_insert_link_2.png differ diff --git a/report_aeroo/static/description/libreoffice_calc_with_links.png b/report_aeroo/static/description/libreoffice_calc_with_links.png new file mode 100644 index 0000000..e3d2f99 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_calc_with_links.png differ diff --git a/report_aeroo/static/description/libreoffice_date_field.png b/report_aeroo/static/description/libreoffice_date_field.png new file mode 100644 index 0000000..087f73d Binary files /dev/null and b/report_aeroo/static/description/libreoffice_date_field.png differ diff --git a/report_aeroo/static/description/libreoffice_for_loop.png b/report_aeroo/static/description/libreoffice_for_loop.png new file mode 100644 index 0000000..51b4b08 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_for_loop.png differ diff --git a/report_aeroo/static/description/libreoffice_for_loop_reference.png b/report_aeroo/static/description/libreoffice_for_loop_reference.png new file mode 100644 index 0000000..b17c13e Binary files /dev/null and b/report_aeroo/static/description/libreoffice_for_loop_reference.png differ diff --git a/report_aeroo/static/description/libreoffice_frame_barcode_options.png b/report_aeroo/static/description/libreoffice_frame_barcode_options.png new file mode 100644 index 0000000..956d12f Binary files /dev/null and b/report_aeroo/static/description/libreoffice_frame_barcode_options.png differ diff --git a/report_aeroo/static/description/libreoffice_frame_options.png b/report_aeroo/static/description/libreoffice_frame_options.png new file mode 100644 index 0000000..5160e7e Binary files /dev/null and b/report_aeroo/static/description/libreoffice_frame_options.png differ diff --git a/report_aeroo/static/description/libreoffice_frame_qrcode_options.png b/report_aeroo/static/description/libreoffice_frame_qrcode_options.png new file mode 100644 index 0000000..5e9c276 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_frame_qrcode_options.png differ diff --git a/report_aeroo/static/description/libreoffice_frame_qrcode_type.png b/report_aeroo/static/description/libreoffice_frame_qrcode_type.png new file mode 100644 index 0000000..a493708 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_frame_qrcode_type.png differ diff --git a/report_aeroo/static/description/libreoffice_frame_type.png b/report_aeroo/static/description/libreoffice_frame_type.png new file mode 100644 index 0000000..7c8930e Binary files /dev/null and b/report_aeroo/static/description/libreoffice_frame_type.png differ diff --git a/report_aeroo/static/description/libreoffice_group_by.png b/report_aeroo/static/description/libreoffice_group_by.png new file mode 100644 index 0000000..384b4d5 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_group_by.png differ diff --git a/report_aeroo/static/description/libreoffice_if_statement.png b/report_aeroo/static/description/libreoffice_if_statement.png new file mode 100644 index 0000000..6b2dd35 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_if_statement.png differ diff --git a/report_aeroo/static/description/libreoffice_if_statement_end.png b/report_aeroo/static/description/libreoffice_if_statement_end.png new file mode 100644 index 0000000..d8df78e Binary files /dev/null and b/report_aeroo/static/description/libreoffice_if_statement_end.png differ diff --git a/report_aeroo/static/description/libreoffice_if_statement_reference.png b/report_aeroo/static/description/libreoffice_if_statement_reference.png new file mode 100644 index 0000000..8feb0c2 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_if_statement_reference.png differ diff --git a/report_aeroo/static/description/libreoffice_image_resize.png b/report_aeroo/static/description/libreoffice_image_resize.png new file mode 100644 index 0000000..b50dee6 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_image_resize.png differ diff --git a/report_aeroo/static/description/libreoffice_insert_field.png b/report_aeroo/static/description/libreoffice_insert_field.png new file mode 100644 index 0000000..416827b Binary files /dev/null and b/report_aeroo/static/description/libreoffice_insert_field.png differ diff --git a/report_aeroo/static/description/libreoffice_insert_field_placeholder.png b/report_aeroo/static/description/libreoffice_insert_field_placeholder.png new file mode 100644 index 0000000..f27c73a Binary files /dev/null and b/report_aeroo/static/description/libreoffice_insert_field_placeholder.png differ diff --git a/report_aeroo/static/description/libreoffice_insert_frame.png b/report_aeroo/static/description/libreoffice_insert_frame.png new file mode 100644 index 0000000..e196e5c Binary files /dev/null and b/report_aeroo/static/description/libreoffice_insert_frame.png differ diff --git a/report_aeroo/static/description/libreoffice_insert_input_field.png b/report_aeroo/static/description/libreoffice_insert_input_field.png new file mode 100644 index 0000000..61db9b0 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_insert_input_field.png differ diff --git a/report_aeroo/static/description/libreoffice_invoice.png b/report_aeroo/static/description/libreoffice_invoice.png new file mode 100644 index 0000000..8168eb7 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_invoice.png differ diff --git a/report_aeroo/static/description/libreoffice_invoice_pdf.png b/report_aeroo/static/description/libreoffice_invoice_pdf.png new file mode 100644 index 0000000..d8501cf Binary files /dev/null and b/report_aeroo/static/description/libreoffice_invoice_pdf.png differ diff --git a/report_aeroo/static/description/libreoffice_number_utilities.png b/report_aeroo/static/description/libreoffice_number_utilities.png new file mode 100644 index 0000000..f10eef8 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_number_utilities.png differ diff --git a/report_aeroo/static/description/libreoffice_placeholder_filled.png b/report_aeroo/static/description/libreoffice_placeholder_filled.png new file mode 100644 index 0000000..001a603 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_placeholder_filled.png differ diff --git a/report_aeroo/static/description/libreoffice_placeholder_insert.png b/report_aeroo/static/description/libreoffice_placeholder_insert.png new file mode 100644 index 0000000..56057ee Binary files /dev/null and b/report_aeroo/static/description/libreoffice_placeholder_insert.png differ diff --git a/report_aeroo/static/description/libreoffice_writer_qrcode.png b/report_aeroo/static/description/libreoffice_writer_qrcode.png new file mode 100644 index 0000000..c319646 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_writer_qrcode.png differ diff --git a/report_aeroo/static/description/libreoffice_writer_qrcode_rendered.png b/report_aeroo/static/description/libreoffice_writer_qrcode_rendered.png new file mode 100644 index 0000000..1a58e16 Binary files /dev/null and b/report_aeroo/static/description/libreoffice_writer_qrcode_rendered.png differ diff --git a/report_aeroo/static/description/list_view_report.png b/report_aeroo/static/description/list_view_report.png new file mode 100644 index 0000000..ca9d9a3 Binary files /dev/null and b/report_aeroo/static/description/list_view_report.png differ diff --git a/report_aeroo/static/description/list_view_report_pdf.png b/report_aeroo/static/description/list_view_report_pdf.png new file mode 100644 index 0000000..a23018c Binary files /dev/null and b/report_aeroo/static/description/list_view_report_pdf.png differ diff --git a/report_aeroo/static/description/list_view_standard_report.png b/report_aeroo/static/description/list_view_standard_report.png new file mode 100644 index 0000000..8ef3337 Binary files /dev/null and b/report_aeroo/static/description/list_view_standard_report.png differ diff --git a/report_aeroo/static/description/list_view_standard_report_pdf.png b/report_aeroo/static/description/list_view_standard_report_pdf.png new file mode 100644 index 0000000..571ffd9 Binary files /dev/null and b/report_aeroo/static/description/list_view_standard_report_pdf.png differ diff --git a/report_aeroo/static/description/report_add_print_menu.png b/report_aeroo/static/description/report_add_print_menu.png new file mode 100644 index 0000000..90137f8 Binary files /dev/null and b/report_aeroo/static/description/report_add_print_menu.png differ diff --git a/report_aeroo/static/description/report_attachment.png b/report_aeroo/static/description/report_attachment.png new file mode 100644 index 0000000..903e1c5 Binary files /dev/null and b/report_aeroo/static/description/report_attachment.png differ diff --git a/report_aeroo/static/description/report_attachment_filename.png b/report_aeroo/static/description/report_attachment_filename.png new file mode 100644 index 0000000..af4d05f Binary files /dev/null and b/report_aeroo/static/description/report_attachment_filename.png differ diff --git a/report_aeroo/static/description/report_attachment_filename_multi.png b/report_aeroo/static/description/report_attachment_filename_multi.png new file mode 100644 index 0000000..30e36e5 Binary files /dev/null and b/report_aeroo/static/description/report_attachment_filename_multi.png differ diff --git a/report_aeroo/static/description/report_context.png b/report_aeroo/static/description/report_context.png new file mode 100644 index 0000000..822aa91 Binary files /dev/null and b/report_aeroo/static/description/report_context.png differ diff --git a/report_aeroo/static/description/report_context_country_and_currency.png b/report_aeroo/static/description/report_context_country_and_currency.png new file mode 100644 index 0000000..cb3f69c Binary files /dev/null and b/report_aeroo/static/description/report_context_country_and_currency.png differ diff --git a/report_aeroo/static/description/report_context_partner.png b/report_aeroo/static/description/report_context_partner.png new file mode 100644 index 0000000..bee0dc7 Binary files /dev/null and b/report_aeroo/static/description/report_context_partner.png differ diff --git a/report_aeroo/static/description/report_context_user.png b/report_aeroo/static/description/report_context_user.png new file mode 100644 index 0000000..4d3bd84 Binary files /dev/null and b/report_aeroo/static/description/report_context_user.png differ diff --git a/report_aeroo/static/description/report_form.png b/report_aeroo/static/description/report_form.png new file mode 100644 index 0000000..be1d637 Binary files /dev/null and b/report_aeroo/static/description/report_form.png differ diff --git a/report_aeroo/static/description/report_from_list_view.png b/report_aeroo/static/description/report_from_list_view.png new file mode 100644 index 0000000..3cf3c44 Binary files /dev/null and b/report_aeroo/static/description/report_from_list_view.png differ diff --git a/report_aeroo/static/description/report_from_record_list.png b/report_aeroo/static/description/report_from_record_list.png new file mode 100644 index 0000000..5957795 Binary files /dev/null and b/report_aeroo/static/description/report_from_record_list.png differ diff --git a/report_aeroo/static/description/report_from_record_list_template.png b/report_aeroo/static/description/report_from_record_list_template.png new file mode 100644 index 0000000..a470725 Binary files /dev/null and b/report_aeroo/static/description/report_from_record_list_template.png differ diff --git a/report_aeroo/static/description/report_ods.png b/report_aeroo/static/description/report_ods.png new file mode 100644 index 0000000..5ae3ae3 Binary files /dev/null and b/report_aeroo/static/description/report_ods.png differ diff --git a/report_aeroo/static/description/report_ods_rendered.png b/report_aeroo/static/description/report_ods_rendered.png new file mode 100644 index 0000000..77eff9b Binary files /dev/null and b/report_aeroo/static/description/report_ods_rendered.png differ diff --git a/report_aeroo/static/description/report_output_mime_type.png b/report_aeroo/static/description/report_output_mime_type.png new file mode 100644 index 0000000..5aef6cb Binary files /dev/null and b/report_aeroo/static/description/report_output_mime_type.png differ diff --git a/report_aeroo/static/description/report_reload_from_attachment.png b/report_aeroo/static/description/report_reload_from_attachment.png new file mode 100644 index 0000000..bb1dd1f Binary files /dev/null and b/report_aeroo/static/description/report_reload_from_attachment.png differ diff --git a/report_aeroo/static/description/report_technical_name.png b/report_aeroo/static/description/report_technical_name.png new file mode 100644 index 0000000..8c3b59e Binary files /dev/null and b/report_aeroo/static/description/report_technical_name.png differ diff --git a/report_aeroo/static/description/report_template_database.png b/report_aeroo/static/description/report_template_database.png new file mode 100644 index 0000000..f4ca4e1 Binary files /dev/null and b/report_aeroo/static/description/report_template_database.png differ diff --git a/report_aeroo/static/description/report_template_file.png b/report_aeroo/static/description/report_template_file.png new file mode 100644 index 0000000..9b141ad Binary files /dev/null and b/report_aeroo/static/description/report_template_file.png differ diff --git a/report_aeroo/static/description/report_template_mime_type.png b/report_aeroo/static/description/report_template_mime_type.png new file mode 100644 index 0000000..5ca452d Binary files /dev/null and b/report_aeroo/static/description/report_template_mime_type.png differ diff --git a/report_aeroo/static/description/report_template_multi.png b/report_aeroo/static/description/report_template_multi.png new file mode 100644 index 0000000..f6f84b6 Binary files /dev/null and b/report_aeroo/static/description/report_template_multi.png differ diff --git a/report_aeroo/static/description/report_template_multi_filled.png b/report_aeroo/static/description/report_template_multi_filled.png new file mode 100644 index 0000000..d8dc803 Binary files /dev/null and b/report_aeroo/static/description/report_template_multi_filled.png differ diff --git a/report_aeroo/static/description/report_template_multi_form.png b/report_aeroo/static/description/report_template_multi_form.png new file mode 100644 index 0000000..4acb6e5 Binary files /dev/null and b/report_aeroo/static/description/report_template_multi_form.png differ diff --git a/report_aeroo/static/description/report_template_options.png b/report_aeroo/static/description/report_template_options.png new file mode 100644 index 0000000..67d30b7 Binary files /dev/null and b/report_aeroo/static/description/report_template_options.png differ diff --git a/report_aeroo/static/description/serpentcs.png b/report_aeroo/static/description/serpentcs.png deleted file mode 100644 index d4820da..0000000 Binary files a/report_aeroo/static/description/serpentcs.png and /dev/null differ diff --git a/report_aeroo/static/img/logo.png b/report_aeroo/static/img/logo.png new file mode 100644 index 0000000..1d34cb3 Binary files /dev/null and b/report_aeroo/static/img/logo.png differ diff --git a/report_aeroo/static/src/js/report/reportactionmanager.js b/report_aeroo/static/src/js/action_manager.js similarity index 90% rename from report_aeroo/static/src/js/report/reportactionmanager.js rename to report_aeroo/static/src/js/action_manager.js index 384e19a..0d5b1a1 100644 --- a/report_aeroo/static/src/js/report/reportactionmanager.js +++ b/report_aeroo/static/src/js/action_manager.js @@ -20,9 +20,10 @@ async function aerooReportHandler (action, options, env){ env.services.ui.block(); try { await download({ - url: "/report/download", + url: "/web/report_aeroo", data: { - data: JSON.stringify([url_, action.report_type]), + report_id : cloned_action.id, + record_ids: JSON.stringify(cloned_action.context.active_ids), context: JSON.stringify(env.services.user.context), }, }); @@ -45,4 +46,4 @@ async function aerooReportHandler (action, options, env){ } } -registry.category("ir.actions.report handlers").add("aeroo_handler", aerooReportHandler); +registry.category("ir.actions.report handlers").add("aeroo_handler", aerooReportHandler); \ No newline at end of file diff --git a/report_aeroo/test_temp.odt b/report_aeroo/test_temp.odt deleted file mode 100755 index 4c2b5cb..0000000 Binary files a/report_aeroo/test_temp.odt and /dev/null differ diff --git a/report_aeroo/tests/__init__.py b/report_aeroo/tests/__init__.py new file mode 100644 index 0000000..b5aa7a2 --- /dev/null +++ b/report_aeroo/tests/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_email_template +from . import test_extra_functions +from . import test_load_views +from . import test_report_aeroo +from . import test_report_aeroo_access +from . import test_report_aeroo_company_eval +from . import test_report_aeroo_lang_eval +from . import test_report_aeroo_multi diff --git a/report_aeroo/tests/test_email_template.py b/report_aeroo/tests/test_email_template.py new file mode 100644 index 0000000..77c57eb --- /dev/null +++ b/report_aeroo/tests/test_email_template.py @@ -0,0 +1,25 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestMailTemplateWithAerooReport(TransactionCase): + """Test generating an aeroo report from a list of records.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner_1 = cls.env["res.partner"].create({"name": "Partner 1"}) + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report_multi") + cls.template = cls.env["mail.template"].create( + { + "name": "Partner Email", + "model_id": cls.env.ref("base.model_res_partner").id, + "aeroo_report_ids": [(4, cls.report.id)], + } + ) + + def test_generate_mail_template(self): + res = self.template.generate_email([self.partner_1.id], ["body_html"]) + self.assertEqual(len(res[self.partner_1.id]["attachments"]), 1) diff --git a/report_aeroo/tests/test_extra_functions.py b/report_aeroo/tests/test_extra_functions.py new file mode 100644 index 0000000..4b9f043 --- /dev/null +++ b/report_aeroo/tests/test_extra_functions.py @@ -0,0 +1,320 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import io +import pytest +from datetime import datetime, date +from dateutil.relativedelta import relativedelta +from ddt import data, ddt, unpack +from freezegun import freeze_time +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase +from ..extra_functions import ( + barcode, + qrcode, + format_date, + format_date_today, + format_datetime, + format_datetime_now, + format_decimal, + format_currency, + format_hours, + format_html2text, + group_by, +) + + +@ddt +class TestAerooReport(TransactionCase): + def setUp(self): + super().setUp() + self.report = self.env.ref("report_aeroo.aeroo_sample_report") + + @data( + (0, "00:00"), + (2.25, "02:15"), + (-2.25, "-02:15"), + (100, "100:00"), + ) + @unpack + def test_format_hours(self, number, result): + self.assertEqual(format_hours(self.report, number), result) + + def test_format_date_fr(self): + report = self.report.with_context(lang="fr_CA") + result = format_date(report, date(2018, 4, 6), "d MMMM yyyy") + self.assertEqual(result, "6 avril 2018") + + def test_format_date_en(self): + report = self.report.with_context(lang="en_US") + result = format_date(report, date(2018, 4, 6), "d MMMM yyyy") + self.assertEqual(result, "6 April 2018") + + def test_format_date_today_fr_in_utc(self): + with freeze_time("2018-04-06 00:00:00"): + report = self.report.with_context(lang="fr_CA", tz="UTC") + result = format_date_today(report, "d MMMM yyyy") + self.assertEqual(result, "6 avril 2018") + + def test_format_date_today_fr_in_specific_timezone(self): + with freeze_time("2018-04-06 00:00:00"): + report = self.report.with_context(lang="fr_CA", tz="Canada/Eastern") + result = format_date_today(report, "d MMMM yyyy") + self.assertEqual(result, "5 avril 2018") # Canada/Eastern = UTC - 4 hours + + def test_format_date_today_with_delta(self): + delta = relativedelta(months=1) + with freeze_time("2018-04-06 00:00:00"): + report = self.report.with_context(lang="fr_CA", tz="UTC") + result = format_date_today(report, "d MMMM yyyy", delta=delta) + self.assertEqual(result, "6 mai 2018") + + def test_format_datetime_fr(self): + report = self.report.with_context(lang="fr_CA", tz="UTC") + result = format_datetime( + report, datetime(2018, 4, 6, 10, 34), "d MMMM yyyy hh:mm a" + ) + self.assertEqual(result, "6 avril 2018 10:34 AM") + + def test_format_datetime_en(self): + report = self.report.with_context(lang="en_US", tz="UTC") + result = format_datetime( + report, datetime(2018, 4, 6, 10, 34), "d MMMM yyyy hh:mm a" + ) + self.assertEqual(result, "6 April 2018 10:34 AM") + + def test_format_datetime_en_in_specific_timezone(self): + report = self.report.with_context(lang="en_US", tz="Canada/Eastern") + result = format_datetime( + report, datetime(2018, 4, 6, 10, 34), "d MMMM yyyy hh:mm a" + ) + self.assertEqual( + result, "6 April 2018 06:34 AM" + ) # Canada/Eastern = UTC - 4 hours + + def test_format_datetime_now_fr_in_utc(self): + with freeze_time(datetime(2018, 4, 6, 10, 34)): + report = self.report.with_context(lang="fr_CA", tz="UTC") + result = format_datetime_now(report, "d MMMM yyyy hh:mm a") + self.assertEqual(result, "6 avril 2018 10:34 AM") + + def test_format_datetime_now_fr_in_specific_timezone(self): + with freeze_time(datetime(2018, 4, 6, 10, 34)): + report = self.report.with_context(lang="fr_CA", tz="Canada/Eastern") + result = format_datetime_now(report, "d MMMM yyyy hh:mm a") + self.assertEqual( + result, "6 avril 2018 06:34 AM" + ) # Canada/Eastern = UTC - 4 hours + + def test_format_datetime_now_with_delta(self): + delta = relativedelta(months=1) + with freeze_time(datetime(2018, 4, 6, 10, 34)): + report = self.report.with_context(lang="fr_CA", tz="UTC") + result = format_datetime_now(report, "d MMMM yyyy hh:mm a", delta) + self.assertEqual(result, "6 mai 2018 10:34 AM") + + def test_format_decimal_fr(self): + report = self.report.with_context(lang="fr_CA") + result = format_decimal(report, 1500) + self.assertEqual(result, "1\xa0500,00") + + def test_format_decimal_en(self): + report = self.report.with_context(lang="en_US") + result = format_decimal(report, 1500) + self.assertEqual(result, "1,500.00") + + def test_format_decimal_fr_with_format(self): + report = self.report.with_context(lang="fr_CA") + result = format_decimal(report, 1500, amount_format="#,##0.0") + self.assertEqual(result, "1\xa0500,0") + + def test_format_currency_fr(self): + report = self.report.with_context(lang="fr_CA") + result = format_currency(report, 1500, self.env.ref("base.USD")) + self.assertEqual(result, "1\xa0500,00\xa0$US") + + def test_format_currency_en(self): + report = self.report.with_context(lang="en_US") + result = format_currency(report, 1500, self.env.ref("base.USD")) + self.assertEqual(result, "$1,500.00") + + @data( + ("en_US", "base.ca", "base.CAD", "$1,500.00"), + ("en_US", "base.ca", "base.USD", "US$1,500.00"), + ("en_US", "base.us", "base.CAD", "CA$1,500.00"), + ("en_US", "base.us", "base.USD", "$1,500.00"), + ("fr_FR", "base.ca", "base.CAD", "1 500,00 $"), + ("fr_FR", "base.ca", "base.USD", "1 500,00 $US"), + ("fr_FR", "base.us", "base.CAD", "1 500,00 $CA"), + ("fr_FR", "base.us", "base.USD", "1 500,00 $US"), + ) + @unpack + def test_format_currency_en__with_specific_country( + self, lang, country, currency, expected_amount + ): + report = self.report.with_context(lang=lang) + result = format_currency( + report, 1500, self.env.ref(currency), country=self.env.ref(country) + ) + self.assertEqual(result, expected_amount) + + @data( + ("fr_FR", "base.ca", "base.CAD", "1 500,00 $"), + ("fr_FR", "base.us", "base.CAD", "1 500,00 $CA"), + ) + @unpack + def test_country_from_context_used_by_default( + self, lang, country, currency, expected_amount + ): + report = self.report.with_context(lang=lang, country=self.env.ref(country)) + result = format_currency(report, 1500, self.env.ref(currency)) + self.assertEqual(result, expected_amount) + + @data( + ("fr_FR", "base.us", "base.USD", "1 500,00 $US"), + ("fr_FR", "base.us", "base.CAD", "1 500,00 $CA"), + ) + @unpack + def test_currency_from_context_used_by_default( + self, lang, country, currency, expected_amount + ): + report = self.report.with_context(lang=lang, currency=self.env.ref(currency)) + result = format_currency(report, 1500, country=self.env.ref(country)) + self.assertEqual(result, expected_amount) + + def test_if_format_currency_called_without_currency__raise_error(self): + report = self.report.with_context(lang="en_US", country=self.env.ref("base.us")) + with pytest.raises(ValidationError): + format_currency(report, 1500) + + def test_format_currency_fr_with_format(self): + report = self.report.with_context(lang="fr_CA") + result = format_currency( + report, 1500, self.env.ref("base.USD"), amount_format="#,##0.00\xa0¤¤" + ) + self.assertEqual(result, "1\xa0500,00\xa0USD") + + def test_format_html2text(self): + r"""Test the formating of html into text. + + * \n\n is added after the end of a div. + * Line breaks are replaced with 2 spaces and one \n. + * The text is ended with a single \n. + * No \n is added when a line exceeds a given number length (i.e. 79 chars) + """ + html = ( + "
Lorem Ipsum
" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + "Morbi eleifend magna sit amet sem gravida sollicitudin." + "
Vestibulum metus ipsum, varius in ultricies eget, vulputate eu felis." + ) + text = format_html2text(self.report, html) + self.assertEqual( + text, + ( + "Lorem Ipsum" + "\n\n" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + "Morbi eleifend magna sit amet sem gravida sollicitudin. \n" + "Vestibulum metus ipsum, varius in ultricies eget, vulputate eu felis.\n" + ), + ) + + def test_format_html2text_with_none(self): + self.assertEqual(format_html2text(self.report, None), "\n") + + @data( + ("ean13", "501234567890"), + ("code128", "1234"), + ("code39", "1234"), + ) + @unpack + def test_render_barcode(self, barcode_type, code): + result = barcode(self.report, code, barcode_type) + assert isinstance(result[0], io.BytesIO) + + def test_qrcode(self): + result = qrcode(self.report, "1234") + assert isinstance(result[0], io.BytesIO) + + def test_qrcode__specific_dimension(self): + result = qrcode(self.report, "1234", "4.31cm") + assert result[2] == "4.31cm" + assert result[3] == "4.31cm" + + +class TestGroupBy(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report") + cls.contact_1 = cls.env["res.partner"].create({"name": "C1", "type": "contact"}) + cls.invoice_1 = cls.env["res.partner"].create({"name": "I1", "type": "invoice"}) + cls.invoice_2 = cls.env["res.partner"].create({"name": "I2", "type": "invoice"}) + cls.delivery_1 = cls.env["res.partner"].create( + {"name": "D1", "type": "delivery"} + ) + cls.delivery_2 = cls.env["res.partner"].create( + {"name": "D2", "type": "delivery"} + ) + + def test_group_by_with_no_record(self): + partners = self.env["res.partner"] + groups = list(group_by(self.report, partners, lambda p: p.type)) + assert len(groups) == 0 + + def test_group_by_with_one_record(self): + groups = list(group_by(self.report, self.contact_1, lambda p: p.type)) + assert len(groups) == 1 + assert groups[0][0] == "contact" + assert groups[0][1] == self.contact_1 + + def test_group_by_with_multiple_records(self): + partners = ( + self.delivery_1 + | self.contact_1 + | self.invoice_1 + | self.delivery_2 + | self.invoice_2 + ) + + def groupby(p): + return p.type + + groups = list(group_by(self.report, partners, groupby)) + assert len(groups) == 3 + assert groups[0][0] == "contact" + assert groups[0][1] == self.contact_1 + assert groups[1][0] == "delivery" + assert groups[1][1] == self.delivery_1 | self.delivery_2 + assert groups[2][0] == "invoice" + assert groups[2][1] == self.invoice_1 | self.invoice_2 + + def test_group_by_with_custom_sort(self): + partners = ( + self.delivery_1 + | self.contact_1 + | self.invoice_1 + | self.delivery_2 + | self.invoice_2 + ) + + def groupby(p): + return p.type + + def custom_sort(partner_type): + if partner_type == "invoice": + return 1 + elif partner_type == "delivery": + return 2 + else: + return 3 + + groups = list(group_by(self.report, partners, groupby, sort=custom_sort)) + assert len(groups) == 3 + assert groups[0][0] == "invoice" + assert groups[0][1] == self.invoice_1 | self.invoice_2 + assert groups[1][0] == "delivery" + assert groups[1][1] == self.delivery_1 | self.delivery_2 + assert groups[2][0] == "contact" + assert groups[2][1] == self.contact_1 diff --git a/report_aeroo/tests/test_load_views.py b/report_aeroo/tests/test_load_views.py new file mode 100644 index 0000000..a3d4763 --- /dev/null +++ b/report_aeroo/tests/test_load_views.py @@ -0,0 +1,17 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestLoadViews(TransactionCase): + def test_aeroo_template_data_not_in_result(self): + simple_report = self.env.ref("report_aeroo.aeroo_sample_report") + simple_report.create_action() + view = self.env.ref("base.view_partner_tree") + result = self.env["res.partner"].get_views( + [(view.id, "list")], {"toolbar": True} + ) + action_reports = result["views"]["list"]["toolbar"]["print"] + report_ids = [report["id"] for report in action_reports] + assert simple_report.id in report_ids diff --git a/report_aeroo/tests/test_report_aeroo.py b/report_aeroo/tests/test_report_aeroo.py new file mode 100644 index 0000000..351f2c3 --- /dev/null +++ b/report_aeroo/tests/test_report_aeroo.py @@ -0,0 +1,243 @@ +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +from freezegun import freeze_time + +from odoo.exceptions import ValidationError +from odoo.modules import module +from odoo.tests.common import TransactionCase + + +class TestAerooReport(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + image_path = module.get_module_path("report_aeroo") + "/static/img/logo.png" + + cls.company = cls.env["res.company"].create( + { + "name": "My Company", + } + ) + cls.company_2 = cls.env["res.company"].create( + { + "name": "My Company 2", + } + ) + + cls.user = cls.env.ref("base.user_demo") + cls.user.company_ids |= cls.company | cls.company_2 + + cls.partner = cls.env["res.partner"].create( + { + "name": "My Partner", + "lang": "en_US", + "company_id": cls.company.id, + "image_1920": base64.b64encode(open(image_path, "rb").read()), + } + ) + + cls.lang_en = cls.env.ref("base.lang_en") + cls.lang_fr = cls.env.ref("base.lang_fr") + + cls.partner_2 = cls.env["res.partner"].create( + { + "name": "My Partner 2", + "lang": "en_US", + } + ) + + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report") + cls.report.write( + { + "attachment": None, + "attachment_use": False, + "aeroo_lang_eval": "o.lang", + "aeroo_company_eval": "o.company_id", + "aeroo_out_format_id": cls.env.ref( + "report_aeroo.aeroo_mimetype_pdf_odt" + ).id, + } + ) + + def _render_report(self, partners): + """Render the demo aeroo report for the given partners. + + The report is rendered with a basic user to detect issues related to access rights. + + :param partners: a res.partner recordset + """ + self.report._render_aeroo(partners.ids, {}) + + def _create_report_line(self, lang, company=None): + self.report.write( + { + "aeroo_template_source": "lines", + } + ) + return self.env["aeroo.template.line"].create( + { + "report_id": self.report.id, + "lang_id": lang.id if lang else False, + "company_id": company.id if company else False, + "template_data": base64.b64encode( + self.report._get_aeroo_template_from_file() + ), + } + ) + + def test_sample_report_doc(self): + self.report.aeroo_out_format_id = self.env.ref( + "report_aeroo.aeroo_mimetype_doc_odt" + ) + self._render_report(self.partner) + + def test_sample_report_pdf_by_lang(self): + self._create_report_line(self.lang_en) + self._render_report(self.partner) + + def test_ifFirstTemplateLineHasNoLang_thenItIsSelected(self): + line_1 = self._create_report_line(False) + self._create_report_line(self.lang_en) + selected_line = self.report._get_aeroo_template_line(self.partner) + self.assertEqual(selected_line, line_1) + + def test_ifFirstTemplateLineHasWrongLang_thenItIsNotSelected(self): + self._create_report_line(self.lang_fr) + line_2 = self._create_report_line(self.lang_en) + selected_line = self.report._get_aeroo_template_line(self.partner) + self.assertEqual(selected_line, line_2) + + def test_ifFirstTemplateLineHasWrongCompany_thenItIsNotSelected(self): + self._create_report_line(self.lang_en, company=self.company_2) + line_2 = self._create_report_line(self.lang_en, company=self.company) + selected_line = self.report._get_aeroo_template_line(self.partner) + self.assertEqual(selected_line, line_2) + + def test_get_template_line_with_2_lines(self): + self._create_report_line(self.lang_en) + self._render_report(self.partner) + + def test_sample_report_pdf_with_attachment(self): + self.report.write( + { + "attachment_use": True, + "attachment": "${o.name}", + } + ) + self._render_report(self.partner) + + attachment = self.env["ir.attachment"].search( + [ + ("res_id", "=", self.partner.id), + ("res_model", "=", "res.partner"), + ("name", "=", "My Partner.pdf"), + ] + ) + self.assertEqual(len(attachment), 1) + + self._render_report(self.partner) + + def _create_filename_line(self, lang, filename): + self.report.write( + { + "attachment_use": True, + "aeroo_filename_per_lang": True, + "aeroo_filename_line_ids": [ + ( + 0, + 0, + { + "lang_id": lang.id, + "filename": filename, + }, + ), + ], + } + ) + + def _search_attachment(self): + return self.env["ir.attachment"].search( + [ + ("res_id", "=", self.partner.id), + ("res_model", "=", "res.partner"), + ] + ) + + def test_ifPartnerHasNoLang_thenAttachmentNameIsRenderedInEnglish(self): + self.report.aeroo_lang_eval = "None" + + self._create_filename_line(self.lang_fr, "Rapport de contact: ${o.name}") + self._create_filename_line(self.lang_en, "Sample Report: ${o.name}") + + self._render_report(self.partner) + + attachment = self._search_attachment() + self.assertEqual(attachment.name, "Sample Report: My Partner.pdf") + + def test_ifReportHasSpecificLang_thenAttachmentNameIsRenderedInSpecificLang(self): + filename = "Rapport de contact: ${today('d MMMM yyyy')}" + self._create_filename_line(self.lang_fr, filename) + + self.report.aeroo_lang_eval = "'fr_FR'" + self.report.aeroo_tz_eval = "'UTC'" + + with freeze_time("2018-04-06 00:00:00"): + self._render_report(self.partner) + + attachment = self._search_attachment() + self.assertEqual(attachment.name, "Rapport de contact: 6 avril 2018.pdf") + + def test_ifReportHasSpecificTimezone_thenAttachmentNameIsRenderedInSpecificTimezone( + self, + ): + filename = "Sample Report: ${today('MMMM d, yyyy')}" + self._create_filename_line(self.lang_en, filename) + + self.report.aeroo_lang_eval = "'en_US'" + self.report.aeroo_tz_eval = "'Canada/Eastern'" + + with freeze_time("2018-04-06 00:00:00"): + self._render_report(self.partner) + + attachment = self._search_attachment() + self.assertEqual(attachment.name, "Sample Report: April 5, 2018.pdf") + + def test_multicompany_context_with_lang_and_company(self): + self._create_report_line(self.lang_en, self.company) + self._render_report(self.partner) + + def test_multicompany_context_company_not_available(self): + self._create_report_line(self.lang_en, self.company) + self.partner.write({"company_id": self.company_2.id}) + with self.assertRaises(ValidationError): + self._render_report(self.partner) + + def test_multicompany_context_with_lang(self): + self._create_report_line(self.lang_en) + self._render_report(self.partner) + + def test_multicompany_context_lang_not_available(self): + self._create_report_line(self.lang_fr) + with self.assertRaises(ValidationError): + self._render_report(self.partner) + + def test_sample_report_pdf_with_multiple_export(self): + self._render_report(self.partner | self.partner_2) + + def test_context_contains_evaluated_country(self): + country = self.env.ref("base.ca") + self.company.country_id = country + self.report.aeroo_country_eval = "o.company_id.country_id" + context = self.report._get_aeroo_context(self.partner) + assert context["country"] == country + + def test_context_contains_evaluated_currency(self): + currency = self.env.ref("base.CAD") + self.company.currency_id = currency + self.report.aeroo_currency_eval = "o.company_id.currency_id" + context = self.report._get_aeroo_context(self.partner) + assert context["currency"] == currency diff --git a/report_aeroo/tests/test_report_aeroo_access.py b/report_aeroo/tests/test_report_aeroo_access.py new file mode 100644 index 0000000..489a126 --- /dev/null +++ b/report_aeroo/tests/test_report_aeroo_access.py @@ -0,0 +1,22 @@ +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestAerooReportAccess(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report") + cls.user = cls.env.ref("base.user_demo") + cls.user.groups_id |= cls.env.ref("report_aeroo.group_aeroo_manager") + + def test_report_create(self): + assert self.report.with_user(self.user.id).copy({}) + + def test_report_unlink(self): + self.report.with_user(self.user.id).unlink() + assert not self.report.exists() diff --git a/report_aeroo/tests/test_report_aeroo_company_eval.py b/report_aeroo/tests/test_report_aeroo_company_eval.py new file mode 100644 index 0000000..2626c18 --- /dev/null +++ b/report_aeroo/tests/test_report_aeroo_company_eval.py @@ -0,0 +1,55 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestAerooReportCompanyEval(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env["res.company"].create( + { + "name": "My Company", + } + ) + + cls.company_2 = cls.env["res.company"].create( + { + "name": "My Company 2", + } + ) + + cls.env.user.write( + { + "company_id": cls.company.id, + "company_ids": [(4, cls.company.id)], + } + ) + + cls.partner = cls.env["res.partner"].create( + { + "name": "My Partner", + "company_id": cls.company_2.id, + } + ) + + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report") + + def test_eval_company_using_the_company_defined_on_the_partner(self): + self.report.aeroo_company_eval = "o.company_id" + self.assertEqual(self.report._get_aeroo_company(self.partner), self.company_2) + + def test_if_no_company_defined_on_the_partner_then_no_company_is_used(self): + self.report.aeroo_company_eval = "o.company_id" + self.partner.company_id = None + self.assertFalse(self.report._get_aeroo_company(self.partner)) + + def test_eval_company_using_the_company_of_the_user(self): + self.report.aeroo_company_eval = "user.company_id" + self.assertEqual(self.report._get_aeroo_company(self.partner), self.company) + + def test_if_company_eval_is_not_defined_then_the_company_of_the_user_is_used(self): + self.report.aeroo_company_eval = None + self.assertEqual(self.report._get_aeroo_company(self.partner), self.company) diff --git a/report_aeroo/tests/test_report_aeroo_lang_eval.py b/report_aeroo/tests/test_report_aeroo_lang_eval.py new file mode 100644 index 0000000..5a08162 --- /dev/null +++ b/report_aeroo/tests/test_report_aeroo_lang_eval.py @@ -0,0 +1,37 @@ +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + + +class TestAerooReportLangEval(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env["res.lang"].with_context(active_test=False).search( + [("code", "in", ("fr_CA", "fr_FR"))] + ).write({"active": True}) + + cls.env.user.lang = "fr_CA" + + cls.partner = cls.env["res.partner"].create( + { + "name": "My Partner", + "lang": "fr_FR", + } + ) + + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report") + + def test_eval_lang_using_the_lang_defined_on_the_partner(self): + self.report.aeroo_lang_eval = "o.lang" + self.assertEqual(self.report._get_aeroo_lang(self.partner), "fr_FR") + + def test_eval_lang_using_the_lang_of_the_user(self): + self.report.aeroo_lang_eval = "user.lang" + self.assertEqual(self.report._get_aeroo_lang(self.partner), "fr_CA") + + def test_if_lang_eval_is_not_defined_then_the_laguage_used_is_en_us(self): + self.report.aeroo_lang_eval = None + self.assertEqual(self.report._get_aeroo_lang(self.partner), "en_US") diff --git a/report_aeroo/tests/test_report_aeroo_multi.py b/report_aeroo/tests/test_report_aeroo_multi.py new file mode 100644 index 0000000..5921bb7 --- /dev/null +++ b/report_aeroo/tests/test_report_aeroo_multi.py @@ -0,0 +1,55 @@ +# Copyright 2016-2018 Savoir-faire Linux +# Copyright 2018 Numigi (tm) and all its contributors (https://bit.ly/numigiens) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +from odoo.modules import module +from odoo.tests.common import TransactionCase + + +class TestAerooReportMulti(TransactionCase): + """Test generating an aeroo report from a list of records.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + image_path = module.get_module_path("report_aeroo") + "/static/img/logo.png" + + cls.partner_1 = cls.env["res.partner"].create( + { + "name": "Partner 1", + "lang": "en_US", + "image_1920": base64.b64encode(open(image_path, "rb").read()), + } + ) + + cls.partner_2 = cls.partner_1.copy() + + cls.report = cls.env.ref("report_aeroo.aeroo_sample_report_multi") + + def _render_report(self, partners): + """Render the demo aeroo report for the given partners. + + The report is rendered with a basic user to detect issues related to access rights. + + :param partners: a res.partner recordset + """ + self.report.with_user(self.env.ref("base.user_demo").id)._render_aeroo(partners.ids, {}) + + def test_generate_report_with_pdf_format_and_multiple_records(self): + self.report.aeroo_out_format_id = self.env.ref( + "report_aeroo.aeroo_mimetype_pdf_odt" + ) + self._render_report(self.partner_1 | self.partner_2) + + def test_generate_report_with_odt_format_and_multiple_records(self): + self.report.aeroo_out_format_id = self.env.ref( + "report_aeroo.aeroo_mimetype_doc_odt" + ) + self._render_report(self.partner_1 | self.partner_2) + + def test_generate_report_with_single_record(self): + self.report.aeroo_out_format_id = self.env.ref( + "report_aeroo.aeroo_mimetype_doc_odt" + ) + self._render_report(self.partner_1 | self.partner_2) diff --git a/report_aeroo/views/ir_actions_report.xml b/report_aeroo/views/ir_actions_report.xml new file mode 100644 index 0000000..8e21acd --- /dev/null +++ b/report_aeroo/views/ir_actions_report.xml @@ -0,0 +1,205 @@ + + + + + ir.actions.report.aeroo.form + ir.actions.report + 16 + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + ir.actions.report.tree + ir.actions.report + + + + + + + + + + + + + + ir.actions.report.search + ir.actions.report + + + + + + + + + + + + + + + + + + tree + + + + + + + form + + + + + + Reports + ir.actions.act_window + ir.actions.report + tree,form + [('report_type', '=', 'aeroo')] + {'default_report_type': 'aeroo'} + + + + + + + tree + + + + + + + form + + + + + + + + + + + + +
diff --git a/report_aeroo/views/mail_template.xml b/report_aeroo/views/mail_template.xml new file mode 100644 index 0000000..5787450 --- /dev/null +++ b/report_aeroo/views/mail_template.xml @@ -0,0 +1,16 @@ + + + + + mail.template.aeroo.form + mail.template + + + + + + + + + + diff --git a/report_aeroo/views/report_view.xml b/report_aeroo/views/report_view.xml deleted file mode 100644 index fd5f843..0000000 --- a/report_aeroo/views/report_view.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - ir.actions.report.aeroo.form - ir.actions.report - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - report.stylesheets.form - report.stylesheets - -
- - - - -
-
-
- - - report.stylesheets.tree - report.stylesheets - - - - - - - - - res.company.form - res.company - - - - - - - - - - ir.actions.report.aeroo.search - ir.actions.report - - - - - - - - - - - - - - - - Aeroo Report Stylesheets - report.stylesheets - tree,form - - - - - -
-
diff --git a/report_aeroo/wizard/__init__.py b/report_aeroo/wizard/__init__.py deleted file mode 100755 index 79bd59e..0000000 --- a/report_aeroo/wizard/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -from . import installer diff --git a/report_aeroo/wizard/installer.py b/report_aeroo/wizard/installer.py deleted file mode 100644 index f6165b5..0000000 --- a/report_aeroo/wizard/installer.py +++ /dev/null @@ -1,133 +0,0 @@ -################################################################################ -# -# This file is part of Aeroo Reports software - for license refer LICENSE file -# -################################################################################ - -import os -import urllib3 - -from base64 import b64encode - -from odoo.addons.report_aeroo.docs_client_lib import DOCSConnection - -from odoo import api, fields, models, _ -from odoo.tools import file_open - -import logging -_logger = logging.getLogger(__name__) - -_url = 'xhttp://www.alistek.com/aeroo_banner/v11_1_report_aeroo.png' - - -class DocsConfigInstaller(models.TransientModel): - _name = 'docs_config.installer' - _description = 'docs_config.installer' - _inherit = 'res.config.installer' - _rec_name = 'host' - _logo_image = fields.Binary() - - @api.model - def _get_image(self): - if self._logo_image: - return self._logo_image - try: - im = urllib3.urlopen(_url.encode("UTF-8")) - if im.headers.maintype != 'image': - raise TypeError(im.headers.maintype) - except Exception as e: - _logger.info(e) - path = os.path.join('report_aeroo', 'config_pixmaps', 'module_banner_1.png') - image_file = file_data = file_open(path, 'rb') - try: - file_data = image_file.read() - self._logo_image = b64encode(file_data) - return self._logo_image - finally: - image_file.close() - else: - self._logo_image = b64encode(im.read()) - return self._logo_image - - def _get_image_fn(recs): - recs.config_logo = recs._get_image() - - enabled = fields.Boolean('Enabled', default=False) - host = fields.Char('Host', size=64, required=True, default='localhost') - port = fields.Integer('Port', required=True, default=8989) - auth_type = fields.Selection( - [('simple', 'Simple Authentication')], - 'Authentication', - default=False - ) - username = fields.Char('Username', size=32, default='anonymous') - password = fields.Char('Password', size=32, default='anonymous') - state = fields.Selection( - [('init', 'Init'), ('error', 'Error'), ('done', 'Done')], - 'State', - index=True, - readonly=True, - default='init' - ) - msg = fields.Text('Message', readonly=True) - error_details = fields.Text('Error Details', readonly=True) - config_logo = fields.Binary( - compute='_get_image_fn', string='Image', default=_get_image - ) - - @api.model - def default_get(self, allfields): - icp = self.env['ir.config_parameter'].sudo() - defaults = super(DocsConfigInstaller, self).default_get(allfields) - enabled = icp.get_param('aeroo.docs_enabled') - defaults['enabled'] = enabled == 'True' and True or False - defaults['host'] = icp.get_param('aeroo.docs_host') or 'localhost' - defaults['port'] = int(icp.get_param('aeroo.docs_port')) or 8989 - defaults['auth_type'] = icp.get_param('aeroo.docs_auth_type') or False - defaults['username'] = icp.get_param('aeroo.docs_username') or 'anonymous' - defaults['password'] = icp.get_param('aeroo.docs_password') or 'anonymous' - return defaults - - def check(self): - icp = self.env['ir.config_parameter'].sudo() - icp.set_param('aeroo.docs_enabled', str(self.enabled)) - icp.set_param('aeroo.docs_host', self.host) - icp.set_param('aeroo.docs_port', self.port) - icp.set_param('aeroo.docs_auth_type', self.auth_type or 'simple') - icp.set_param('aeroo.docs_username', self.username) - icp.set_param('aeroo.docs_password', self.password) - error_details = '' - state = 'done' - - if self.enabled: - try: - fp = file_open('report_aeroo/test_temp.odt', mode='rb') - file_data = fp.read() - docs_client = DOCSConnection( - self.host, self.port, username=self.username, password=self.password - ) - token = docs_client.upload(file_data) - docs_client.convert(identifier=token, out_mime='pdf') - except Exception as e: - error_details = str(e) - state = 'error' - if state == 'error': - msg = _('Failure! Connection to DOCS service was not established ' - + 'or convertion to PDF unsuccessful!') - elif state == 'done' and not self.enabled: - msg = _('Connection to Aeroo DOCS disabled!') - else: - msg = _('Success! Connection to the DOCS service was successfully ' - + 'established and PDF convertion is working.') - self.msg = msg - self.error_details = error_details - self.state = state - mod_obj = self.env['ir.model.data'] - act_obj = self.env['ir.actions.act_window'] - result = mod_obj.check_object_reference( - 'report_aeroo', 'action_docs_config_wizard' - ) - act_id = result and result[1] or False - result = act_obj.search([('id', '=', act_id)]).read()[0] - result['res_id'] = self.id - return result diff --git a/report_aeroo/wizard/installer.xml b/report_aeroo/wizard/installer.xml deleted file mode 100755 index f53fc59..0000000 --- a/report_aeroo/wizard/installer.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - docs_config.installer.view - docs_config.installer - - - -
- Configure DOCS service connection -
-
-
-
-
- - - - - - - -

- Configure Aeroo Reports connection to DOCS service and test document conversion. -

- - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
- - - Aeroo DOCS connection - ir.actions.act_window - docs_config.installer - - form - new - - - - -