Codice sorgente per thx.appy.backend

# -*- coding: utf-8 -*-
""".. _appypod:

=====================================
OpenOffice Utils via Template Backend
=====================================

This package uses appy.pod_ to generate documents using a
template in OpenDocument Format ``.odt/ods``.

Following template api, a std way to use it is as follows::

    from django.template import loader
    tmpl = loader.get_template('example/simple.odt')
    pdf_content = tmpl.render(context={...})

Substitution in templates
==========================

Variable substituition and template logic is done as described in 
``appy`` documentation. ``appy.pod`` package returns an OpenDocument
document w/o type conversion (an ``odt`` template remains an ``odt``
templates). Conversion to pdf is delegated to LibreOffice.

Conversion to PDF
==================

There are 2 main modes that we can convert to pdf using libreoffice:

1. locally, invoking a Python interpreter that has ``uno`` package
   and setting a port to connect to an instance of libreoffice.

2. connetting to an external service (eg.: a Gotenborg docker).
   This can be forced by setting
   ``POD_CONVERTER`` to the base url of a gotenberg_ server.


AppyPod Template
================

``thx.appy.backend.Template`` accepts some more parameters than default
django template,  and has a ``.save_as`` method that can be very handy. 


Merging files
==============

The function func:`convert_to_pdf` that uses `gotenberg` service is able to
merge files according to following rules:

* if a list of template (eg: odt) is provided, all the files are converted
  in a single pdf. Pdf input files are ignored

* if a list of pdf files are provided *and* ``merge = True`` all pdf files
  are converted. OpenDocument input files are ignored


API
---

.. autoclass:: AppyPodEngine
   :members:

.. autoclass:: Template
   :members:

.. autofunction:: convert_to_pdf

.. _appy.pod: http://appyframework.org/
.. _gotenberg: https://thecodingmachine.github.io/gotenberg/
"""
from __future__ import unicode_literals

import os
import logging
from tempfile import NamedTemporaryFile

import httpx
from django.conf import settings
from django.template import TemplateDoesNotExist
from appy.pod.renderer import Renderer
from django.template.context import make_context
from django.template.engine import Engine
from django.template.backends.django import reraise
from django.template.backends.base import BaseEngine

logger = logging.getLogger('jmb.core')


[documenti]class AppyPodEngine(BaseEngine): app_dirname = 'templates' def __init__(self, params): params = params.copy() options = params.pop('OPTIONS').copy() options.setdefault('autoescape', True) options.setdefault('debug', settings.DEBUG) super().__init__(params) # if 'loader' not in options: # options['loader'] = [loaders.FilesystemLoader] self.engine = Engine(self.dirs, self.app_dirs, **options)
[documenti] def from_string(self, template_code): return Template(self.engine.from_string(template_code), self)
[documenti] def get_template(self, template_name): try: return Template(self.engine.get_template(template_name), self) except TemplateDoesNotExist as exc: reraise(exc, self)
[documenti]class Template: def __init__(self, template, backend): self.template = template self.backend = backend @property def origin(self): return self.template.origin
[documenti] def render(self, context=None, request=None, output_format='pdf', forceOoCall=False, external=None): """ False render function. :arg context: context (default: self.context) :arg request: the current request (optional) :arg output_format: output file format (.odt, .ods, .pdf - default) :return: generated file stream if created, else None """ result = None output = None output_format_orig = output_format context = make_context_complete(self.template, context, request) flattened_context = context.flatten() if settings.POD_CONVERTER and output_format == 'pdf' and external is not False: # using external service output_format = os.path.splitext(self.template.name)[1].lstrip('.') with NamedTemporaryFile('w', suffix='.%s' % output_format, delete=False) as f: output = f.name renderer = Renderer( self.template.origin.name, flattened_context, output, ooPort=settings.UNO_OOO_PORT, overwriteExisting=True, pythonWithUnoPath=settings.UNO_PYTHON_PATH, forceOoCall=forceOoCall, ) renderer.run() if settings.POD_CONVERTER and output_format_orig == 'pdf' and external is not False: result = convert_to_pdf(output).read() else: result = open(output, 'rb').read() if output and os.path.exists(output): os.unlink(output) return result
[documenti] def save_as(self, output_path=None, output_format=None, context=None, forceOoCall=False, external=None): """ Save the template into file :arg output_path: the output path :return: the open handler of the generated file .. code-block:: python from django.template import loader templ = loader.get_template('admin/fax/cover.odt', using='appy') templ.save_as('/tmp/fax.pdf', context={...}) """ assert output_format or output_path, "At least one of output_path or output_format must be provided" output_format = output_format or os.path.splitext(output_path)[1].lstrip('.') if not output_path: output_path = NamedTemporaryFile(suffix='.%s' % output_format, prefix='ooo_').name f = open(output_path, 'wb') content = self.render( output_format=output_format, context=context, forceOoCall=forceOoCall, external=external) f.write(content) f.flush() return f
def __repr__(self): return f'<AppyPodTemplate: {self.template.name}>'
[documenti]def convert_to_pdf(files, output_filename=None, merge=False, timeout=settings.POD_CONVERTER_TIMEOUT): """Convert input to pdf. If many files are handled one single merged file is returned :arg file: filename or open file handler (can be a list/tuple) :arg output_filename: name of the output file (optional) :arg merge: (boolean). Suggest that we need to generate an output that is the mewrge of input files (that ned to be ``.pdf``) :arg timeout: timeout for the connection to the server (default settings.POD_CONVERTER_TIMEOUT) """ if not isinstance(files, (list, tuple)): files = [files] files_dict = {} for j, file in enumerate(files): if isinstance(file, str): files_dict[f'file{j:02}'] = open(file, 'rb') else: files_dict[f'file{j:02}'] = file if merge: POD_CONVERTER_URL = settings.POD_CONVERTER + '/merge' else: POD_CONVERTER_URL = settings.POD_CONVERTER + '/convert/office' rh = httpx.post(POD_CONVERTER_URL, files=files_dict, timeout=timeout) if output_filename: with open(output_filename, 'wb') as f: f.write(rh.content) return rh
def make_context_complete(template, context, request=None, **kwargs): """Return a context that also has result of all context_processors""" context = make_context(context, request=None, **kwargs) if request: context_processors = template.engine.template_context_processors for context_processor in context_processors: context.update(context_processor(request)) return context