Story: This series of articles assume you are an employee of the City of Sacramento’s IT Department.
At the end of each month, a CSV file is sent to the Chief of Police outlining the crime statistics for the current month.
However, the Chief prefers to view the output in a styled PDF format based on a District/Beat of his choosing.
Part 2 of this series is a continuation of Part 1 and focuses on:
- reading in the CSV created in Part 1
- modifying the column headings for clarity
- creating and formatting a PDF from the data
- saving the PDF
Preparation
This article assumes you have completed the following from Part 1:
- Installed the Pandas library.
- Created the CSV outlined in Part 1.
import pandas as pd from fpdf import FPDF import csv import datetime import yagmail
Read in CSV to a List
In preparation for converting the CSV file to a PDF, this code snippet reads in the CSV file (3C.csv
) created in Part 1 and located in the current working directory.
with open(f'{rpt_num}.csv', 'r') as csvfile: data_list= list(csv.reader(csvfile))[1:] print(data_list[:3]
- Line [1] opens the CSV in read (
r
) mode created in Part 1 (3C.csv
) located in the current working directory. - Line [2] reads in all rows (excluding the header row) and converts the rows to a list. This output saves to
data_list
. - Line [3] outputs the top three (3) rows from
data_list
to the terminal.
Output:
[['0', '1/1/06 0:00', '3108 Occidental Dr', '1115', '10851(A)Vc Take Veh W/O Owner'], |
💡 Note: Before continuing, you may want to remove Line [3].
Save Logo
All documents from the City of Sacramento require the addition of their logo. To save the logo to the current working directory, perform the following steps:
- With your mouse, hover over the logo shown below.
- View the pop-up menu by performing a right-mouse click.
- Click to select
Save image as
from the pop-up menu. - From the
Save As
dialog box, perform the following steps:- Browse to determine the current working directory.
- Enter the filename
sacramento_logo
. - Click the
Save
button.

Set PDF Variables
This code snippet declares several variables that the code will use to create the PDF.
pdf_name = f'{rpt_num}.pdf' rpt_hdgs = ['Row #', 'Date/Time', 'Address', 'Grid', 'Description'] cwidths = [20, 40, 50, 30, 55] rpt_font_sz = 7 hdg_font_sz = 11 line_height = 6
- Line [1] creates a filename based on the variable
rpt_num
from Part 1. The extension ‘.pdf
‘ is appended. The output saves topdf_name
(example:3C.pdf
). - Line [2] creates a list of new Column Headings. This output saves to
rpt_hdgs
. - Line [3] creates a list of column widths in the PDF.
- Line [4-5] assigns the font size for the report and the header.
- Line [6] assigns a line-height for row spacing in the PDF.
Create PDF Class
In this section, a new class is declared. This class contains header and footer functions to style the PDF.
class PDF(FPDF):
Inside the PDF Class, indent and add in the following code. This code styles the header and displays it on each PDF page.
def header(self): today = datetime.date.today() date_fmt = today.strftime("%B" " " "%d" ", " "%Y") self.l_margin = 6 self.r_margin = 6 self.set_font('Arial', '', rpt_font_sz) self.image('sacramento_logo.png', 10, 8, 36) self.cell(80) self.set_font('Arial', '', hdg_font_sz) self.set_text_color(43,60,102) self.cell(30, 3, f'District/Beat: {rpt_num}', 0, 0, 'C') self.set_font('Arial', '', rpt_font_sz) self.cell(-30, 11, f'{date_fmt}', 0, 0, 'C') self.ln(12) self.set_fill_color(240,248,255) col = 0 while col < len(rpt_hdgs): col_width = cwidths[col] self.cell(col_width, line_height, rpt_hdgs[col], 0, 0, fill=True) col += 1 self.ln(12)
- Line [1] declares the
header
function. - Line [2-5] sets the current date and margins for the PDF.
- Line [6] sets the font name and size.
- Line [7] sets the image file to display in the top left corner of the PDF.
- Line [8] specifies the position of the PDF heading from the left margin.
- Line [9-11] sets the top margin’s font name, size, and text.
- Line [12-13] sets the current date’s font name, size, and offset from the top margin.
- Line [14] sets the offset from the current date to the next row (table header row).
- Line [15] sets the header row background color.
- Line [16] creates a counter variable col for the
while
loop. - Line [17-20] creates a
while
loop that displays the header row columns using the variablerpt_hdgs
. - Line [21] sets the offset from the heading row to the next row.
The above header code styles the PDF header as shown below.

Inside the PDF Class, indent and add in the following code. This code styles the footer and displays it on each PDF page.
def footer(self): # self.set_y(-15) self.set_font('Arial', 'I', rpt_font_sz) self.set_fill_color(240,248,255) self.cell(0, line_height, 'Report Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C', fill=True)
- Line [1] declares the
footer
function. - Line [2] sets the font style and size for the footer.
- Line [3] sets the background color for the footer row.
- Line [4] sets the placement and footer text for each page.
Output:
The above footer code styles the PDF footer as shown below.

Put Data in PDF
Now that the header and footer code has been created, we need code to display the data from the data_lst
created in Part 1.
def convert_to_pdf(data_list): pdf = PDF() pdf.alias_nb_pages() pdf.add_page() pdf.set_font('Arial', '', rpt_font_sz) row_count = 0 while row_count < len(data_list): col = 0 for c in cwidths: pdf.cell(c, 0, data_list[row_count][col], align='L', border=0) col += 1 pdf.ln(4) row_count += 1 pdf.output(pdf_name, 'F') convert_to_pdf(data_list)
EnlighterJS Syntax Highlighter
- Line [1] declares a function with one (1) parameter,
data_lst
. - Line [2] declares an instance of the class
PDF
. - Line [3-5] sets up the PDF pages and font details for the data.
- Line [6-14] instantiates a
while
loop to output the data to the PDF based on rows/page. - Line [15] sets the output to a PDF.
- Line [16] calls the
convert_to_pdf
function with the appropriate parameter to create the PDF.
Output:
Depending on the report selected, the output will vary.

Create a PDF Complete Code
Below is the complete code from Part 1 and Part 2.PythonEnlighterJS Syntax Highlighter
Summary
In this article, you learned how to:
- Convert a CSV to a List.
- Save an Image.
- Create a Class.
- Create functions within a Class.
- Create and save a PDF file.
What’s Next
In Part 3 of this series, you will learn how to attach the PDF file to a Gmail account and send the email to a recipient.
Zählmarken für VG WORT
Toggle panel: Zählmarken für VG WORT
Table of Contents
Toggle panel: Table of Contents
- Post
- Block
Status & visibility
VisibilityPublicPublishFebruary 17, 2022 2:24 pmPost FormatAsideImageLinkQuoteStandardStatusVideoMove to trash185 Revisions
Permalink
- Line [1] declares a function with one (1) parameter,
data_lst
. - Line [2] declares an instance of the class
PDF
. - Line [3-5] sets up the PDF pages and font details for the data.
- Line [6-14] instantiates a
while
loop to output the data to the PDF based on rows/page. - Line [15] sets the output to a PDF.
- Line [16] calls the
convert_to_pdf
function with the appropriate parameter to create the PDF.
Output:
Depending on the report selected, the output will vary.

Create a PDF Complete Code
Below is the complete code from Part 1 and Part 2.
import pandas as pd from fpdf import FPDF import csv import datetime import yagmail cols = ['cdatetime', 'address', 'district', 'beat', 'grid', 'crimedescr'] df = pd.read_csv('crimes.csv', usecols=cols) df.sort_values('cdatetime', inplace=True, ascending=True) df['beat'] = df['beat'].str.rstrip() df = df.apply(lambda x: x.astype(str).str.title()) lst = '123456ABCQ' rpt_num = None while True: rpt_num = input('Select a District/Beat (1A-6C or Q to quit): ').upper() if rpt_num == 'Q': exit() elif rpt_num[0] not in lst[0:6] or rpt_num[1] not in lst[6:9]: print('You entered an invalid selection!') else: break print(f'Report {rpt_num} generating!') the_filter = (df.query(f"beat == '{rpt_num}'")) filt_cols=['cdatetime','address','grid','crimedescr'] the_filter.to_csv(f'{rpt_num}.csv', columns=filt_cols) print(f'Report {rpt_num}.csv resides in the current working directory!') with open(f'{rpt_num}.csv', 'r') as csvfile: data_list = list(csv.reader(csvfile))[1:] pdf_name = f'{rpt_num}.pdf' rpt_hdgs = ['Row #', 'Date/Time', 'Address', 'Grid', 'Description'] cwidths = [20, 40, 50, 30, 55] rpt_font_sz = 7 hdg_font_sz = 11 line_height = 6 class PDF(FPDF): def header(self): today = datetime.date.today() date_fmt = today.strftime("%B" " " "%d" ", " "%Y") self.l_margin = 6 self.r_margin = 6 self.set_font('Arial', '', rpt_font_sz) self.image('sacramento_logo.png', 10, 8, 36) self.cell(80) self.set_font('Arial', '', hdg_font_sz) self.set_text_color(43,60,102) self.cell(30, 3, f'District/Beat: {rpt_num}', 0, 0, 'C') self.set_font('Arial', '', rpt_font_sz) self.cell(-30, 11, f'{date_fmt}', 0, 0, 'C') self.ln(12) self.set_fill_color(240,248,255) col = 0 while col < len(rpt_hdgs): col_width = cwidths[col] self.cell(col_width, line_height, rpt_hdgs[col], 0, 0, fill=True) col += 1 self.ln(12) def footer(self): # self.set_y(-15) self.set_font('Arial', 'I', rpt_font_sz) self.set_fill_color(240,248,255) self.cell(0, line_height, 'Report Page ' + str(self.page_no()) + '/{nb}', 0, 0, 'C', fill=True) def convert_to_pdf(data_list): pdf = PDF() pdf.alias_nb_pages() pdf.add_page() pdf.set_font('Arial', '', rpt_font_sz) row_count = 0 while row_count < len(data_list): col = 0 for c in cwidths: pdf.cell(c, 0, data_list[row_count][col], align='L', border=0) col += 1 pdf.ln(4) row_count += 1 pdf.output(pdf_name, 'F') convert_to_pdf(data_list)
Summary
In this article, you learned how to:
- Convert a CSV to a List.
- Save an Image.
- Create a Class.
- Create functions within a Class.
- Create and save a PDF file.
What’s Next
In Part 3 of this series, you will learn how to attach the PDF file to a Gmail account and send the email to a recipient.