Coverage for crateweb/extra/pdf.py: 32%

57 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-08-27 10:34 -0500

1""" 

2crate_anon/crateweb/extra/pdf.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CRATE. 

10 

11 CRATE is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CRATE is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CRATE. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**Assistance functions for working with PDFs.** 

27 

28""" 

29 

30import logging 

31from typing import Any, Dict, Optional, TYPE_CHECKING 

32 

33from cardinal_pythonlib.dicts import merge_two_dicts 

34from cardinal_pythonlib.django.serve import serve_buffer 

35from cardinal_pythonlib.pdf import ( 

36 get_pdf_from_html, 

37 make_pdf_on_disk_from_html, 

38 PdfPlan, 

39) 

40from django.conf import settings 

41from django.http import HttpResponse 

42from django.template.loader import render_to_string 

43 

44if TYPE_CHECKING: 

45 from crate_anon.crateweb.consent.constants import EthicsInfo 

46 

47log = logging.getLogger(__name__) 

48 

49 

50# ============================================================================= 

51# CratePdfPlan 

52# ============================================================================= 

53 

54 

55class CratePdfPlan(PdfPlan): 

56 """ 

57 Specializes :class:`cardinal_pythonlib.pdf.PdfPlan` for our default 

58 header/footer. 

59 """ 

60 

61 def __init__(self, *args, ethics_doccode: str = None, **kwargs) -> None: 

62 if "header_html" not in kwargs: 

63 kwargs["header_html"] = get_pdf_header_html() 

64 if "footer_html" not in kwargs: 

65 kwargs["footer_html"] = get_pdf_footer_html( 

66 ethics_doccode=ethics_doccode 

67 ) 

68 if "wkhtmltopdf_filename" not in kwargs: # added 2018-06-28 

69 kwargs["wkhtmltopdf_filename"] = settings.WKHTMLTOPDF_FILENAME 

70 if "wkhtmltopdf_options" not in kwargs: # added 2018-06-28 

71 kwargs["wkhtmltopdf_options"] = settings.WKHTMLTOPDF_OPTIONS 

72 super().__init__(*args, **kwargs) 

73 

74 

75# ============================================================================= 

76# Create PDFs from HTML 

77# ============================================================================= 

78 

79 

80def get_pdf_header_html() -> str: 

81 """ 

82 Returns header HTML for PDF creation via wkhtmltopdf. 

83 Replaces settings.PDF_LETTER_HEADER_HTML. 

84 """ 

85 return render_to_string("pdf_header.html") 

86 

87 

88def get_pdf_footer_html(ethics_doccode: str = None) -> str: 

89 """ 

90 Returns footer HTML for PDF creation via wkhtmltopdf. 

91 Replaces settings.PDF_LETTER_FOOTER_HTML. 

92 """ 

93 title = "" 

94 version = "" 

95 date = "" 

96 ethics = settings.ETHICS_INFO # type: Optional[EthicsInfo] 

97 if ethics_doccode and ethics: 

98 docinfo = ethics.get_docinfo(ethics_doccode) 

99 title = docinfo.title 

100 version = docinfo.version 

101 date = docinfo.date 

102 else: 

103 # All info or none. 

104 ethics = None 

105 return render_to_string( 

106 "pdf_footer.html", 

107 context={ 

108 "address": settings.PDF_LETTER_FOOTER_ADDRESS_HTML, 

109 "date": date, 

110 "ethics": ethics, 

111 "title": title, 

112 "version": version, 

113 }, 

114 ) 

115 

116 

117def get_pdf_from_html_with_django_settings( 

118 html: str, 

119 header_html: str = None, 

120 footer_html: str = None, 

121 wkhtmltopdf_filename: str = None, 

122 wkhtmltopdf_options: Dict[str, Any] = None, 

123 debug_content: bool = False, 

124 debug_options: bool = False, 

125 fix_pdfkit_encoding_bug: bool = None, 

126) -> bytes: 

127 """ 

128 Applies our ``settings.WKHTMLTOPDF_OPTIONS`` and then makes a PDF from the 

129 supplied HTML. 

130 

131 See the arguments to :func:`cardinal_pythonlib.pdf.make_pdf_from_html`. 

132 

133 Returns: 

134 a binary PDF 

135 """ 

136 # Customized for this Django site 

137 wkhtmltopdf_filename = ( 

138 wkhtmltopdf_filename or settings.WKHTMLTOPDF_FILENAME 

139 ) 

140 if wkhtmltopdf_options is None: 

141 wkhtmltopdf_options = settings.WKHTMLTOPDF_OPTIONS.copy() 

142 else: 

143 wkhtmltopdf_options = merge_two_dicts( 

144 settings.WKHTMLTOPDF_OPTIONS, wkhtmltopdf_options 

145 ) 

146 # log.debug(f"{wkhtmltopdf_options!r}") 

147 

148 return get_pdf_from_html( 

149 html=html, 

150 header_html=header_html, 

151 footer_html=footer_html, 

152 wkhtmltopdf_filename=wkhtmltopdf_filename, 

153 wkhtmltopdf_options=wkhtmltopdf_options, 

154 debug_content=debug_content, 

155 debug_options=debug_options, 

156 fix_pdfkit_encoding_bug=fix_pdfkit_encoding_bug, 

157 ) 

158 

159 

160def make_pdf_on_disk_from_html_with_django_settings( 

161 html: str, 

162 header_html: str = None, 

163 footer_html: str = None, 

164 wkhtmltopdf_filename: str = None, 

165 wkhtmltopdf_options: Dict[str, Any] = None, 

166 output_path: str = None, 

167 debug_content: bool = False, 

168 debug_options: bool = False, 

169 fix_pdfkit_encoding_bug: bool = None, 

170) -> bool: 

171 """ 

172 Applies our ``settings.WKHTMLTOPDF_OPTIONS`` and then makes a PDF from the 

173 supplied ``html`` and stores it in the file named by ``output_path``. 

174 

175 See the arguments to :func:`cardinal_pythonlib.pdf.make_pdf_from_html`. 

176 

177 Returns: 

178 success? 

179 """ 

180 # Customized for this Django site 

181 wkhtmltopdf_filename = ( 

182 wkhtmltopdf_filename or settings.WKHTMLTOPDF_FILENAME 

183 ) 

184 if wkhtmltopdf_options is None: 

185 wkhtmltopdf_options = settings.WKHTMLTOPDF_OPTIONS.copy() 

186 else: 

187 wkhtmltopdf_options = merge_two_dicts( 

188 settings.WKHTMLTOPDF_OPTIONS, wkhtmltopdf_options 

189 ) 

190 

191 return make_pdf_on_disk_from_html( 

192 html=html, 

193 output_path=output_path, 

194 header_html=header_html, 

195 footer_html=footer_html, 

196 wkhtmltopdf_filename=wkhtmltopdf_filename, 

197 wkhtmltopdf_options=wkhtmltopdf_options, 

198 debug_content=debug_content, 

199 debug_options=debug_options, 

200 fix_pdfkit_encoding_bug=fix_pdfkit_encoding_bug, 

201 ) 

202 

203 

204# ============================================================================= 

205# Serve PDFs from HTML 

206# ============================================================================= 

207 

208 

209def serve_pdf_from_html( 

210 html: str, offered_filename: str = "test.pdf", **kwargs 

211) -> HttpResponse: 

212 """ 

213 Converts HTML into a PDF and serves it. 

214 

215 Args: 

216 html: HTML to make into a PDF and serve 

217 offered_filename: filename from the user's perspective 

218 **kwargs: passed to :func:`get_pdf_from_html_with_django_settings` 

219 """ 

220 pdf = get_pdf_from_html_with_django_settings(html, **kwargs) 

221 return serve_buffer( 

222 pdf, 

223 offered_filename=offered_filename, 

224 content_type="application/pdf", 

225 as_attachment=False, 

226 as_inline=True, 

227 ) 

228 

229 

230def serve_html_or_pdf( 

231 html: str, viewtype: str, ethics_doccode: str = None 

232) -> HttpResponse: 

233 """ 

234 Serves some HTML as HTML or after converting it to a PDF in our letter 

235 style. For development. 

236 

237 Args: 

238 html: contents 

239 viewtype: ``"pdf"`` or ``"html"`` 

240 ethics_doccode: ethics document code 

241 """ 

242 if viewtype == "pdf": 

243 return serve_pdf_from_html( 

244 html, 

245 header_html=get_pdf_header_html(), 

246 footer_html=get_pdf_footer_html(ethics_doccode=ethics_doccode), 

247 ) 

248 elif viewtype == "html": 

249 return HttpResponse(html) 

250 else: 

251 raise ValueError("Bad viewtype")