Outils pour utilisateurs

Outils du site


issue160:python

Welcome back. I truly hope that everyone is healthy. Here in central Texas, it’s been hot. Air temperature of 104°F (40°C) with a heat index well over 107 for days, and it’s expected to continue for at least the next 7 days. Last month, I started showing you the pyFPDF library. This month (FCM #160), we will be continuing to look at this library and examining the Templating portion of the library. My assumption was that the Templating feature was originally designed to handle things like invoices and labels. Furthermore, I thought that this could easily be used to produce things like recipe printouts or anything else that you wanted to have certain data show up at some preset X/Y position on a page and rendered into a PDF file. I was partially right, but it seems that pyFPDF is not completely up to the task for my needs without a tonne of work.

Bonjour, j'espère vraiment que tout le monde est en bonne santé. Ici dans le centre du Texas, il a fait très chaud. Une température de l'air de 104° F (40° C) avec un index de chaleur bien au-dessus de 107 (41,6 °C) pendant des jours, et on s'attend à ce que ça dure au moins pendant les 7 prochains jours.

Le mois dernier, j'ai commencé à vous montrer la bibliothèque pyFPDF. Ce mois-ci (le FCM n° 160), nous continuerons à regarder cette bibliothèque et examinerons sa partie « Template ».

Ma supposition était que la fonctionnalité Template avait été conçue au départ pour gérer des choses comme des factures et des étiquettes. De plus, je pensais qu'elle pourrait être facilement utilisable pour produire des choses comme des impressions de recettes ou n'importe quoi d'autre dont vous voudriez que les données soient prépositionnées dans une position x/y sur une page et restituées dans un fichier PDF. J'avais partiellement raison, mais il semble que pyFPDF ne soit pas complètement au point pour ma tâche, comme je la conçois, sans une tonne de travail.

I’ll start with a sample of a recipe from my cookbook database that represents a “normal” single page recipe that would or could be printed. In its current “state”, the cookbook program creates an html page on the fly based on the recipe that needs to be printed, then sends it to the default browser for printing. It looks something like the image bottom left. Not the prettiest, I have to admit, but it does the job and handles situations where a user needs a paper copy of the recipe (like if a friend who just can’t live without the recipe) as well as recipes that have lots of ingredients and/or lots of instructions, and flows into two or three pages. Back to the pyFPDF library; at first glance, the documentation is fairly sparse when it comes to some of the options. Most of it made sense and seemed to be reasonably easy to translate into what I was hoping to do. Well, I was really wrong. It IS sparse and some of the information is confusing. I’ll address this as we go.

Je commencerai avec un échantillon de recette sorti de la base de données d'un livre de cuisine, qui représente une recette sur une seule page « normale » lors d'une éventuelle impression. Dans son « état » actuel, le programme du livre culinaire crée une page html à la volée, basée sur la recette qui doit être imprimée, puis l'envoie au navigateur par défaut pour l'impression. Elle ressemble à quelque chose comme l'image ci-dessous.

Pas la plus jolie, je dois l'admettre, mais elle fait son travail et gère les situations où un utilisateur a besoin d'une copie papier de la recette (comme si c'était un ami qui ne pourrait pas vivre sans la recette), tout comme les recettes qui comportent un grand nombre d'ingrédients et/ou beaucoup d'instructions, et qui se déroulent sur deux ou trois pages.

Revenons à la bibliothèque pyFPDF ; à première vue, la documentation est plutôt maigre pour certaines des options. Elle était généralement compréhensible et semblait être assez facile à traduire pour ce que je souhaitais faire. Eh bien, je me trompais vraiment. Elle EST maigre et certaines informations prêtent à confusion. Je vous en parlerai au fur et à mesure de notre progression.

Since I almost always provide the code and most everything you need to run the demos I create for these articles, I didn’t want to attempt to provide the entire database. There are over 300 recipes and images (in a separate folder), and I really don’t think that pastebin will let me upload a sql database and that many images (or images at all). Therefore, I created a simple program to extract 3 “normal” single-page recipes and hard coded them into a series of “records” which are then dumped into lists for each table. Then, with a printout of the recipes on my clipboard and my ruler in hand, I started to dissect the XY coordinates of where I wanted to put each of the elements. I decided to use the hardcode method of defining the elements rather than the CSV method, since I wasn’t sure how everything would work and fit together. If it worked, then I could always create a CSV directly later on. I’m really glad I didn’t. You’ll understand later.

Puisque je fournis presque toujours le code et la plupart des choses dont vous avez besoin pour faire tourner les démos que je crée pour ces articles, je ne voulais pas tenter de vous fournir toute la base de données. Il y a plus de 300 recettes et images (dans un dossier séparé), et je ne pense vraiment pas que pastebin me laissera téléverser une base de données sql et autant d'images (ou aucune image). Aussi, j'ai créé un simple programme pour extraire 3 recettes « normales » sur une seule page et je les ai codées en dur dans une série d'« enregistrements » qui sont ensuite transformés en listes pour chaque table. Ensuite, avec une impression des recettes dans mon presse-papier et une règle à la main, j'ai commencé à disséquer les coordonnées XY auxquelles je voulais mettre chacun des éléments.

J'ai décidé d'utiliser la méthode du codage en dur pour la définition des éléments plutôt que la méthode CSV, car je n'étais pas sûr de comment les choses fonctionneraient et s'assembleraient. Si ça marchait, je pourrais toujours créer directement un CSV par la suite. Je suis vraiment heureux de ne pas l'avoir fait. Vous comprendrez plus tard.

Due to the use of f-strings, you will need to use Python 3.7.4 or greater. I started with the header and recipe title. That went pretty well and I was able to get those two elements defined. With one of the sample programs, I started to copy and paste from their sample into my code. Shown right is a shortened example of the element structure…

Du fait de l'utilisation des f-strings, j'aurai besoin d'utiliser Python 3.7.4 ou plus.

J'ai commencé avec les fichiers d'en-tête et de titre de recette. Ça s'est fait plutôt bien et j'ai été capable de définir ces deux éléments. Avec un des programmes échantillons, j'ai commencé à faire des copier/coller entre leur programme et mon code.

À droite, voici un exemple raccourci de la structure de l'élément :

As you can see, it’s a list of dictionaries. We’ll break one of them down… • Name is the name you want to reference it by • Type is the type of element this is defining. The documented options are: 'T': texts, 'L': lines, 'I': images, 'B': boxes, 'BC': barcodes (more on this in a moment). • X1, Y1, X2, Y2 are the xy positions of an imaginary box. Upper left to lower right. • Next comes the font specifications, foreground/background definitions, alignment, text (if any) and the priority for the Z-order of the element. The text can be overridden when calling the element. • There is also an optional multiline attribute, which, if used, will be the last in the dictionary.

Comme vous pouvez le voir, c'est une liste de dictionnaires. Nous en détaillerons une : ••Name est le nom par lequel vous voulez y faire référence. ••Type est le type de l'élément qu'il définit. Les options documentées sont : 'T': textes, 'L': lignes, 'I': images, 'B': boîtes, 'BC': barcodes (plus là-dessus dans un moment). ••X1, Y1, X2, Y2 sont les positions xy dans une boîte imaginaire. D'en haut à gauche au bas à droite. ••Ensuite viennent les spécifications des polices, les définitions de l'arrière-plan et et du premier plan, l'alignement, le texte (s'il y en a) et la priorité de l'élément sur l'axe Z. Le texte peut être écrasé quand l'élément est appelé. ••Il y a aussi un attribut optionnel pour lignes multiples qui, s'il est utilisé, sera à la fin du dictionnaire.

Now in the documentation, the Type specification shows only these 5 options. However, by digging into the code of the library, there is one other. It is “W” for write. It was added to allow (url) links in templates (using write method), but can be used in other situations. The good news for this option is that it works similar to the function that we created last month called chapter_body. It allows for multiline paragraph type text to be rendered correctly in a “flowing” manner. From what I could see from my limited testing, the priority (Z-Order) doesn’t make a difference what value you use. I didn’t spend a tremendous amount of time on that part of the testing.

Actuellement dans la documentation, la spécification de Type ne montre que ces 5 options. Cependant, en creusant le code de la bibliothèque, il y en a une autre. C'est « W » pour Write (écrire). Elle a été ajoutée pour permettre les liens (URL) dans les modèles (en utilisant la méthode write), mais peut être utilisée dans d'autres occasions. La bonne nouvelle pour cette option, c'est qu'elle marche de la même manière que la fonction appelée chapter_body que nous avons créée le mois dernier. Elle permet qu'un texte du type d'un paragraphe de plusieurs lignes soit restitué correctement d'une manière « fluide ».

De ce que j'ai pu voir avec mes essais limités, il n'y a pas de différence dans la priorité (l'ordre sur Z) quelle que soit la valeur utilisée. Je n'ai pas passé trop de temps sur cette partie de mon test.

I was somewhat concerned about the image type, since the images I use are not a standard size. Since most are scraped from the web, some will be in landscape mode and some in portrait mode. Some are rather large and others fairly small. Luckily, the rendering image resizes the image to fit within the bounds set in the XY positioning. Now, let’s take a look at the Ingredients section of the PDF. I really wanted to have the header and the text of the list to align as left-justified, similar to the printout image at the top of the article. For the list of ingredients (and the instructions) I ended up having to use the “W” write method. The normal “T” text method, with multiline set to True (it uses the multi_cell method internally), didn’t work correctly. The only drawback is a visual one which forces an indent for the first line of the “paragraph”. While this is nice for a chapter paragraph, it looks (at least to me) wrong, and is somewhat distracting.

J'étais quelque peu inquiet à propos du type d'image, car les images que j'utilise ne sont pas de taille standard. Comme la plupart ont été récupérées sur le Web, certaines sont en mode paysage et certaines en portrait. Certaines sont plutôt grandes et d'autres assez petites. Par chance, le rendu des images redimensionne l'image pour qu'elle s'adapte aux limites fixées pour le positionnement XY.

Maintenant, jetons un regard à la section Ingredients du PDF. Je voulais vraiment que l'en-tête et le texte de la liste soient alignés à gauche, comme dans l'image imprimée en haut de l'article. Pour la liste des ingrédients (et les instructions), j'ai finalement dû utiliser la méthode d'écriture « W ». La méthode de texte normal « T », avec le multiligne réglé à True (Vrai)(il utilise la méthode multi_cell en interne) ne fonctionnait pas correctement. Le seul inconvénient est visuel, parce qu'une indentation de la première ligne du « paragraphe » est mise d'office. Alors que c'est joli pour le paragraphe d'un chapitre, ça ne va pas (au moins pour moi) et c'est un peu perturbant.

Shown top right the element definitions for the ingredientshead and ingredientitems, which is very similar to the instructions section… Notice that the y1 position for the static text “Ingredients” is at 220 while the ingredient items block is set at 115. To me, this makes no sense. The same thing occurs with the instructions static text and the flowing text of the instructions. Now that we have our data defined and the elements dictionary set up (see the full code for all the elements definition), all that’s left to do is to make the function that will step through all the parts of the recipe document and render the document (below).

Vous voyez en haut à droite la définition des éléments pour ingredientshead (Titre des ingrédients) et ingredientitems (composition) qui est très voisine de la section des instructions :

Notez que la position y1 pour le texte fixe « Ingredients » est à 220, alors que le bloc des composants est placé à 115. Pour moi, ça n'a pas de sens. La même chose se produit avec le texte fixe des instructions et le texte flottant de celles-ci.

Maintenant que nos données sont définies et que le dictionnaire des éléments est paramétré (voir le code en entier pour la définition complète des éléments), tout ce qui reste à faire est de réaliser la fonction qui passera à travers l'ensemble du document de recette et restituera le document (ci-dessous).

So the function definition accepts a parameter called “which”. This will be the index of the recipe within the “database”. We pull the recipe title and the recipe id into separate variables, not only for use now, but also later on. Then we instantiate the template object, defining the title of the document, the format of the printout, and the elements. You can also set properties for the document (author, subject, etc) here as well. Finally, we add a page with the add_page() method. Again, fairly close to what we did last month. So, to set the various elements in the document, we call the elements that we want to include with, in some cases, a string – one at a time. The format is: Object[element] = optional string The code for that is on the next page, top right.

Ainsi, la définition de la fonction accepte un paramètre appelé « which ». Ce sera l'index de la recette dans la « base de données ». Nous plaçons le titre de la recette et l'id de la recette dans des variables séparées, non seulement pour les utiliser maintenant, mais aussi plus tard. Ensuite, nous instancions l'objet template, en définissant le titre du document, le format de l'épreuve imprimée et les éléments. Vous pouvez aussi paramétrer ici les propriétés du document (auteur, sujet, etc). Enfin, nous ajoutons une page avec la méthode add_page(). À nouveau, c'est assez proche de ce que nous avons fait le mois dernier.

Ainsi, pour paramétrer les différents éléments du document, nous appelons les éléments que nous voulons inclure avec, dans certains cas, une chaîne - une à la fois. Le format est :

Object[element] = chaîne de caractères optionnelle

Le code de cela est sur la page suivante, en haut à droite.

Since there is no straightforward way to handle a list, I stepped through the list of ingredients and created a string, which is then set using the ingredientitems element (bottom left). The instructions section is handled the same way. Finally (bottom right), we render the page pretty much as we did last month, but this time, we use the recipe title as the filename for the PDF. After that, we notify the user that the rendering process is complete. Lastly, I needed a way for the program to prompt the user to select which recipe to use. I created a simple CLI based menu (right).

Comme il n'y a pas de manière directe de gérer une liste, je suis passé pas à pas dans la liste des ingrédients et j'ai créé une chaîne qui est ensuite paramétrée en utilisant l'élément ingredientitems (en bas à gauche).

La section des instructions est gérée de la même manière.

Enfin (en bas à droite), nous restituons la page à peu près comme nous le fîmes le mois dernier, mais, cette fois, nous utilisons le titre de la recette comme nom de fichier du PDF. Après ça, nous indiquons à l'utilisateur que le traitement est fini.

Pour terminer, j'avais besoin de trouver un moyen pour que le programme invite l'utilisateur à sélectionner la recette à utiliser. J'ai créé un menu simple en ligne de commande (à droite).

And a routine that loops and does the menu until the user uses “0” to quit (middle right). Well, that’s it. I believe that, as a quick PDF generator that can be controlled easily from Python, pyFPDF is a good tool. However, as a template engine, I’m not convinced that it will work in the real world. Given the fact that the latest code changes were done 3 years ago, I don’t hold out much hope that the author will address the many issues surrounding the template engine. There is a big part of me that wants to fork the project and modify the code myself, but life and time and work (as always) prevent me from doing it right now. Just one more item on the todo list.

Et une routine qui boucle et présente le menu jusqu'à ce que l'utilisateur utilise « 0 » pour quitter (au milieu à droite).

Bon, c'est tout. Je crois que, en tant que générateur rapide de PDF qui peut être facilement contrôlé en Python, pyFPDF est un bon outil. Cependant, comme moteur de modèle (en anglais, template), je ne suis pas convaincu qu'il fonctionnera dans le monde réel. Étant donné le fait que les dernières modifications du code ont été faites il y a 3 ans, je ne garde pas beaucoup d'espoir que l'auteur résolve les nombreux problèmes autour du moteur de modèle. Il y a une grosse partie de moi qui voudrait travailler sur un fork (branche dérivée) du projet et modifier moi-même le code, mais la vie et le temps et le travail (comme toujours) m'empêchent de le faire tout de suite. Ce n'est qu'un élément de plus dans la liste de choses à faire.

Normally, I would post the project code, when the project is more than working directly within the Python shell. This month, I am going to break tradition and not post it on Pastebin.com. The reason for this is that I don’t believe that pastebin will allow me to post the image files for the recipes. This is also one of the reasons that I didn’t just directly work from the recipe database, which at the moment, holds over 300 recipes, and for each recipe there is an image. So, I’ve created a repository on Github to hold this month’s project. By doing it this way, all you have to do is follow the link to the repository, download the project as a zip file (you could clone it, but that is, in my mind, a lot of wasted effort on your part), unzip the folder somewhere convenient for you, and run the python program, called template1.py. The link to the repository is https://github.com/gregwa1953/FCM160. One other benefit is that if something happens and I lose a domain (as has happened in the past), this will last forever or until github dies. Given the amount of code on github, I REALLY doubt that will happen.

Normalement, j'aurais posté le code du projet, si le projet était plus avancé que le seul fonctionnement direct avec le shell de Python. Ce mois-ci, je vais rompre avec la tradition et ne pas le poster sur pastebin.com. La raison en est que je ne crois pas que pastebin me permette de poster les fichiers des images des recettes. C'est aussi une des raisons pour lesquelles je n'ai pas travaillé directement sur la base de données des recettes, qui, actuellement, contient plus de 300 recettes, avec une image pour chaque recette. Aussi, j'ai créé un dépôt sur GitHub pour y placer le projet de ce mois. En agissant de cette manière, tout ce que vous avez à faire est de suivre le lien vers le dépôt, télécharger le projet en fichier zip (vous pouvez le cloner, mais, dans mon esprit, c'est beaucoup d'efforts perdus de votre part), dézipper le dossier à un endroit qui vous convient et lancer le programme en python, appelé template1.py. Le lien vers le dépôt est : https://github.com/gregwa1953/FCM160. Un autre avantage est que si quelque chose arrive et que je perds un domaine (comme c'est arrivé dans le passé), le dépôt y restera pour toujours ou jusqu'à ce que github meurt. Étant donné la quantité de code sur github, je doute VRAIMENT que ça arrive.

One more bit of news before I leave you for this month. As of Wednesday, August 5, I have signed a contract to write another book. This one will be about learning to use PAGE to create GUIs for Python. I’ve written a few articles about PAGE in the past, and I’m sure that I will in the future as well. The working title, at the moment, is ‘Learning Page A GUI Designer for Python’ and I’m estimating that it will be published sometime in January 2021. So as always, until next time; stay safe, healthy, positive and creative!

Quelques nouvelles de plus avant que je ne vous quitte pour ce mois. Le mercredi 5 août, j'ai signé un contrat pour écrire un autre livre. Celui-ci sera sur l'apprentissage de l'utilisation de PAGE pour créer des interfaces utilisateur graphiques pour Python. J'ai écrit quelques articles sur PAGE dans le passé, et je suis sûr que je le ferai aussi dans le futur. Le titre provisoire, pour le moment, est « Learning Page A GUI Designer for Python » (Apprendre PAGE : un concepteur de GUI pour Python) et j'estime qu'il sera publié aux environs de janvier 2021.

Aussi, comme toujours, jusqu'à la prochaine fois : soyez prudent, en bonne santé, positif et créatif !

issue160/python.txt · Dernière modification : 2020/09/06 14:59 de andre_domenech