Move QR generation to the browser, saving some time and effort on the server.

This commit is contained in:
Phil Elson 2021-08-10 17:34:37 +02:00
parent 22109c0e04
commit dfc66991f4
6 changed files with 24 additions and 62 deletions

View file

@ -1,17 +1,15 @@
import concurrent.futures import concurrent.futures
import base64 import base64
import dataclasses import dataclasses
from datetime import datetime, timedelta from datetime import datetime
import io import io
import json
import typing import typing
import urllib import urllib
import zlib import zlib
import loky
import jinja2 import jinja2
import numpy as np import numpy as np
import qrcode
import json
from cara import models from cara import models
from ... import monte_carlo as mc from ... import monte_carlo as mc
@ -123,7 +121,7 @@ def calculate_report_data(model: models.ExposureModel):
} }
def generate_qr_code(base_url, calculator_prefix, form: FormData): def generate_permalink(base_url, calculator_prefix, form: FormData):
form_dict = FormData.to_dict(form, strip_defaults=True) form_dict = FormData.to_dict(form, strip_defaults=True)
# Generate the calculator URL arguments that would be needed to re-create this # Generate the calculator URL arguments that would be needed to re-create this
@ -136,20 +134,9 @@ def generate_qr_code(base_url, calculator_prefix, form: FormData):
qr_url = f"{base_url}/_c/{compressed_args}" qr_url = f"{base_url}/_c/{compressed_args}"
url = f"{base_url}{calculator_prefix}?{args}" url = f"{base_url}{calculator_prefix}?{args}"
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=4,
)
qr.add_data(qr_url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white").convert('RGB')
return { return {
'image': img2base64(_img2bytes(img)),
'link': url, 'link': url,
'qr_url': qr_url, 'shortened': qr_url,
} }
@ -313,7 +300,7 @@ class ReportGenerator:
context['alternative_scenarios'] = comparison_report( context['alternative_scenarios'] = comparison_report(
alternative_scenarios, scenario_sample_times, executor_factory=executor_factory, alternative_scenarios, scenario_sample_times, executor_factory=executor_factory,
) )
context['qr_code'] = generate_qr_code(base_url, self.calculator_prefix, form) context['permalink'] = generate_permalink(base_url, self.calculator_prefix, form)
context['calculator_prefix'] = self.calculator_prefix context['calculator_prefix'] = self.calculator_prefix
context['scale_warning'] = { context['scale_warning'] = {
'level': 'yellow-2', 'level': 'yellow-2',

View file

@ -1,29 +0,0 @@
function generate_pdf_version(qr_link) {
const pdf_version = this.document.getElementById("body");
// PDF styling
var opt = {
filename: 'myfile.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, width: 1200, windowWidth: 1200 },
enableLinks: false,
jsPDF: {
unit: 'pt',
format: 'letter',
orientation: 'portrait',
},
pagebreak: { mode: '', avoid: '.break-avoid' },
};
html2pdf().set(opt).from(pdf_version).toPdf().get('pdf').then(function(pdf) {
var totalPages = pdf.internal.getNumberOfPages();
pdf.setPage(1);
pdf.link(530, 25, 60, 60, { url: qr_link }); //Hyperlink to reproduce results
for (i = 1; i <= totalPages; i++) {
pdf.setPage(i);
pdf.setFontSize(10);
pdf.setTextColor(150);
pdf.text('Page ' + i + ' of ' + totalPages, (pdf.internal.pageSize.getWidth() / 2.25), (pdf.internal.pageSize.getHeight() - 10));
}
}).save();
};

View file

@ -24,8 +24,6 @@
<p class="mb-0"> Created {{ creation_date }} using CARA calculator version v{{ form.calculator_version }}</p> <p class="mb-0"> Created {{ creation_date }} using CARA calculator version v{{ form.calculator_version }}</p>
</div> </div>
<button type="button" class="btn btn-outline-dark align-self-center" style="margin-right: -100pt" id="download-pdf" onclick="print()">Print Report</button> <button type="button" class="btn btn-outline-dark align-self-center" style="margin-right: -100pt" id="download-pdf" onclick="print()">Print Report</button>
{# To be replaced by "Generate PDF" #}
<img id="pdf-qr-code" class="align-self-center invisible" src="{{ qr_code.image }}"/>
</div> </div>
{% endblock report_header %} {% endblock report_header %}
@ -162,10 +160,10 @@
<div class="collapse show" id="collapseQRcode"> <div class="collapse show" id="collapseQRcode">
<div class="card-body"> <div class="card-body">
<div> <div>
<a href="{{ qr_code.link }}" style="float: left;"><img style="width:250pt;" id="qr_code" src="{{ qr_code.image }}"/></a> <a href="{{ permalink.link }}" style="float: left;"><div id="qrcode"></div></a>
<span style="float: left; min-height: 250pt; line-height: 250pt; vertical-align: middle; display: inline-block;"> <span style="float: left; min-height: 250pt; line-height: 250pt; vertical-align: middle; display: inline-block;">
<p style="display: inline-block; vertical-align: middle; line-height: normal;"> <p style="display: inline-block; vertical-align: middle; line-height: normal;">
Click the QR code to regenerate the report and get a shareable link.<br>Alternatively, scan to regenerate the report.<br> Mobile-friendly app coming soon! Click the QR code to regenerate the report and get a shareable link.<br>Alternatively, scan to regenerate the report.<br>
</p> </p>
</span> </span>
</div> </div>
@ -433,11 +431,19 @@
</div> </div>
{% endblock disclaimer_container %} {% endblock disclaimer_container %}
<script src="{{ calculator_prefix }}/static/js/pdf.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script type="text/javascript">
new QRCode(document.getElementById("qrcode"), {
text: "{{ permalink.shortened }}",
width: 330,
height: 330}
);
</script>
</body> </body>
</html> </html>

View file

@ -4,7 +4,7 @@ import pytest
import tornado.testing import tornado.testing
import cara.apps.calculator import cara.apps.calculator
from cara.apps.calculator.report_generator import generate_qr_code from cara.apps.calculator.report_generator import generate_permalink
_TIMEOUT = 20. _TIMEOUT = 20.
@ -97,26 +97,26 @@ class TestOpenApp(tornado.testing.AsyncHTTPTestCase):
assert response.code == 404 assert response.code == 404
async def test_qrcode_urls(http_server_client, baseline_form): async def test_permalink_urls(http_server_client, baseline_form):
base_url = 'proto://hostname/prefix' base_url = 'proto://hostname/prefix'
qr_data = generate_qr_code(base_url, "/calculator", baseline_form) permalink_data = generate_permalink(base_url, "/calculator", baseline_form)
expected = f'{base_url}/calculator?exposed_coffee_break_option={baseline_form.exposed_coffee_break_option}&' expected = f'{base_url}/calculator?exposed_coffee_break_option={baseline_form.exposed_coffee_break_option}&'
assert qr_data['link'].startswith(expected) assert permalink_data['link'].startswith(expected)
# We should get a 200 for the link. # We should get a 200 for the link.
response = await http_server_client.fetch(qr_data['link'].replace(base_url, '')) response = await http_server_client.fetch(permalink_data['link'].replace(base_url, ''))
assert response.code == 200 assert response.code == 200
# And a 302 for the QR url itself. The redirected URL should be the same as # And a 302 for the QR url itself. The redirected URL should be the same as
# in the link. # in the link.
assert qr_data['qr_url'].startswith(base_url) assert permalink_data['shortened'].startswith(base_url)
response = await http_server_client.fetch( response = await http_server_client.fetch(
qr_data['qr_url'].replace(base_url, ''), permalink_data['shortened'].replace(base_url, ''),
max_redirects=0, max_redirects=0,
raise_error=False, raise_error=False,
) )
assert response.code == 302 assert response.code == 302
assert response.headers['Location'] == qr_data['link'].replace(base_url, '') assert response.headers['Location'] == permalink_data['link'].replace(base_url, '')
async def test_invalid_compressed_url(http_server_client, baseline_form): async def test_invalid_compressed_url(http_server_client, baseline_form):

View file

@ -62,7 +62,6 @@ pyparsing==2.4.7
pyrsistent==0.18.0 pyrsistent==0.18.0
python-dateutil==2.8.2 python-dateutil==2.8.2
pyzmq==22.1.0 pyzmq==22.1.0
qrcode==7.2
requests==2.26.0 requests==2.26.0
requests-unixsocket==0.2.0 requests-unixsocket==0.2.0
scikit-learn==0.24.2 scikit-learn==0.24.2

View file

@ -30,7 +30,6 @@ REQUIREMENTS: dict = {
'numpy', 'numpy',
'psutil', 'psutil',
'python-dateutil', 'python-dateutil',
'qrcode[pil]',
'scipy', 'scipy',
'sklearn', 'sklearn',
'timezonefinder', 'timezonefinder',