Commit 619844ce authored by Pierre Dittgen's avatar Pierre Dittgen

PDF report using chromium headless

parent 9a0854c2
......@@ -23,14 +23,15 @@ setup(
packages=['validata_ui'],
include_package_data=True,
install_requires=[
'flask',
'goodtables',
'requests',
'validata_core >= 0.1, < 0.2',
'commonmark',
'ezodf',
'flask',
'lxml',
'requests',
'goodtables',
'tabulator',
'validata_core >= 0.1, < 0.2',
]
)
#!/usr/bin/env python3
from pathlib import Path
from urllib.parse import quote_plus
from flask import Flask
import validata_core
import flask
import jinja2
import validata_core
from validata_ui.validate_helper import ValidatorHelper
# Schemas settings
......@@ -11,7 +13,18 @@ schemas_config = validata_core.get_schemas_config()
ValidatorHelper.init(schemas_config)
# Flask things
app = Flask(__name__)
app = flask.Flask(__name__)
app.secret_key = 'MyPr3ci0u5$€cr€t'
# Jinja2 url_quote_plus custom filter
# https://stackoverflow.com/questions/12288454/how-to-import-custom-jinja2-filters-from-another-file-and-using-flask
blueprint = flask.Blueprint('filters', __name__)
@jinja2.contextfilter
@blueprint.app_template_filter()
def urlencode(context, value):
return quote_plus(value)
# Let this import after app initialisation
import validata_ui.views
......@@ -69,4 +69,4 @@
</footer>
</body>
</html>
\ No newline at end of file
</html>
......@@ -7,142 +7,167 @@
font-size: 1.2em;
font-weight: bold;
}
@media print {
.hidden-print {
display: none;
}
}
</style>
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<p class="text">Validation effectuée {{ validation_date }}</p>
<p class="text alert alert-info">
Pour relancer la validation, <a href="#" onclick="location.reload();">rechargez</a> la page.
</p>
{# Schema info #}
{% if print_mode %}
<div class="row">
<div class="col-md-5 my-4">
<div class="card bg-faded">
<div class="card-body">
<h5 class="card-title">
Schéma {{ val_info.code }}
<span class="badge badge-primary">{{ val_info.version }}</span>
</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ val_info.description }}</h6>
{% if val_info.author or val_info.contributor %}
<p class="text">
{% if val_info.author %}
Auteur : {{ val_info.author }}
{% endif %}
{% if val_info.contributor %}
<br />Contributeur(s) : {{ val_info.contributor }}
{% endif %}
</p>
{% endif %}
<p>
<a href="{{ val_info.docurl }}" target="_blank" class="card-link">Documentation</a>
</p>
<hr />
<p class="text alert alert-secondary" style="margin-bottom: 0;">
Source
{% if source_type == 'file' %}
(fichier) : <em>{{ source.name }}</em>
{% endif %}
{% if source_type == 'url' %}
(URL) : <em><a href="{{source.name}}" target="_blank" title="{{source.name}}">{{source.name |
truncate(60)}}</a></em>
<div class="col-md-5">
{% endif %}
<h1>{{ title }}</h1>
<p class="text">Validation effectuée {{ validation_date }}</p>
<p class="text alert alert-info hidden-print">
Pour relancer la validation, <a href="#" onclick="location.reload();">rechargez</a> la page.
</p>
{% if print_mode %}
</div>
<div class="col-md-7">
<img src="{{ url_for('static', filename='img/logo-horizontal.png') }}" height="80" alt="Validata" />
</div>
</div>
{% endif %}
{% if source.type == 'url' %}
<p class="text hidden-print">Obtenir le rapport au format <a href="{{ url_for('pdf_report', val_code=val_info.code) }}?url={{source.name|urlencode}}">PDF</a>
{% endif %}
{# Schema info #}
<div class="row">
<div class="col-md-{% if print_mode %}12{% else %}5{% endif %} my-4">
<div class="card bg-faded">
<div class="card-body">
<h5 class="card-title">
Schéma {{ val_info.code }}
<span class="badge badge-primary">{{ val_info.version }}</span>
</h5>
<h6 class="card-subtitle mb-2 text-muted">{{ val_info.description }}</h6>
{% if val_info.author or val_info.contributor %}
<p class="text">
{% if val_info.author %}
Auteur : {{ val_info.author }}
{% endif %}
{% if val_info.contributor %}
<br />Contributeur(s) : {{ val_info.contributor }}
{% endif %}
</p>
{% endif %}
<br />
Dimensions : <em>{{ report.table.col_count }} colonnes et {{ report.table.row_count }} lignes</em>
</p>
<p>
<a href="{{ val_info.docurl }}" target="_blank" class="card-link">Documentation</a>
</p>
<hr />
<p class="text alert alert-secondary" style="margin-bottom: 0;">
Source
{% if source_type == 'file' %}
(fichier) : <em>{{ source.name }}</em>
{% endif %}
{% if source_type == 'url' %}
(URL) : <em><a href="{{source.name}}" target="_blank" title="{{source.name}}">
{% if print_mode %}
{{source.name}}
{% else %}
{{source.name | truncate(60)}}
{% endif %}
</a></em>
{% endif %}
<br />
Dimensions : <em>{{ report.table.col_count }} colonnes et {{ report.table.row_count }}
lignes</em>
</p>
</div>
</div>
</div>
</div>
</div>
{% import 'table_macros.html' as tables %}
{% import 'table_macros.html' as tables %}
{% if report.error_count == 0 %}
<h2>La table est valide</h2>
<p>Aucune erreur détectée</p>
{% if report.error_count == 0 %}
<h2>La table est valide</h2>
<p>Aucune erreur détectée</p>
{{ tables.preview(source_data) }}
{{ tables.preview(source_data) }}
{% else %}
{% else %}
<h2>La table est invalide</h2>
<h2>La table est invalide</h2>
{# table checks #}
{# table checks #}
{% if report.table.errors.structure %}
<div>
<h3>Problèmes de structure</h3>
{% for err in report.table.errors.structure %}
<div class="alert alert-danger">
{{ err.message | safe}}
{% if report.table.errors.structure %}
<div>
<h3>Problèmes de structure</h3>
{% for err in report.table.errors.structure %}
<div class="alert alert-danger">
{{ err.message | safe}}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
<!-- row checks -->
{% endif %}
{% if not report.table.errors.body %}
<h3>Problèmes de contenu</h3>
<p class="text">
Aucune erreur de contenu
</p>
<!-- row checks -->
{{ tables.preview(source_data) }}
{% if not report.table.errors.body %}
<h3>Problèmes de contenu</h3>
<p class="text">
Aucune erreur de contenu
</p>
{% endif %}
{% if report.table.errors.body %}
{{ tables.preview(source_data) }}
{% if not report.table.display_body_errors %}
<p class="text">
Veuillez corriger ce(s) erreur(s) pour visualiser les problèmes de contenu.
</p>
{% endif %}
<p class="text">
<a href="#" id="show_body_errors" title="">Affichez
tout de même les problèmes de contenu</a>
</p>
<div class="alert alert-warning">
Attention, en cas de problèmes de structure signalés ci-dessus, certains messages d'erreurs ne seront pas
pertinents
</div>
{% if report.table.errors.body %}
<div id="body_errors" style="display: none">
{{ tables.body_errors(report, source_data) }}
</div>
{% else %}
<div>
{{ tables.body_errors(report, source_data) }}
</div>
{% endif %}
{% endif %}
{% if not report.table.display_body_errors %}
<p class="text">
Veuillez corriger ce(s) erreur(s) pour visualiser les problèmes de contenu.
</p>
<p class="text">
<a href="#" id="show_body_errors">Affichez
tout de même les problèmes de contenu</a>
</p>
<div class="alert alert-warning">
Attention, en cas de problèmes de structure signalés ci-dessus, certains messages d'erreurs ne seront pas
pertinents
</div>
{% endif %}
{% endblock %}
{% block footer %}
<script>
$(function () {
// Errors tooltip activate
$('[data-toggle="popover"]').popover({
html: true,
placement: 'auto',
trigger: 'hover'
});
// Show body errors
$('#show_body_errors').on('click', function () {
$('#body_errors').show();
$(this).remove();
<div id="body_errors" style="display: none">
{{ tables.body_errors(report, source_data) }}
</div>
{% else %}
<div>
{{ tables.body_errors(report, source_data) }}
</div>
{% endif %}
{% endif %}
{% endif %}
{% endblock %}
{% block footer %}
<script>
$(function () {
// Errors tooltip activate
$('[data-toggle="popover"]').popover({
html: true,
placement: 'auto',
trigger: 'hover'
});
// Show body errors
$('#show_body_errors').on('click', function () {
$('#body_errors').show();
$(this).remove();
})
})
})
</script>
</script>
{#
{{ report.table.errors.structure|length }} erreur(s) détectée(s)
{{ report.table.errors.body|length }} erreur(s) détectée(s)
#}
{% endblock %}
\ No newline at end of file
{% endblock %}
......@@ -38,7 +38,7 @@ def bytes_data(f):
return iob.getvalue()
def validate(schema_code, **args):
def core_validate(schema_code, **args):
""" Validate source against schema using custom-checks and pre-checks"""
return validata_core.validate(
......
......@@ -4,19 +4,22 @@
"""
import copy
import json
import subprocess
import time
from datetime import datetime
from io import BytesIO
from pathlib import Path
from urllib.parse import quote_plus
from commonmark import commonmark
from flask import redirect, render_template, request, url_for
from flask import make_response, redirect, render_template, request, url_for
import tabulator
import validata_ui.validata_util as vu
from validata_core import csv_helpers
from validata_core.loaders import custom_loaders
from validata_ui import app
from validata_ui.ui_util import flash_error, flash_warning
from validata_ui.validata_util import ValidataSource
from validata_ui.validata_util import ValidataSource, core_validate
from validata_ui.validate_helper import ValidatorHelper
......@@ -167,7 +170,7 @@ def validate(schema_code, source: ValidataSource):
""" Validate source and display report """
try:
goodtables_report = vu.validate(schema_code, **source.get_tabulator_params())
goodtables_report = core_validate(schema_code, **source.get_tabulator_params())
except tabulator.exceptions.FormatError:
flash_error('Erreur : format de fichier non supporté')
return redirect(url_for('scdl_validator', val_code=schema_code))
......@@ -187,6 +190,7 @@ def validate(schema_code, source: ValidataSource):
val_info=ValidatorHelper.schema_info(schema_code), report=validata_report,
validation_date=report_date.strftime('le %d/%m/%Y à %Hh%M'),
source=source, source_type=source.type, source_data=source_data,
print_mode=request.args.get('print', 'false') == 'true',
report_str=json.dumps(validata_report, sort_keys=True, indent=2),
breadcrumbs=[{'url': url_for('home'), 'title': 'Accueil'},
{'url': url_for('scdl_validator', val_code=schema_code),
......@@ -218,6 +222,45 @@ def validators():
return redirect(url_for('home'))
@app.route('/pdf/<val_code>')
def pdf_report(val_code):
"""PDF report generation"""
err_prefix = 'Erreur de génération du rapport PDF: '
if not ValidatorHelper.schema_exist(val_code):
flash_error(err_prefix + 'schéma inconnu')
return redirect(url_for('home'))
url_param = request.args.get('url')
if not url_param:
flash_error(err_prefix+'url non fournie')
return redirect(url_for('home'))
validation_url = '{}?input=url&print=true&url={}'.format(url_for('scdl_validator', val_code=val_code, _external=True),
quote_plus(url_param))
# temporary pdf_report.pdf filename
tmp_pdf_report = Path('/tmp') / 'validata_{}_{}_pdf_report.pdf'.format(val_code, str(time.time()))
cmd = ['chromium', '--headless', '--disable-gpu',
'--print-to-pdf={}'.format(str(tmp_pdf_report)), validation_url]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
flash_error(err_prefix + result.stdout)
if tmp_pdf_report.exists():
tmp_pdf_report.unlink()
return redirect(url_for('home'))
response = make_response(tmp_pdf_report.open('rb').read())
response.headers.set('Content-disposition', 'attachment', filename='report.pdf')
response.headers.set('Content-type', 'application/pdf')
response.headers.set('Content-length', tmp_pdf_report.stat().st_size)
tmp_pdf_report.unlink()
return response
@app.route('/validators/<val_code>', methods=['GET', 'POST'])
def scdl_validator(val_code):
""" Validator page """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment