Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global - Génération des PDF #2195

Closed
Gaetanbrl opened this issue Dec 7, 2022 · 7 comments
Closed

Global - Génération des PDF #2195

Gaetanbrl opened this issue Dec 7, 2022 · 7 comments
Assignees
Milestone

Comments

@Gaetanbrl
Copy link
Contributor

Gaetanbrl commented Dec 7, 2022

Issues générée dans le cadre d'un financement par le Museum National d'Histoire Naturel (unité PatriNat) dans le cadre de la mise en œuvre du Système d'Information sur la Biodiversité (SIB)

Description

Des besoins liés actuellement à l'export des metadata dans GeoNature (#2034, #1416, #1116 ) et modules connexes (PnX-SI/gn_module_import#398) indiquent qu'il devient nécessaire de repenser le système de génération des PDF.

Les travaux en cours sont centrés sur :

  • l'analyse des besoins et de l'existant technique dans GeoNature
  • la proposition d'une solution qui s'intègrera dans l'environnement technique de GeoNature (hors module) et qui répondra aux besoins existants

Une phase de développement n'est pas encore planifiée à ce jour

Analyse

Analyse détaillée

Les besoins et éléments fonctionnel liés à la génération d'un PDF et vu dans GeoNature semblent sont listés ci-dessous (liste à compléter).

Où trouver un export PDF ?

L'export PDF est actuellement disponible dans GeoNature pour les éléments suivants :

https://github.com/PnX-SI/GeoNature/search?q=%2Fexport_pdf

Ressource URL UI route
JDD /metadata/dataset_detail/1 /meta/dataset/export_pdf/<id_dataset>
CA /metadata/af_detail/1 /meta/acquisition_frameworks/export_pdf/<id_acquisition_framework>

export_meta

Export des graphiques dans le PDF

  • Analyse de l'existant technique

Les graphiques visibles dans l'interface GeoNature sont en premier transformés en URL (image/png) :

const chart_img = this.chart ? this.chart.ctx['canvas'].toDataURL('image/png') : '';

const dataUrl = this.chart ? this.chart.ctx['canvas'].toDataURL('image/png') : '';

L'image (sous forme d'URL) est ensuite envoyé par le frontend au backend via la route /meta/upload_canvas.

return this._http.post<any>(`${AppConfig.API_ENDPOINT}/meta/upload_canvas`, img);

@routes.route("/upload_canvas", methods=["POST"])

Le backend s'occupe alors actuellement de stocker l'image sous forme de fichier PNG dans un répertoire statique /static/images/taxa.png :

filepath = str(BACKEND_DIR) + "/static/images/taxa.png"

Le nom du fichier est saisit en dur et reste identique. En cas d'exports réalisés au même moment, le fichier du premier export est écrasé par le second. Un des utilisateurs risque donc de ne pas avoir l'export demandé.

Le frontend appel ensuite la génération de PDF via la route /xxx/export_pdf/id :

... pour générer l'URL à partir :

https://github.com/PnX-SI/GeoNature/blob/cef64b514613fa76ee85d6a861cdb4f957838548/backend/geonature/templates/dataset_template_pdf.html

Export de la carte dans le PDF

La carte semble être absente de l'export PDF des métadonnées.
Plus globalement, GeoNature ne prévoit pas l'export de la carte en image contrairement au module d'import qui, sur le même fonctionnement que les graphiques, permet l'export de la carte en tant qu'image déposée par le backend :

https://github.com/PnX-SI/gn_module_import/blob/eef177f6db4353e25fa4c74d1d0ddfa592fee781/frontend/app/components/import_report/import_report.component.ts#L198-L207

Si GeoNature doit prévoir de réaliser un export de carte dans le PDF, il sera alors nécessaire d'intégrer cette dépendance (leaflet-image) dans le frontend (si PDF généré par le front ou par front + back).

Personnalisation du PDF

Actuellement, le logo et le bandeau (images) semblent personnalisables :

"logo": "Logo_pdf.png",
"bandeau": "Bandeau_pdf.png",
"entite": "sinp",

Dans le template pour le logo :

<img
class="logo"
src="{{url_for('static', filename='images/logo_structure.jpg')}}"
alt="Logo"

Il semble donc utile (voir obligatoire) de pouvoir conserver ces éléments.

Le template peut également être personnalisé en modifiant le code source. En effet, les administrateurs semblent actuellement modifier ce template dans le code source directement ou au sein d'un fork dans le meilleur des cas.

Ce fonctionnement est complexe pour certains profils d'organisation ou d'administrateur. Il comporte un risque pour la migration / maintenance du code source. Il faut également penser aux administrateur de petite structure qui n'ont pas forcément accès à GitHub ou qui n'ont pas de compétences Git / GitHub.

La possibilité de pouvoir personnaliser le document entier (position des informations, couleurs, etc.) est donc un vrai plus qui semble être à conserver, mais à améliorer.

Analyse Globale

Le code développé actuellement utilise Weasyprint qui fonctionne très bien. Ce système en place intègre déjà un système de de template en tirant parti du render Flask. Weasyprint est aussi connu pour utiliser énormément de mémoire, mais uniquement avec un gros HTML (ce qui n'est pas le cas ici). Les premiers tests réalisés au sein de notre installation sur les métadonnées sur n'affichent aucun pic de mémoire sur un jeux de données pauvre.

Cependant :

  • il n'existe qu'un seul template qui n'est pas modifiable facilement sans changer le code source GeoNature
  • la modification du template nécessite de rebuilder la configuration et donc le front
  • le graphique est déposé sur le serveur via la création d'un fichier de nom unique pour tous les exports
  • le PDF est déposé sur le serveur avec un risque de nom commun pour des exports similaires
  • l'export de la carte n'est pas prévu techniquement dans GeoNature si un besoin existe

Solutions pressenties

Plusieurs solutions sont possibles.

Génération full front

La génération d'un PDF par le frontend uniquement est possible via l'utilisation d'une librairie (e.g pdfMake).

Le front end serait notamment plus à même de générer la carte Leaflet comme c'est le cas dans le module gn_import :

https://github.com/PnX-SI/gn_module_import/blob/eef177f6db4353e25fa4c74d1d0ddfa592fee781/frontend/app/components/import_report/import_report.component.ts#L198-L207

Pour la gestion des templates, il semble complexe de la déléguer entièrement au frontend sans devoir réaliser un build (Angular) à chaque modification d'un template. La notion de template dynamique n'est donc plus là.

Nous pensons donc qu'une route backend peux permettre de récupérer le template.

Cette option a plusieurs avantages :

  • permet d'avoir plusieurs template selon par exemple si on exporte un CA ou un JDD : /export_pdf/<nom_template>
  • permet aux modules de réutiliser des templates existants / le répertoire de GeoNature contenant les templates
  • standardiser ce fonctionnement entre les modules et GeoNature

Cette option a aussi des inconvénients car un template contient des variables (ex: nombre d'observation, unique_dataset_id, etc.) qui proviennent d'une source qui doit contenir toutes ces valeurs :

Exemple dans le template actuel

            {% if data.unique_dataset_id: %}
            {{ data.unique_dataset_id }}
            {% endif %}

Si le contexte frontend ne contient par toutes ces valeurs au moment de la génération du PDF partir du template, il sera nécessaire d'appeler le backend pour récupérer ces valeurs.

La génération full frontend perd alors son intérêt.

Génération full back

Dans le fonctionnement actuel, seule la génération du graphique est gérée par le frontend.

Ce graphique pourrait alors être généré par une librairie type plotly ou highchart ou matplotlib.

La librairie la plus simple serait préférable puisque les graphiques actuels ne sont pas complexes.

Pour la question du composant de carte, il est nécessaire de trouver une librairie pour générer une carte dans python et exportable en PNG (voir folium). Autrement il faudra voir comment regénérer une carte par le backend. Cette dernière option serait potentiellement moins simple que la génération d'un graphique puisqu'il faudrait prendre en compte les couches visibles, les features affichées, l'étendue visible (bbox, zoom) pour recréer une carte dans le backend et réaliser un snapshot de la carte Leaflet. Ce point est questionnable.

Pour générer le PDF sans problème d'export simultané, on peut améliorer l'existant en générant le PDF avec un filename + timestamp.
Autrement, on peut utiliser un système sans dépôt sur le serveur via un BytesIO ou un StringIO et une réponse type :

response = make_response(binary_pdf)
response.headers['Content-Type'] = 'application/pdf'
response.headers['Content-Disposition'] =
'inline; filename=%s.pdf' % 'yourfilename'

Génération mixte

C'est le fonctionnement actuel.

Il fonctionne correctement et n'est pas forcément complexe puisque seule la partie des graphique est générée par le front.

Ce fonctionnement permet notamment de déléguer la création de l'image de la carte au front end si cette dernière est à utiliser dans un PDF. Le code serait alors potentiellement lus simple que si le backend généré lui-même la carte.

Ce fonctionnement peut être amélioré via :

  • l'utilisation d'une autre librairie que Weasyprint comme https://pypi.org/project/pypfop/ qui est plus rapide mais qui utilise du XML (plus complexe pour modifier les templates, et nécessite de revoir le backend, notamment pour obtenir un modèle de données compréhensible par le backend)
  • une gestion améliorée du filemanager.py à la dépose sur le serveur (graphiques et PDF) afin d'éviter des conflits en cas d'exports simultanés
  • L'utilisation de template dans un répertoire de configuration ne nécessitant aucune étape de build afin de récupérer n'importe quel template modifié à chaud
  • La modification de la route pour étendre l'export PDF à d'autres éléments que les métadonnées (vers une API d'export PDF plus standard ?)
@Gaetanbrl Gaetanbrl moved this to 🆕 New in MNHM - EVOLUTIONS Dec 8, 2022
@Gaetanbrl Gaetanbrl moved this from 🆕 New to 🏗 In progress in MNHM - EVOLUTIONS Dec 8, 2022
@pierrejego
Copy link
Contributor

Laquelle des trois solutions vous semble la plus pertinente pour la communauté géoNature ?

@bouttier
Copy link
Contributor

Bonjour,
Merci pour l’analyse. Mes remarques :

  • Effectivement la solution full-front a des limitations sur la gestion des templates & contexte disponible
  • La solution full-back demande pas mal de boulot : il faut réimplémenter la génération des graphiques alors qu’ils sont déjà réalisé côté frontend. Les évolutions futurs seront également plus lourdes.

Finalement, une génération mixte semble être un bon compromis au vu des remarques faites.

  • Weasyprint vs pypfop : la performance n’apparaît pas comme une limitation particulièrement gênante actuellement, donc autant garder la solution la plus simple pour le moment.
  • +1 pour personnaliser les templates sans modifier des fichiers trackés par git / facilement injectable dans une image Docker.
  • La récupération des images avant génération du PDF me semble le vrai point faible de la solution actuelle. Je pense que la meilleur simplification consisterait à n’avoir plus qu’une unique route de génération des PDF, qui reçoit les images et retourne le PDF (supprimant donc la double étape envoi des images puis récupération du PDF). Cela faciliterait très grandement le nettoyage des images et PDF lorsqu’ils ne sont plus utiles (voir même les stocker uniquement en ram).
  • Extension à d’autres éléments : à voir comment faire des fonctions utilitaires de génération des PDF les plus génériques et réutilisables possibles. Je doute cependant qu’une même route permettant de générer des PDF pour plusieurs éléments soient une bonne stratégie.

@Gaetanbrl
Copy link
Contributor Author

Gaetanbrl commented Dec 13, 2022

Merci le retour.

J'identifie donc ces travaux :

  • Refonde backend de l'API d'export PDF (1 appel = 1 pdf) et refonte du système d'appel front (>> 1 appel envoyant l'image du chart)
  • en plus du point précédent > Standardisation de la génération des PDF (une classe ou méthode spécifique peut être prévue)
  • Révision du mécanisme de dépose temporaire des fichiers

Des choses à compléter ? @bouttier @pierrejego

@Gaetanbrl
Copy link
Contributor Author

ref #2205 pour la partie configuration

@bouttier
Copy link
Contributor

Ok sur les différents points, sauf sur le dernier : je ne comprend pas le besoin lié à cette route permettant de récupérer des templates depuis un répertoire statique.
Les PDF étant généré côté backend, je ne vois pas de raison pour le front de les récupérer.
Tu peux détailler l’idée que tu avais en tête sur ce point ?

@Gaetanbrl
Copy link
Contributor Author

Gaetanbrl commented Dec 15, 2022

Oui pardon c'est inutile ici je me suis mélangé avec les autres solutions.

(liste des travaux mise à jour dans le commentaire précédent)

@mvergez
Copy link
Contributor

mvergez commented Jan 13, 2023

Salut !

Merci pour toutes ces réflexions intéressantes et ces travaux sur la génération des PDF !

J'arrive un peu tard mais pour générer la carte côté backend, on avait utilisé cette solution : https://github.com/komoot/staticmap/ si jamais ça peut aider...

Mais ça pose plusieurs soucis :

  • La lib ne semble plus trop maintenue
  • Même si elle utilise plusieurs threads, le téléchargement des tuiles peut être long
  • On ne réutilise pas la carte téléchargée côté front, c'est dommage
  • Le rendu ne semble pas identique au front mais c'est quand même OK

Donc votre décision de rester sur l'envoi d'images depuis le frontend va dans le bon sens.

@Gaetanbrl Gaetanbrl moved this from 🏗 In progress to ✅ Done in MNHM - EVOLUTIONS Feb 2, 2023
@camillemonchicourt camillemonchicourt added this to the 2.12 milestone Feb 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants