générer un pdf
Hello !
J'ai vu un peu de doc sur la toile pour rajouter une fonction à un petit projet mais j'aimerai votre avis sur la question ! Je souhaiterais récupérer la liste de mes produits de depuis mes models ( sa je pense pas de souci ) mais surtout que cette liste de produit soit récupérer sur un fichier pdf en cliquant sur un bouton depuis le backend Django. J'ai vu qu'il y avait plusieurs possibilitées pdfkit, la librairie ReportLab. Voici mes questions sur le sujet ^^ : - Est-ce qu'il en existe d'autre ? - Quel option choisiriez-vous ? - Est-ce qu'il existe une solution polyvente pour eventuellement extraire sur d'autres format (excel etc etc )
Merci d'avance, bonne journée.
Hello, pour moi le plus simple si tu veux écrire directement du texte et créer un pdf c'est fpdf:
from fpdf import FPDF
pdf = FPDF()
# Add a page
pdf.add_page()
# set style and size of font
# that you want in the pdf
pdf.set_font("Arial", size = 15)
for l in range(10):
pdf.cell(200, 10, txt = f"Hello {l+1}",
ln = l, align = 'L')
# save the pdf with name .pdf
pdf.output("GFG.pdf")
Si tu veux un pdf avec template un peu plus complexe, je ferai le template aec Word, ensuite avec la lib python-docx je ferai mon traitement dans le fichier word et je le sauvegarderais avec docx2pdf du genre:
from docx2pdf import convert
convert("input.docx", "output.pdf")
ça peut être une solution si t'as besoin d'extraire des data depuis excel par exemple avec pandas.
Sinon pour manipuler les pdf j'avais utiliser une fois la librairies pikepdf qui est très bien. https://github.com/p-acDev/pdf-manip.git
L'application est toujours en ligne à l'heure actuelle (c'est une vieille app hein =D ) https://pacourbet-pdf-manip-main-hf0m7e.streamlit.app
j'espère que ça t'aide.
Oui cela m'aide merci, mais je pense que je vais opté pour pdfkit, car fpdf dans la documentation je n'ai pas trouvé d'option get_template
comme dans pdfkit qui sera plus adapté. Par contre je parcour la documention officiel sur les models mais je n'arrive à trouver un field qui me créerai un simple bouton pour téléchargé le fichier pdf depuis l'espace administration de django en créant une class dans mon fichier models.py.
Hello, ok ça marche. Si tu veux créer un custom boutton dans ton interface admin pour moi c'est plutôt dans ton fichier admin.py de ton app. Y a peut être un autre moyen mais je l'ai déjà fait une fois comme ceci. Tu sais dans ton interface admin lorsque tu as la possibilité de selectionner plusieurs items et de pouvoir les supprimer, c'est en fait une action buitin de Django. Tu peux créer les tiennes custom. Ci dessous un exemple, j'avais besoin de créer une action update ratio qui mettrai à jour le ratio définit dans l'interface admin pour tous mes items. ici ratio
étant un attribut de MonModel
from django.contrib import admin
from .models import MonModel
from django.contrib.admin.helpers import ActionForm
from django import forms
class UpdateActionForm(ActionForm):
ratio = forms.FloatField(required=True)
def update_ratio(modeladmin, request, queryset):
ratio = request.POST['ratio']
ratio = float(ratio)
queryset.update(ratio=ratio)
class MonModelAdmin(admin.ModelAdmin):
action_form = UpdateActionForm
actions = [update_ratio]
# Register your models here.
admin.site.register(MonModel, MonModelAdmin)
à tester. Dis moi si tu as des problèmes, j'avais fait ça pour une vieille app qui ne tourne plus. Si tu as des pbs j'essairai de la relancer.
Sa roule merci je vais testé cela !
Au sujet de ce genre de manipulation sur le back end, vaudrait-il pas créer entierement un back end a par entière pour plus de souplesse ? Parce que de créer un bouton dans une page HTML où seul les admins y ont accés est plus simple. Mais est-ce un bon choix ? Je parle professionnellement parlant.
Salut Yann !
Pour avoir fait les deux je ne peux que te conseiller de t'appuyer le plus possible sur le backend d'administration de Django.
Tu as tellement de choses qui sont déjà toutes faites, c'est sur que niveau visuel ce n'est pas très plaisant, ça fait un peu gros tableur Excel, mais en terme de rapidité de développement il n'y a pas photo.
Tu peux facilement modifier / surcharger tous les templates d'admin que tu veux pour y rajouter des informations (c'est un peu plus avancé mais c'est possible).
Et tu as tout le système de groupes et de permissions qui est built-in et qui te permet justement de donner des accès très granulaires à chaque personne de ton équipe (pour chaque modèle, tu peux créer des permissions pour déterminer qui a le droit de créer, modifier, supprimer ou juste voir un modèle et ses instances).
Merci Thibault pour ton retour.
Je reviens sur généré mon pdf..... Je bloque un peu :s ! J'essaie d'utilisé PDFKIT. J'ai créé ma fonction dans views.py :
def get_rapport_pdf(request):
context['date'] = datetime.datetime.today()
template = get_template('filepdf.html')
html = template.render(context)
option = {
'page-size': 'letter',
'encoding': 'UTF-8',
}
pdf = pdfkit.from_string(html, False, option)
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = "attachement"
return response
Dan mon fichier html le bouton permettant de télécharger le pdf :
{% if user.is_authenticated %}
<a class="pdf" href="{% url 'rapport-pdf' %}">Rapport PDF</a>
{% endif %}
Dans mon fichier urls j'ai créer le lien :
path('get-rapport', views.get_rapport_pdf, name="rapport-pdf"),
Et le message d'erreur que j'ai lors du clique est le suivant :
TypeError at /get-rapport
'module' object does not support item assignment
Après est-ce qu'il faut que je m'oriente sur pikepdf sur les conseilles de pacdev pour ce dont j'ai besoin ?
Merci encore ^^
Je suis en train d'essayé une nouvelle approche ( déjà j'ai reussi a faire apparaitre un pdf ^^ ) Et en lisant la doc de fpdf on peu injecter du HTML donc j'en ai déduis qu'on peu utilisé les gabarits :). Mais le problème c'est que sa m'affiche les { productMeatPdf.description
J'ai essayé d'échapé les les {
sans succés. Je pense que cette approche est plus simple que dans mon précédent message. Quand pensez-vous ? Et qu'est ce que j'oubli pour faire apparaitre ma descritpion avec mon gabarit ? Le but de cette manipulation est de récupérer tous les produits de ma base de données dans un pdf.
Voici ma fonction complète :
def get_rapport_pdf(request):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", "B", 16)
pdf.cell(40, 10, "Hello world!")
pdf.write_html(
f"""
<p>{{ productMeatPdf.description }}</p>
"""
)
return HttpResponse(bytes(pdf.output()), content_type="application/pdf")
Salut Yann !
Le problème là c'est que le write_html
de FPDF n'utilise pas le même langage de template que Django. Donc là effectivement tu fais juste utiliser une f-string et insérer productMeatPdf.description tel quel (vu qu'avec les doubles accolades, les premières échappent les secondes, et donc ton productMeat n'est pas évalué).
Ce qu'il faudrait faire c'est générer déjà ton HTML avec Django, tu peux le faire en utilisant render_to_string
:
from django.template.loader import render_to_string
from fpdf import FPDF
def get_rapport_pdf(request):
# Ici, tu devras récupérer ta liste de produits depuis ta base de données
products = MonModel.objects.all() # Par exemple
# Ensuite, tu passes cette liste à ton template
html_content = render_to_string('ton_template.html', {'products': products})
# Création du PDF
pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", "B", 16)
pdf.cell(40, 10, "Hello world!")
pdf.write_html(html_content)
# Envoi du PDF comme réponse HTTP
response = HttpResponse(bytes(pdf.output()), content_type="application/pdf")
response['Content-Disposition'] = 'attachment; filename="rapport.pdf"'
return response
Dans ton fichier ton_template.html
, tu pourras parcourir tes produits et construire ton HTML en utilisant les données récupérées :
<!DOCTYPE html>
<html>
<head>
<title>Rapport de produits</title>
</head>
<body>
{% for product in products %}
<p>Description : {{ product.description }}</p>
<!-- Autres champs... -->
{% endfor %}
</body>
</html>
J'en profite pour te suggérer une petite correction aussi : dans le HttpResponse
, pour l'entête Content-Disposition
, tu peux ajouter le nom du fichier par défaut avec attachment; filename="ton_fichier.pdf"
pour indiquer un nom de fichier par défaut au téléchargement.
Désolé je reviens encore vers vous, grace à vos réponse j'arrive a avancé mais c'est un sujet où il est dur d'avoir de la documentation clair ou je n'arrive pas a les interprété !
Avecrender_to_string
J'ai reussi à avoir un résultat mais j'ai eu que des erreurs avant de faire appartaitre mon pdf, j'ai du supprimer les images car les balise <img alt="{{ productMeat.title }}" src="{{ productMeat.thumbnail.url }}"/>
me générait une erreur, et les caractères spéciaux aussi style €, % etc... La doc de fpdf parle d'importation de police et très peu de unicode, idem pour les images...
Difficile de trouver de la doc dessus !
Qu'est ce qui pourrait me bloqué pour l'affichage de mes images ?
Comment reussir a affiché les caractères spéciaux ?
Voici ma fonction grace a ton aide :
def get_rapport_pdf(request):
productsMeatPdf = ProductMeat.objects.filter(promo=True)
productsVegetablePdf = ProductVegetable.objects.filter(promo=True)
productsFishPdf = ProductFish.objects.filter(promo=True)
productsFruitPdf = ProductFruit.objects.filter(promo=True)
productsCosmeticHPdf = ProductCosmeticH.objects.filter(promo=True)
productsCosmeticFPdf = ProductCosmeticF.objects.filter(promo=True)
html_content = render_to_string('store/filepdf.html', {'productsMeatPdf': productsMeatPdf,
'productsVegetablePdf': productsVegetablePdf,
'productsFishPdf': productsFishPdf,
'productsFruitPdf': productsFruitPdf,
'productsCosmeticHPdf': productsCosmeticHPdf,
'productsCosmeticFPdf': productsCosmeticFPdf})
pdf = FPDF()
pdf.add_page()
pdf.add_font('sysfont', '', r"c:\WINDOWS\Fonts\arial.ttf", uni=True)
pdf.set_font("Arial")
pdf.write_html(html_content)
response = HttpResponse(bytes(pdf.output()), content_type="application/pdf")
response['Content-Disposition'] = 'attachment; filename="rapport.pdf"'
return response
Une parti de mon html (j'ai délibérément enlevé les balise img
et caractère spéciaux) pour que mon pdf de génère :
<div class="vignette">
{% for productMeatPdf in productsMeatPdf %}
<div class="itemT">
<h4>{{ productMeatPdf.title }}</h4>
<div class="description">
<p>Description:</p>
<p>{{ productMeatPdf.description}}</p>
</div>
<div class="categoryPrice">
{% if productMeatPdf.promo %}
<p class="priceP">{{ productMeatPdf.price }} </p>
<p class="percent">-{{ productMeatPdf.percent }}</p>
<p class="pricePromo">{{ productMeatPdf.price_with_promotion }} </p>
<p class="endP">Début promotion : {{ productMeatPdf.promo_start_date }}</p>
<p class="endP">Fin de promotion : {{ productMeatPdf.promo_date_end }}</p>
{% else %}
<p class="price">{{ productMeatPdf.price }} </p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
J'ai remarqué que le CSS n'avais aucune incidence sur le rendu de mon pdf, et la pareil la doc est très maigre dessus. Est-il possible d'intéragire avec le css avec fpdf ?
Après normalment je vous embête plus avec ce sujet !
Salut Yann !
Est-ce que tu es sûr que tu as bien une image sur toutes tes instances ?
Je vois que là tu utilises productMeat.thumbail.url
, mais si sur une instance de productMeat tu n'as pas de thumbnail
, tu auras une erreur (car thumbnail sera None et donc il ne trouvera pas url
).
Je me dis que le problème vient peut-être de là.
Ce que j'aime bien faire pour éviter ça c'est ajouter une propriété dans mes modèles :
class ProductMeat:
...
@property
def thumbnail_url(self):
return self.thumbnail.url if self.thumbnail else static('img/defaut.png')
De cette façon si tu as bien un thumbnail tu récupères l'URL, sinon tu mets une image par défaut de tes fichiers statiques.
Salut Thibault,
Bon je désespère.... j'ai inclus ta propriété au cas ou si il me manque une imags sur mes instances. Je suis même allez plus loin j'ai supprimé tous mes produit existant pour en laissé qu'un seul (avec une image) Je pense que j'ai un souci du côté du chemin d'accès a cette image. Jai le message d'erreur suivant sur mon navigateur :
FileNotFoundError at /get-rapport/
[Errno 2] No such file or directory: '/media/products/Steak.jpg'
Pourtant sur mon site l'image s'affiche parfaitement mais c'est pour la récupéré sur ce pdf que sa bloque...J'ai bien inclus une image par défaut aussi mais on dirait toujours un histoire de chemin d'accès.
Hello,
pour l'histoire de chemin de l'image je me demande si le fait que l'image s'affiche bien sur le site mais pas quand tu utlises ta fonction n'est pas parce que pour afficher les images c'est le gestionnaire de média qui opère (whitenoise ou nginx si tu es en prod) alors que avec ta fonction peut être qu'il ne connait pas le chemin '/media/products/Steak.jpg' . Tu es en developpement local ? essaye juste pour voir avec un chemin complet absolue pour le chemin de ton image pour voir si c'est bien ça.
Salut,
Avec le chemin absolue sa fonctionne ^^. Je peux voir une image dans mon pdf. Comment faire fonctionner mes gabarits des images en local ? Deuxieme question comment modifier le css de mon pdf, j'ai essayé avec un chemin absolue de mon fichier style et aussi directement dans la balise html. Est-ce qu'il faut rajouter une ligne de code pour que le css interagisse ?
Merci !
Salut Yann ! Ok, on au moins on avance !
Parfait pour l'image ! En ce qui concerne le chemin relatif qui ne fonctionne pas, ça pourrait être dû effectivement à l'environnement d'exécution de ton code, qui n'a pas le même chemin de base que ton serveur web. Pour corriger cela, tu peux essayer de déterminer le chemin absolu vers ton dossier media
dans Django, par exemple comme ça :
from django.conf import settings
import os
def get_rapport_pdf(request):
# Ton code habituel ici...
# Utilise le chemin absolu à l'image
full_path_to_image = os.path.join(settings.MEDIA_ROOT, 'products/Steak.jpg')
# La suite de ta fonction...
Sinon pour le CSS, FPDF ne supporte pas tous les styles CSS, et l'intégration directe des styles est relativement limitée. Il interprétera certains styles de base pour le formatage HTML, mais il ne lira pas un fichier CSS externe.
En général, le CSS que tu peux utiliser avec FPDF est très basique : tu peux normalement spécifier des styles pour des balises individuelles dans ton contenu HTML (comme la couleur, la taille de police, etc.), mais sans l'utilisation de sélecteurs CSS ou de classes et d'IDs.
Tu peux essayer avec du style inline pour modifier le CSS comme ça :
<p style="font-family: Arial; font-size: 16;">Un paragraphe.</p>
Salut !
Oui on avance, j'ai cru versé une larme quand j'ai vu la photo apparaitre dans mon PDF ^^ !
Par contre mon jeune niveau n'arrive pas dicerner comment utiliser la variable full_path_to_image
?
Pour le style inline, j'avais vu dans la doc FPDF qu'on était très limité mais rien que d'essayer de changer la couleur de police n'interagi pas sur une balise.
Salut Yann !
Tu peux l'utiliser quand tu fais ton render_to_string
en la passant dans le contexte :
html_content = render_to_string('store/filepdf.html', {'image_path': full_path_to_image})
Hello ! Bon en css on est vraiment limité c'est sur ! mais sa fonctionne ^^ J'ai déployé le site juste pour essayé et les images apparaissent !
Merci encore, bonne journée.
Inscris-toi
(c'est gratuit !)

Tu dois créer un compte pour participer aux discussions.
Créer un compte person