Merge remote-tracking branch 'zawadzki/new-design'
[wolnelektury.git] / src / reporting / utils.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from errno import ENOENT
5 import os
6 import os.path
7 from django.conf import settings
8 import logging
9 from django.http import HttpResponse
10 from wolnelektury.utils import makedirs
11
12 logger = logging.getLogger(__name__)
13
14
15 def render_to_pdf(output_path, template, context=None, add_files=None):
16     """Renders a TeXML document into a PDF file.
17
18     :param str output_path: is where the PDF file should go
19     :param str template: is a TeXML template path
20     :param context: is context for rendering the template
21     :param dict add_files: a dictionary of additional files XeTeX will need
22     """
23
24     from io import BytesIO
25     import shutil
26     from tempfile import mkdtemp
27     import subprocess
28     import Texml.processor
29     from django.template.loader import render_to_string
30
31     rendered = render_to_string(template, context)
32     texml = BytesIO(rendered.encode('utf-8'))
33     tempdir = mkdtemp(prefix="render_to_pdf-")
34     tex_path = os.path.join(tempdir, "doc.tex")
35     with open(tex_path, 'wb') as tex_file:
36         Texml.processor.process(texml, tex_file, encoding="utf-8")
37
38     if add_files:
39         for add_name, src_file in add_files.items():
40             add_path = os.path.join(tempdir, add_name)
41             if hasattr(src_file, "read"):
42                 with open(add_path, 'w') as add_file:
43                     add_file.write(add_file.read())
44             else:
45                 shutil.copy(src_file, add_path)
46
47     cwd = os.getcwd()
48     os.chdir(tempdir)
49     try:
50         subprocess.check_call(
51             ['xelatex', '-interaction=batchmode', tex_path],
52             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53         makedirs(os.path.dirname(output_path))
54         shutil.move(os.path.join(tempdir, "doc.pdf"), output_path)
55     finally:
56         os.chdir(cwd)
57         shutil.rmtree(tempdir)
58
59
60 def render_to_csv(output_path, template, context=None, add_files=None):
61     """Renders a TeXML document into a PDF file.
62
63     :param str output_path: is where the PDF file should go
64     :param str template: is a TeXML template path
65     :param context: is context for rendering the template
66     :param dict add_files: a dictionary of additional files XeTeX will need
67     """
68
69     from django.template.loader import render_to_string
70
71     makedirs(os.path.dirname(output_path))
72
73     rendered = render_to_string(template, context)
74     with open(output_path, 'wb') as csv_file:
75         csv_file.write(rendered.encode('utf-8'))
76
77
78 def read_chunks(f, size=8192):
79     chunk = f.read(size)
80     while chunk:
81         yield chunk
82         chunk = f.read(size)
83
84
85 def generated_file_view(file_name, mime_type, send_name=None, signals=None):
86     file_path = os.path.join(settings.MEDIA_ROOT, file_name)
87     if send_name is None:
88         send_name = os.path.basename(file_name)
89
90     def signal_handler(*args, **kwargs):
91         try:
92             os.unlink(file_path)
93         except OSError as oe:
94             if oe.errno != ENOENT:
95                 raise oe
96
97     if signals:
98         for signal in signals:
99             signal.connect(signal_handler, weak=False)
100
101     def decorator(func):
102         def view(request, *args, **kwargs):
103             if not os.path.exists(file_path):
104                 func(file_path, *args, **kwargs)
105
106             if hasattr(send_name, "__call__"):
107                 name = send_name()
108             else:
109                 name = send_name
110
111             response = HttpResponse(content_type=mime_type)
112             response['Content-Disposition'] = 'attachment; filename=%s' % name
113             with open(file_path, 'rb') as f:
114                 for chunk in read_chunks(f):
115                     response.write(chunk)
116             return response
117         return view
118     return decorator