Attention, cet article est technique… Si vous ne vous intéressez pas au développement en python, passez à l’article suivant 🙂
Vous voulez faire des rapports en pdf, envoyer des lettres ou une quelquonque autre action nécéssitant de la génération de PDF ?
Si vous êtes aguéris du monde python vous avez surement entendu parler de ReportLab.
Oui, mais l’ennui c’est que pour faire du reportlab, il faut faire du développementur et dur. Et lorsque l’on change sa template, il faut tout faire.
La solution, c’est RML, language xml permettant de génerer des pdf avec des feuilles de styles, en séparant en partie le contenu et le contenant (dans différentes parties du xml) fait par la société éditrice de reportlab.
Le hic ? le parseur de ce language est sous license propriétaire…
Heuresement, l’équipe de Z3C a fait z3c.rml, une implémentation libre de ce language.
Maintenant, on peut faire du RML à la main, mais pour générer des fichiers rml avec vos données, c’est plus dur. Les languages de templates me direz vous ? Oui, justement.
Votre serviteur a fait une librairie permettant d’automatiser ca en utilisant des templates genshi (language de templating xml particulièrement adapté à la tache), dans une classe gérant le tout.
Autre problème : reportlab monte en mémoire lorsque l’on a des pages chargées ou beaucoup de pages tout simplement.
Solution : pyjon.reports vous permet d’ajouter des sections petit à petit à votre document, et à la fin de générer un seul pdf avec toutes les sections (grâce à pypdf).
Pour les plus pressés, rendez-vous directement sur les exemples.
Cas concret : une table de données
D’abord initialisons une factory (classe permettant de rendre plusieurs documents en un) :
from pyjon.reports import ReportFactory factory = ReportFactory()
Maintenant créons un fichier test.xml contenant notre template xml :
<?xml version="1.0" encoding="iso-8859-1" standalone="no" ?>
<!DOCTYPE document SYSTEM "rml_1_0.dtd">
<document xmlns:py="http://genshi.edgewall.org/">
<template pageSize="(595, 842)" leftMargin="72" showBoundary="0">
<pageTemplate id="main">
<frame id="first" x1="1in" y1="1in" width="6.27in" height="9.69in"/>
</pageTemplate>
</template>
<stylesheet>
<blockTableStyle id="mynicetable" spaceBefore="12">
<lineStyle kind="OUTLINE" colorName="black" thickness="0.5"/>
<blockFont name="Times-Bold" size="6" leading="7" start="0,0" stop="-1,0"/>
<blockBottomPadding length="1"/>
<blockBackground colorName="0xD0D0D0" start="0,0" stop="-1,0"/>
<lineStyle kind="LINEBELOW" colorName="black" start="0,0" stop="-1,0" thickness="0.5"/>
<!--body section-->
<blockFont name="Times-Roman" size="6" leading="7" start="0,1" stop="-1,-1"/>
<blockTopPadding length="1" start="0,1" stop="-1,-1"/>
<blockBackground colorsByRow="0xD0FFD0;None" start="0,1" stop="-1,-1"/>
<blockAlignment value="right" start="1,1" stop="-1,-1"/>
<!-- closing the table when restarting it on next page -->
<lineStyle kind="LINEBELOW" colorName="black" start="0,splitlast" stop="-1,splitlast" thickness="0.5"/>
</blockTableStyle>
</stylesheet>
<story>
<h1>$title</h1>
<blockTable repeatRows="1" style="mynicetable">
<tr><td py:for="i in range(10)">Row ${i}</td></tr>
<tr py:for="line in data"><td py:for="col in line" py:content="col" /></tr>
</blockTable>
<para py:content="dummy" />
</story>
</document>
Rergardons cette template de plus près :
- Le noeud template permet de choisir la taille du document
- Le noeud stylesheet permet de définir une feuille de style
- Le noeud story contient le contenu du document
- Comme en html, h1 définit un titre (ici on inclut la variable « title » en contenu)
- Un para est comme la balise P en html (paragraphe)
- La table est définie en blockTable. les py:for définissent une iteration
- Le premier py:for (py:for= »i in range(10) ») permet de définir les colonnes (Row 0 à 9)
- Le deuxieme (py:for= »line in data ») permet d’afficher tout le contenu de la variable data
- Le troisième (py:for= »col in line ») permet d’afficher son contenu (avec un py:content, définissant le contenu du tag xml) sur toutes les colonnes
Maintenant utilisons cela pour générer un fichier :
template = 'test.xml'
testdata = [range(10)] * 100
factory.render_template(
template_file=template,
title=u'THE TITLE',
data=testdata,
dummy='foo')
Ici, on a passé une jeu de données contenant les chiffres 1 à 9, 100 fois (100 listes).
On a aussi passé plusieurs variables utilisées dans la template.
On veut ajouter une autre section avec la même template ? Pas de problème !
factory.render_template(
template_file=template,
title=u'THE TITLE 2!',
data=testdata,
dummy='bar'
)
Créons un seul fichier pdf contenant les deux parties, et finalisons l’objet :
factory.render_document('test.pdf')
factory.cleanup()
Et voilà, aussi simple que cela 🙂
Comment l’installer ?
Pour installer pyjon.reports, c’est simple : « easy_install pyjon.reports », vous pourrez avoir des pdf dans vos applis en 5 minutes.
Si vous voulez regarder les sources ou contribuer, rendez-vous sur bitbucket : http://bitbucket.org/jon1012/pyjonreports
Pour plus d’infos :
par Guillaume
17 Juin 2010 à 15:13
Merci pour cet article Jon’.
J’apprécie toujours ton style précis, concis et clair, même pour un novice en Python 🙂
par Kanor
20 Juin 2010 à 02:45
Merci pour ta contribution ça me semble très prometteur
par Emmanuel
24 Août 2010 à 14:37
Une petite question: peut-on et si oui comment utiliser cette bilibotheque pour envoyer du pdf via http ?
En regardant le code source, il me semble que la méthode ‘factory.render_document’ n’accepte pas qu’on lui passe un file-like object (comme l’objet HttpResponse de django), ce qui aurait été royal…
Comment faire ?
par admin
24 Août 2010 à 16:55
@Emmanuel:
En général la plupart des framework web acceptent que les controlleurs renvoient des iterateurs de bytes, auquel cas, on renvoie le fichier petit bout par petit bout…
Pour l’utilisation avec un file-like, il y a deux cas de figure :
– pas de joining à faire (un seul pdf), auquel cas, on a forcément un fichier temporaire en entrée, la lib ne supportant pas les file like, et dans ce cas on peut eventuellement ouvrir le fichier petit à petit et l’écrire dans l’autre (là ce qui est fait c’est un shutil copy)
– un joining est nécéssaire (plusiurs pdf à rejoindre en un) : regarde la méthode factory.join_documents, elle pourrait accepter un file like en entrée sans trop de soucis (juste faire un check sur le type de l’objet passé)
Si tu veux soumettre un patch pour gérer ça, ce sera avec le plus grand plaisir que je l’incluerais.