Seite 2 von 3

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 11:46
von Nobuddy
Habe festgestellt, daß ich das:

Code: Alles auswählen

doc = SimpleDocTemplate(filename, pagesize=A4)
....
....
doc.build(elements)
überhaupt nicht benötige.

Lösung:

Code: Alles auswählen

        f = Frame(1*cm, 1*cm, 19*cm, 20*cm)
        f.addFromList(elements, self.pdf)
Das Nächste ist die Gesamthöhe der Tabelle zu ermitteln.
Da ja Zeilenumbrüche in den Zellen vorhanden sein können, kann man nicht gerade die Zeilenanzahl mit einem Wert multiplizieren, um die Gesamthöhe zu erhalten.
Habt Ihr dazu einen Tip?

Grüße Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 12:06
von BlackJack
@Nobuddy: Das mit dem einzelnen Rahmen dürfte aber nur funktionieren wenn Du Dir sicher bist dass das alles da rein passt. Wenn mehr als eine Seite zusammenkommt, oder zusammenkommen kann, dann braucht man ein Dokument.

Wozu brauchst Du die Tabellenhöhe?

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 12:26
von noisefloor
Hallo,

die folgende Klasse generiert auch mehrseitige PDF, inkl. Kopf- / Fusszeile und Seitennummerierung (Python 2.7):

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from reportlab.lib import colors, pdfencrypt
from reportlab.lib.units import cm
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.rl_config import defaultPageSize, defaultEncoding
from reportlab.platypus import Frame, Paragraph, BaseDocTemplate, PageTemplate
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

class myPDF(BaseDocTemplate):
    
    def __init__(self, filename, **kw):  
        self.allowSplitting = 0  
        apply(BaseDocTemplate.__init__, (self, filename), kw)
        self.format = A4
        self.breite, self.hoehe = self.format
        self.author = 'noisefloor'
        self.title = 'ReportLab Document'
        self.subject = 'ReportLab Demo'        
        self.encrypt = pdfencrypt.StandardEncryption('',
            ownerPassword='noisefloor',
            canPrint=1,
            canModify=0,
            canCopy=0,
            canAnnotate=1,
            strength=40)
        self.style = getSampleStyleSheet()
        self.story = []

    @staticmethod
    def buildHeadFoot(canvas,doc):
        canvas.saveState()
        canvas.setFillColorRGB(0.522, 0.522, 0.522)
        canvas.setStrokeColorRGB(0.522, 0.522, 0.522)
        canvas.line(1*cm ,
                    doc.hoehe-(2.58*cm),
                    doc.breite-(1*cm),
                    doc.hoehe-(2.58*cm))
        canvas.line(1*cm, 1.25 * cm, doc.breite-(1*cm), 1.25 * cm)
        canvas.setFillColorRGB(0, 0, 0)
        canvas.setStrokeColorRGB(0, 0, 0)
        canvas.setFont('Helvetica-Bold',16)
        canvas.drawString(1*cm,doc.hoehe - (2.4*cm), doc.title)
        canvas.setFont('Helvetica', 10)
        canvas.drawCentredString(doc.breite/2, 0.75* cm, 'ReportLab Demo')
        canvas.drawRightString(doc.breite-(1*cm), 0.75 *cm, 'www.reprtlab.com')
        canvas.drawString(1*cm,0.75*cm,'Seite: {}'.format(doc.page))
        canvas.restoreState()

    def build_story(self, text):
        #Frame definieren 
        f = Frame(1*cm,
                  2*cm,
                  self.breite - (2 * cm),
                  self.hoehe - (5.5 * cm),
                  showBoundary=0,
                  leftPadding=0,
                  rightPadding=0,
                  id='main')
        #PageTemplate definieren
        myPage = PageTemplate(id='allPages', frames=[f], 
                              onPage=self.buildHeadFoot,
                              pagesize=(self.breite, self.hoehe))
        self.addPageTemplates(myPage)
        #Inhalt generieren
        for i in text:
            self.story.append(Paragraph(i, self.style['BodyText']))
        return
Funktioniert dann z.B. so:

Code: Alles auswählen

>>> import rl_demo
>>> story = ('foo', 'bar', 'spamegg')
>>> with open('demo.pdf', 'wb') as f:
...     pdf = rl_demo.myPDF(f)
...     pdf.build_story(story)
...     pdf.multiBuild(pdf.story)
... 
1
Ergebnis ist ein verschlüsseltes PDF. Bei der Methode `build_story` werden normale Paragraphen generiert. Man kann da aber natürlich da beliebige Flowables einbauen.

Und wenn man mal PDFs mit automatisch generiertem TOC braucht, dann muss man die `myPDF`-Klasse noch um eine passende `afterFlowable` Methode erweitern.

Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 12:39
von Nobuddy
BlackJack hat geschrieben:@Nobuddy: Das mit dem einzelnen Rahmen dürfte aber nur funktionieren wenn Du Dir sicher bist dass das alles da rein passt. Wenn mehr als eine Seite zusammenkommt, oder zusammenkommen kann, dann braucht man ein Dokument.

Wozu brauchst Du die Tabellenhöhe?
@BlackJack: Da hast Du völlig Recht, daher dachte ich, wenn mehr als eine Seite zusammenkommt, dann brauche ich die Tabellenhöhe.

@noisefloor: Da hast Du was aus dem Ärmel gezaubert, das werde ich mir anschauen und testen.

Danke und Grüße
Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 12:54
von noisefloor
Hallo,

na ja, aus dem Ärmel gezaubert... Ist eine gekürze Variante des Codes, den ich produktiv im Einsatz habe.

Das ganze basiert auf Beispielen, die man so im Netz verteilt findet. :-) Wo weiß ich auch nicht mehr - ist schon ein paar Jahre her, dass ich das geschrieben habe :-)

Wenn man mehrseitige Dokumente generiert, dann sollte man sich auch noch mit ´keepWithNext()` und `KeepTogether()` beschäftigen. Und `KeepInFrame()` gibt auch noch.

Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 18:06
von Nobuddy
Hallo noisefloor,

Code: Alles auswählen

>>> import rl_demo
>>> story = ('foo', 'bar', 'spamegg')
>>> with open('demo.pdf', 'wb') as f:
...     pdf = rl_demo.myPDF(f)
...     pdf.build_story(story)
...     pdf.multiBuild(pdf.story)
Wo holst Du 'rl_demo' her?

Bei mir sieht dies so aus:

Code: Alles auswählen

from reportlap_table import myPDF
Dabei kommt dann klar die Fehlermeldung: 'NameError: name 'rl_demo' is no defined'.

Grüße Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 18:13
von EyDu
Das ist das große Stück Quellcode in seinem Beitrag.

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 19:33
von noisefloor
Hallo,

genau - habe ich oben vergessen explizit zu schreiben. Den Code als `rl_demo.py` abspeichern.

Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 19:35
von Nobuddy
Dann müßte es so richtig sein:

Code: Alles auswählen

from reportlap_table import myPDF
story = ('foo', 'bar', 'spamegg')
with open('demo.pdf', 'wb') as f:
    pdf = myPDF(f)
    pdf.build_story(story)
    pdf.multiBuild(pdf.story)
Wenn ja, dann kommt die Fehlermeldung in 'reportlap_table' Zeile 16, 'apply' is not defined!
Kann das sein, daß 'apply' in python 3.x nicht mehr verwendet wird?
Wenn ja, wie müßte dann diese Zeile richtig sein?

Grüße Nobuddy

Nachtrag:
Sorry, obiges ist falsch, sollte so sein:

Code: Alles auswählen

import reportlap_table
story = ('foo', 'bar', 'spamegg')
with open('demo.pdf', 'wb') as f:
    pdf = reportlap_table.myPDF(f)
    pdf.build_story(story)
    pdf.multiBuild(pdf.story)
Fehlermeldung mit 'apply' bleibt.

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 19:59
von noisefloor
Hallo,

ok - nach ein Nachtrag: der Code ist nur unter Python 2.7 getestet, ReportLab Version 3.0 aus den Ubuntu Trusty-Quellen.

Es funktioniert aber auch unter Python 3 (3.4), wenn du die Zeile in `__init__`, die mit `apply` beginnt, durch diese ersetzt:

Code: Alles auswählen

BaseDocTemplate.__init__(*(self, filename), **kw)
Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 20:08
von EyDu
Was eine umständliche Formulierung ist für:

Code: Alles auswählen

BaseDocTemplate.__init__(self, filename, **kw)
Und natürlich steckt in "filename" auch keine Name, sondern ein Dateiobjekt ;-)

Edit: Code-Tags

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 24. März 2015, 20:19
von noisefloor
Hallo,

die Zeile entstammt der Konvertierung mittels `2to3`. Beschwerden also bitte direkt an den Entwickler von `2to3` richten ;-)

Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Mittwoch 25. März 2015, 08:21
von Nobuddy
Hallo noisefloor,

jetzt funktioniert Dein Code, allerdings werden die Tabellendaten nicht in Tabellenform, sonder untereinander ausgegeben.

Was mich interessiert, damit ich Deinen Code verstehe:
- Wie funktioniert das, was auf die erste Seite passt.
- Und wenn der Inhalt größer als eine Seite ist.

Grüße Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Mittwoch 25. März 2015, 08:46
von noisefloor
Hallo,

das Codebeispiel nimmt ja auch nur eine Liste von Strings und fügt diese dem Attribute "story" als normaler Paragraph mit dem Stil "BodyText" hinzu. Wenn du Bilder, Tabellen etc. einbauen willst, dann musst du die Funktion `build_story` entsprechend umschreiben.

Die Seitenumbrüche macht ReportLab automatisch. Was auf die Seite passt wird durch die Größe des Frames bestimmt. Hast du mehr Inhalt, dann wird automatisch die nächste Seite angelegt. Und jede Seite bekommt halt auch automatisch Kopf- und Fusszeile, dass erledigt das `onPage=self.buildHeadFoot`

Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 31. März 2015, 06:50
von Nobuddy
OK, bin nun schon ein gutes Stück weiter.

Nun möchte ich in einer Tabelle, die letzten 2 Spalten horizontal teilen.
Also nicht, wie mit SPAN zusammenführen, sondern das Gegenteil.
Die Tabelle hat 7 Spalten, davon sollen Spalte 6 und 7 geteilt werden.

Gibt es da eine Möglichkeit und wenn ja, welche?

Grüße Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 31. März 2015, 18:07
von noisefloor
Hallo,

lt. Doku keine.... Was du versuchen könntest:

* 2 Paragraphen in eine Zelle und schauen, ob das reicht
* die Zellen der die nicht geteilten Spalten span-en
* im Quellcode suchen, ob es eine nicht-dokumentierte Funktion dafür gibt. In der offiziellen Doku steht nämlich nicht unbeidngt alles drin...

Hier bei SO gibt's auch ein Beispiel mit Lösung, wie man das machen kann. Ist aber IMHO bei großen Tabellen unhandlich... http://stackoverflow.com/questions/1381 ... s-pdf-page

Gruß, noisefloor

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Dienstag 31. März 2015, 18:30
von Nobuddy
Hallo noisefloor,

Danke für Deine Info.

Den Link habe ich schon mal gesehen, da ist aber der Aufwand zu groß, bis das so ist, wie es sein sollte.
Ist wohl, wie mit der Eierlegendenden Wollmilchsau ... :wink:

Lass das mal ruhen, vielleicht kommt mit etwas Abstand, eine Idee wie ich das noch umsetzen kann.

Grüße Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Freitag 3. April 2015, 17:58
von Nobuddy
Habe mich doch noch weiter mit der Thematik 'verändern der Tabellenstruktur mittels SPAN' beschäftigt und habe da evtl. eine Lösung auch für überlange Tabellen gefunden. :)

Bevor ich aber auf dieses Thema komme, benötige ich nochmals Hilfe zu noisefloorś gepostetem Code, der wirklich prima ist.

Beim Versuch, Daten als Tabelle ausgeben zu lassen, habe ich folgenden Abschnitt entsprechend geändert:

Code: Alles auswählen

    def build_story(self, text):
        #Frame definieren
        b, h = self.breite, self.hoehe
        f = Frame(1*cm, 2*cm, b - (2 * cm), h - (5.5 * cm),
            showBoundary=0, leftPadding=0, rightPadding=0, id='main')
        #PageTemplate definieren
        myPage = PageTemplate(id='allPages', frames=[f],
            onPage=self.buildHeadFoot, pagesize=(self.breite, self.hoehe))
        self.addPageTemplates(myPage)
        #Inhalt generieren
        widths=(1*cm, 1.25*cm, 2*cm, 1.7*cm, 2.3*cm, 5.5*cm, 2.3*cm, 2.3*cm)
        c_style=[('GRID',(0,0),(-1,-1),0.5,'#BFBFBF'),
            ('FONTSIZE',(0,0),(-1,-1), 10),
            ]
        data = list()
        for i in text:
            try:
                # String
                self.story.append(Paragraph(i, self.style['BodyText']))
            except AttributeError:
                # List, Tuple
                line = list()
                for o in i:
                    line.append(Paragraph(o, self.style['BodyText']))
                data.append(line)
        t=Table(data, colWidths=widths, style=c_style)
        self.story.append([t])
        return


def main():
    filename = '/home/whtb/Scripte/officeplanet/firmware_officeplanet/reportlab/aaaa.pdf'
    story = [['001', '50', 'STÜCK', '100000', 'TEST', '12345678901234567890123456789012345678901234567890 TEST ENTHÄLT TEXT', '0.75', '37.50'],
        ['002', '5000', 'STÜCK', '100000', '4711ABC', 'KARTOFFELCHIPS', '0.75', '3.75'],
        ['003', '50', 'STÜCK', '100000', '9999ABC', 'BLEISTIFT HB', '0.25', '12.50'],
        ['004', '1', 'PALETTE', '100000', '750089123', 'KOPIERPAPIER A4', '700.00', '700.00'],
        ['005', '1', 'PALETTE', '100000', '750089123', 'KOPIERPAPIER A4', '700.00', '700.00'],
        ['006', '1', 'PALETTE', '100000', '750089123', 'KOPIeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeERPAPIER A4', '700.00', '700.00']]
    with open(filename, 'wb') as f:
        pdf = myPDF(filename)
        pdf.build_story(story)
        pdf.multiBuild(pdf.story)


if __name__ == '__main__':
    main()
Ich erhalte dabei die Fehlermeldung:
Traceback (most recent call last):
File "def_reportlap_table.py", line 155, in <module>
main()
File "def_reportlap_table.py", line 151, in main
pdf.multiBuild(pdf.story)
File "/usr/lib/python2.7/dist-packages/reportlab/platypus/doctemplate.py", line 951, in multiBuild
if thing.isIndexing():
AttributeError: 'list' object has no attribute 'isIndexing'
Komme da leider zu keiner Lösung und könnte Eure Hilfe gebrauchen!

Grüße Nobuddy

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Freitag 3. April 2015, 19:15
von kbr
Auch ich hatte reportlab für die Erzeugung von Dokumenten mit Logo und Tabellen genutzt, verwende mittlerweile aber Latex. Dabei werden in Latex-Templates variable Inhalte zunächst substituiert, gespeichert und dann per Subprozess mittels Latex verarbeitet. Das ist recht IO-lastig, geht seit Vorhandensein einer SSD aber sauschnell und sieht super aus - insbesondere Tabellen (besonders mehrseitige). Das muss nicht für jeden Einsatz die richtige Lösung sein, ich möchte sie hier aber dennoch einmal erwähnen.

Re: Hilfe zum 1 x 1 für reportlab

Verfasst: Samstag 4. April 2015, 13:35
von Nobuddy
@kbr, brauche kein Latex, reportlab ist für mein Vorhaben völlig ausreichend. :wink:

Ich habe die Lösung gefunden :)

Code: Alles auswählen

    def build_story(self, text):
        #Frame definieren
        b, h = self.breite, self.hoehe
        f = Frame(1*cm, 2*cm, b - (2 * cm), h - (5.5 * cm),
            showBoundary=0, leftPadding=0, rightPadding=0, id='main')
        #PageTemplate definieren
        myPage = PageTemplate(id='allPages', frames=[f],
            onPage=self.buildHeadFoot, pagesize=(self.breite, self.hoehe))
        self.addPageTemplates(myPage)
        #Inhalt generieren
        widths=(1*cm, 1.25*cm, 2*cm, 1.7*cm, 2.3*cm, 5.5*cm, 2.3*cm, 2.3*cm)
        c_style=[('GRID',(0,0),(-1,-1),0.5,'#BFBFBF'),
            ('FONTSIZE',(0,0),(-1,-1), 10),
            ]
        data = list()
        for i in text:
            try:
                # String
                self.story.append(Paragraph(i, self.style['BodyText']))
            except AttributeError:
                # List, Tuple
                line = list()
                for o in i:
                    line.append(Paragraph(o, self.style['BodyText']))
                data.append(line)
        if len(data) > 0:
            self.story=[Table(data, colWidths=widths, style=c_style)]
        return
Jetzt wird auch die Tabelle korrekt ausgegeben.

Grüße Nobuddy