I'm trying to create a VICS Bill of Lading using Reportlab. The PDF document should have 2 fixed section tables on page 1 and they should both flow to page 2. I can get the data to display and they both flow to page 2, but the first table is displaying in the wrong frame. Does anyone know how this can be done? Thank you in advance.
Sample VICS BOL: https://formspal.com/pdf-forms/other/vics-bol/
Here is my code:
from reportlab.lib.pagesizes import letter, landscapefrom reportlab.lib import colorsfrom reportlab.lib.units import inch, cmfrom reportlab.pdfgen import canvasfrom reportlab.platypus import Table, TableStyle, SimpleDocTemplate, Frame, FrameBreak, PageBreak, PageTemplate, Paragraph, BaseDocTemplate, NextPageTemplatefrom reportlab.lib.styles import getSampleStyleSheet, ParagraphStylefrom functools import partialfrom reportlab.lib.enums import TA_JUSTIFY, TA_LEFT, TA_CENTER, TA_RIGHTfrom reportlab.platypus.flowables import Spacerclass vics_bol(BaseDocTemplate): def __init__(self, filename, bol_json, **kwargs): super().__init__(filename, page_size=letter, _pageBreakQuick=0, **kwargs) self.bol_json = bol_json self.flow = [] self.page_templates = [] self.page_width = (self.width + self.leftMargin * 2) self.page_height = (self.height + self.bottomMargin * 2) self.header_content = self.build_header() self.footer_content = self.build_footer() # Setting up the frames, frames are use for dynamic content not fixed page elements p1_order_frame = Frame(65, 475, 450, 150, id='OrderInfo1', showBoundary=0) px_order_frame = Frame(2, 350, 600, 450, id='OrderInfo2', showBoundary=0) p1_carrier_frame = Frame(65, 275, 450, 150, id='CarrierInfo1', showBoundary=0) px_carrier_frame = Frame(2, 150, 600, 450, id='CarrierInfo2', showBoundary=0) # Creating the page templates first_page = PageTemplate(id='FirstPage', frames=[p1_order_frame, p1_carrier_frame], onPage=self.on_first_page) later_pages = PageTemplate(id='LaterPages', frames=[px_order_frame, px_carrier_frame], onPage=self.on_later_page) self.addPageTemplates([first_page, later_pages]) # Tell Reportlab to use the other template on the later pages, # by the default the first template that was added is used for the first page. self.flow = [NextPageTemplate(['*', 'LaterPages'])] order_table = self.build_customer_info_table_from_json() self.flow.append(order_table) self.flow.append(FrameBreak()) carrier_table = self.build_carrier_info_table_from_json() self.flow.append(carrier_table) self.build(self.flow) def build_customer_info_table_from_json(self): table_data = [] i = 0 for itm in self.bol_json["OrderInfo"]["Items"]: indx = 0 i = i + 1 row_data = [] if i == 1: table_data.append(["OrderNo", "Pkgs", "Weight", "AddInfo"]) for field_name in itm: row_data.append(itm[field_name]) indx = indx + 1 table_data.append(row_data) table = Table(table_data, colWidths=[150, 100, 100, 225], repeatRows=1) table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.black), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black) ])) return table def build_carrier_info_table_from_json(self): table_data = [] i = 0 for itm in self.bol_json["CarrierInfo"]["Items"]: indx = 0 i = i + 1 row_data = [] if i == 1: table_data.append(["HUQty", "HUType", "PkgQty", "PkgType", "Weight", "HM", "Desc", "NMFC", "Class"]) for field_name in itm: row_data.append(itm[field_name]) indx = indx + 1 table_data.append(row_data) table = Table(table_data, colWidths=[50, 50, 50, 50, 60, 25, 165, 75, 50], repeatRows=1) table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.black), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black) ])) return table # def create_vics_bill_of_lading(self, pdf_filename, bill_of_lading_data): # # Create a canvas object and specify the PDF file # #c = canvas.Canvas(pdf_filename, pagesize=landscape(letter)) # doc = SimpleDocTemplate(pdf_filename, pagesize=letter) # # header_content = self.build_header() # footer_content = self.build_footer() # # shipfrom_frame = self.draw_ship_from(doc, bill_of_lading_data) # shipto_frame = self.draw_ship_to(doc, bill_of_lading_data) # #bol_frame = self.draw_bol_number(doc, bill_of_lading_data) # order_frame = self.draw_customer_order_info(doc, bill_of_lading_data) # # page_template = PageTemplate(id='page1', frames=[shipfrom_frame, shipto_frame, order_frame], # onPage=partial(self.header_and_footer, header_content=header_content, footer_content=self.footer_content)) # doc.addPageTemplates([page_template]) # # doc.build(self.flow, onFirstPage=self.on_first_page, onLaterPages=self.on_later_page) def build_header(self): styles = getSampleStyleSheet() styles.add(ParagraphStyle(name='Heading1_CENTER', parent=styles['Heading1'], fontName='Helvetica', wordWrap='LTR', alignment=TA_CENTER, fontSize=18, leading=13, textColor=colors.black, borderPadding=0, leftIndent=0, rightIndent=0, spaceAfter=0, spaceBefore=0, splitLongWords=True, spaceShrinkage=0.05, )) header_content = Paragraph("Bill of Lading", styles['Heading1_CENTER']) return header_content def build_footer(self): styles = getSampleStyleSheet() footer_content = Paragraph("This is a footer. It goes on every page. ", styles['Normal']) return footer_content def header(self, canvas, doc, content): canvas.saveState() w, h = content.wrap(doc.width, doc.topMargin) content.drawOn(canvas, doc.leftMargin, doc.height + doc.bottomMargin + doc.topMargin - h) canvas.restoreState() def footer(self, canvas, doc, content): canvas.saveState() w, h = content.wrap(doc.width, doc.bottomMargin) content.drawOn(canvas, doc.leftMargin, h) canvas.restoreState() def header_and_footer(self, canvas, doc, header_content, footer_content): self.header(canvas, doc, header_content) self.footer(canvas, doc, footer_content) def on_first_page(self, canvas, doc): canvas.saveState() canvas.drawString(250, 825, "Bill of Lading") # order_table, order_data = self.build_customer_info_table_from_json() # carrier_table, carrier_data = self.build_carrier_info_table_from_json() # # # # Setting up the frames, frames are use for dynamic content not fixed page elements # p1_order_frame = Frame(65, 475, 450, 150, id='OrderInfo1') # px_order_frame = Frame(2, 350, 600, 450, id='OrderInfo2') # p1_order_frame.addFromList(order_data, canvas) # px_order_frame.addFromList(order_data, canvas) # # p1_carrier_frame = Frame(65, 275, 450, 150, id='CarrierInfo1') # px_carrier_frame = Frame(2, 150, 600, 450, id='CarrierInfo2') # p1_carrier_frame.addFromList(carrier_data, canvas) # px_carrier_frame.addFromList(carrier_data, canvas) #Draw ShipFrom table = self.draw_ship_from(self.bol_json) table.wrapOn(canvas, 0, 0) table.drawOn(canvas, 2, 710) # Draw ShipTo table = self.draw_ship_to(self.bol_json) table.wrapOn(canvas, 0, 0) table.drawOn(canvas, 2, 625) #Draw BOL Data table = self.draw_bol_number(self.bol_json) table.wrapOn(canvas, 0, 0) table.drawOn(canvas, 355, 764) canvas.restoreState() def on_later_page(self, canvas, doc): canvas.saveState() canvas.drawString(225, 825, "Bill of Lading (Supplemental)") canvas.restoreState() # def convert_dict_to_list(self, data_dict): # table_data = [['Key', 'Value']] # for key, value in data_dict.items(): # table_data.append([key, value]) # return table_data def draw_ship_from(self, bol_data): # Create a table for the bill of lading data table_data = [ ["", bol_data["ShipFrom"]["HeaderTitle"]], ["Name:", bol_data["ShipFrom"]["Name"]], ["Address:", bol_data["ShipFrom"]["Address"]], ["City/State/Zip:", bol_data["ShipFrom"]["City/State/Zip"]], ["SID#:", bol_data["ShipFrom"]["SID#"]], # ... add more rows as needed ] table = Table(table_data, colWidths=[100, 250]) table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.black), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black) ])) return table def draw_ship_to(self, bol_data): # Create a table for the bill of lading data table_data = [ ["", bol_data["ShipTo"]["HeaderTitle"]], ["Name:", bol_data["ShipTo"]["Name"]], ["Address:", bol_data["ShipTo"]["Address"]], ["City/State/Zip:", bol_data["ShipTo"]["City/State/Zip"]], # ... add more rows as needed ] table = Table(table_data, colWidths=[100, 250]) table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.black), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black) ])) return table def draw_bol_number(self, bol_data): # Create a table for the bill of lading data table_data = [ ["", bol_data["BOL"]["HeaderTitle"]], ["Bill of Lading Number:", bol_data["BOL"]["BOLNumber"]], ] table = Table(table_data, colWidths=[110, 125]) table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.black), ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black) ])) return table # def draw_customer_order_info(self, doc, bol_data): # # Create a table for the bill of lading data # table_data = [] # row_data = [] # i = 0 # # for itm in bol_data["OrderInfo"]["Items"]: # indx = 0 # i = i + 1 # row_data = [] # if i == 1: # table_data.append(["OrderNo", "Pkgs", "Weight", "AddInfo"]) # for field_name in itm: # row_data.append(itm[field_name]) # indx = indx + 1 # table_data.append(row_data) # #table_data.append(itm["OrderNo"], itm["Pkgs"], itm["Weight"], itm["AddInfo"]) # #table_data = convert_dict_to_list(bol_data["OrderInfo"]["Items"]) # # # table_data = [ # # ["", bol_data["OrderInfo"]["HeaderTitle"]], # # ["Name:", bol_data["ShipTo"]["OrderNo"]], # # ["Address:", bol_data["ShipTo"]["Pkgs"]], # # ["City/State/Zip:", bol_data["ShipTo"]["City/State/Zip"]], # # # ... add more rows as needed # # ] # # table = Table(table_data, colWidths=[150, 100, 100, 250], repeatRows=1) # table.setStyle(TableStyle([ # ('BACKGROUND', (0, 0), (-1, 0), colors.black), # ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), # ('ALIGN', (0, 0), (-1, -1), 'LEFT'), # ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), # ('BOTTOMPADDING', (0, 0), (-1, 0), 12), # ('BACKGROUND', (0, 1), (-1, -1), colors.white), # ('GRID', (0, 0), (-1, -1), 1, colors.black) # ])) # # self.flow.append(table) # frame = Frame(2, 410, 600, 150, showBoundary=0, bottomPadding=0, topPadding=0, leftPadding=0, rightPadding=0) # # return frame # # def draw_carrier_info(self, doc, bol_data): # # Create a table for the bill of lading data # table_data = [] # row_data = [] # i = 0 # # for itm in bol_data["CarrierInfo"]["Items"]: # indx = 0 # i = i + 1 # row_data = [] # if i == 1: # table_data.append(["HUQty", "HUType", "PkgQty", "PkgType", "Weight", "HM", "Desc", "NMFC", "Class"]) # for field_name in itm: # row_data.append(itm[field_name]) # indx = indx + 1 # table_data.append(row_data) # # table = Table(table_data, colWidths=[25, 25, 25, 25, 25, 10, 100, 25, 25], repeatRows=1) # table.setStyle(TableStyle([ # ('BACKGROUND', (0, 0), (-1, 0), colors.black), # ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), # ('ALIGN', (0, 0), (-1, -1), 'LEFT'), # ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), # ('BOTTOMPADDING', (0, 0), (-1, 0), 12), # ('BACKGROUND', (0, 1), (-1, -1), colors.white), # ('GRID', (0, 0), (-1, -1), 1, colors.black) # ])) # # self.flow.append(table) # frame = Frame(2, 410, 600, 150, showBoundary=0, bottomPadding=0, topPadding=0, leftPadding=0, rightPadding=0) # # return frameif __name__ == "__main__": # Sample data for the bill of lading bill_of_lading_data = {"ShipFrom": {"HeaderTitle": "Ship From","Name": "XYZ, LLC","Address": "6000 Airport Plaza Dr. STE 550","City/State/Zip": "Long Beach, CA 90810","SID#": "" },"ShipTo": {"HeaderTitle": "Ship To","Name": "ABC Company","Address": "123 Main St.","City/State/Zip": "Lynn, MA 01904" },"BOL": {"HeaderTitle": "","BOLNumber": "99278793","BarCode": "99278793" },"OrderInfo": {"HeaderTitle": "Customer Order Info","Items": [ {"OrderNo": "9987","Pkgs": "2","Weight": "5.6","AddInfo": "some text" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "8.1","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "11.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "15.4","AddInfo": "some text 2" }, {"OrderNo": "9987","Pkgs": "1","Weight": "5.0","AddInfo": "some text 2" }, ] },"CarrierInfo":{"HeaderTitle": "","Items": [ {"HUQty": "1","HUType": "PLTS","PkgQty": "48","PkgType": "CTNS","Weight": "364 LBS","HM": "","Desc": "Sport Accessories","NMFC": "13431433","Class": "70" }, {"HUQty": "2","HUType": "PLTS","PkgQty": "48","PkgType": "CTNS","Weight": "425 LBS","HM": "","Desc": "Other stuff","NMFC": "234523454","Class": "85" } ] } } pdf_filename = "c:\\temp\\vics_bill_of_lading.pdf" vics_bol(pdf_filename, bill_of_lading_data)