You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: download_encrypted_pdf.md
+25-26Lines changed: 25 additions & 26 deletions
Original file line number
Diff line number
Diff line change
@@ -6,9 +6,9 @@ With a software application capable of storing user data in a database, a user m
6
6
7
7
## Background Story
8
8
9
-
> [M-Pesa](https://www.safaricom.co.ke/personal/m-pesa) is a mobile phone-based money transfer service, payments and micro-financing service, launched in 2007 by Vodafone and Safaricom, the largest mobile network operator in Kenya.
9
+
> [M-Pesa](https://www.safaricom.co.ke/personal/m-pesa) is a mobile phone-based money transfer service, payments, and micro-financing service, launched in 2007 by Vodafone and Safaricom, the largest mobile network operator in Kenya.
10
10
11
-
Safaricom allows its MPesa users to send and receive money from Safaricom and other mobile network users in Kenya. One feature that all MPesa, just like banks, users enjoy, is the provision of a transactions statement. An Mpesa user can request a copy of their transactions statement from the Safaricom MPesa app. On the web, a user can download a copy whereas on mobile, the user will receive a copy of the statement via email. See the image below.
11
+
Safaricom allows its MPesa users to send and receive money from Safaricom and other mobile network users in Kenya. One feature that all MPesa, just like banks, users enjoy, is the provision of a transactions statement. An Mpesa user can request a copy of their transactions statement from the Safaricom MPesa app. On the web, a user can download a copy whereas, on mobile, the user will receive a copy of the statement via email. See the image below.
@@ -25,8 +25,7 @@ This tutorial does not intend to reproduce a clone of the MPesa statement, but r
25
25
5.[Download a copy of the database data](#download-a-copy-of-the-database-data)
26
26
6.[Encrypt the PDF copy](#encrypt-the-pdf-copy)
27
27
28
-
The completed project used to demonstrate the above steps is available [here on GitHub](https://github.com/GitauHarrison/download-encrypted-pdf-copy-of-user-data). To test the live project, click on [this link]().
29
-
28
+
The completed project used to demonstrate the above steps is available [here on GitHub](https://github.com/GitauHarrison/download-encrypted-pdf-copy-of-user-data).
30
29
## Create a simple flask application
31
30
32
31
To begin, we can utilize a minimalist flask structure such as this:
@@ -66,20 +65,20 @@ The `base.html` template will hold the reusable structure we will need throughou
66
65
67
66
## Add a database table to store user data
68
67
69
-
Intentionally, I will use the SQLite database engine. The first thing to do when creating a schema for a database is to figure out what data you want to store. In this case, I will store the user's name, email, age, address and phone number.
68
+
Intentionally, I will use the SQLite database engine. The first thing to do when creating a schema for a database is to figure out what data you want to store. In this case, I will store the user's name, email, age, address, and phone number.
70
69
71
70
### Needed Packages
72
71
73
72
74
-
`flask-sqlalchemy` is a fantastic [Object Relational Mapping (ORM)](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) package that will allow us to interact with the database. It uses highlevel classes, objects and methods to map rows and columns found in a database. It is a wrapper around the [SQLAlchemy](https://www.sqlalchemy.org/) library.
73
+
`flask-sqlalchemy` is a fantastic [Object Relational Mapping (ORM)](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping) package that will allow us to interact with the database. It uses high-level classes, objects, and methods to map rows and columns found in a database. It is a wrapper around the [SQLAlchemy](https://www.sqlalchemy.org/) library.
75
74
76
75
We will need to install it in our virtual environment. A virtual environment helps to isolate the needs of one project from the needs of another.
77
76
78
77
```python
79
78
(venv)$ pip3 install flask-sqlalchemy
80
79
```
81
80
82
-
Once the schema is developed, we will need to create a database by applying the necessary migrations. Since this is the first time we are creating our database, we will need to make only one migration. In the event that we want to add more data to our database, we will need to update the schema and apply the new changes. `flask-migrate` is another useful package we can utilize to create database migrations.
81
+
Once the schema is developed, we will need to create a database by applying the necessary migrations. Since this is the first time we are creating our database, we will need to make only one migration. If we want to add more data to our database, we will need to update the schema and apply the new changes. `flask-migrate` is another useful package we can utilize to create database migrations.
83
82
84
83
```python
85
84
(venv)$ pip3 install flask-migrate
@@ -115,7 +114,7 @@ migrate = Migrate(app, db)
115
114
### Define the database schema
116
115
117
116
118
-
At this point we can define our database schema.
117
+
At this point, we can define our database schema.
119
118
120
119
121
120
`app/models.py`: Create database schema
@@ -162,7 +161,7 @@ User: John Doe
162
161
### Configure the database
163
162
164
163
165
-
To complete the setup of our database, we need to set some configurations which Flask expects. Most importantly, we need to configure the database URI. Following the priciple of separation of concerns, we will need to configure the database URI in the `config.py` file.
164
+
To complete the setup of our database, we need to set some configurations which Flask expects. Most importantly, we need to configure the database URI. Following the principle of separation of concerns, we will need to configure the database URI in the `config.py` file.
166
165
167
166
`config.py`: Database configurations
168
167
@@ -182,7 +181,7 @@ class Config(object):
182
181
183
182
```
184
183
185
-
`DATABASE_URL` is a variable whose value is set in the `.env` file. Should this value not be set, we will use a disk-based database appropriately called `app.db`. This fallback plan ensures that should one option fail, then the other should work. As soon as we run our migrations, shown below, you will notice that this file will be created and located at the top-level directory of the project.
184
+
`DATABASE_URL` is a variable whose value is set in the `.env` file. Should this value not be set, we will use a disk-based database appropriately called `app.db`. This fallback plan ensures that should one option fail, then the other should work. As soon as we run our migrations, shown below, you will notice that this file will be created and located in the top-level directory of the project.
186
185
187
186
188
187
Flask expects these configurations to be registered in the application's instance. We can do this by instantiating the `Config` class in the `__init__.py` file.
@@ -225,14 +224,14 @@ You will see a _migrations_ folder created in the application's root directory.
225
224
## Create fake users and store them in the database
226
225
227
226
228
-
The application will need a lot of users, in their hundreds or even thousands. It will obviously be cumbersome to manually add this number of users to the database one at a time. We will use the [faker](https://faker.readthedocs.io/) package to generate a large number of fake users. These users will automatically be added to the database. Begin by installing the `faker` package.
227
+
The application will need a lot of users, in the hundreds or even thousands. It will be cumbersome to manually add this number of users to the database one at a time. We will use the [faker](https://faker.readthedocs.io/) package to generate a large number of fake users. These users will automatically be added to the database. Begin by installing the `faker` package.
229
228
230
229
```python
231
230
232
231
(venv)$ pip3 install faker
233
232
```
234
233
235
-
The logic used to create the fake users will be in a separate file. This module will be imported in the `app/routes.py` file to display a tabular list of all fake users. First, let us create this module:
234
+
The logic used to create the fake users will be in a separate file. This module will be imported into the `app/routes.py` file to display a tabular list of all fake users. First, let us create this module:
I have implemented here a little contraint logic to limit the total number of fake users that can be stored in the database to only 1000. This is to prevent the database from becoming too large, especially during hosting. The function `limit_num_fake_users()` is called in the `index` view function each time the resource is requested.
346
+
I have implemented here a little constraint logic to limit the total number of fake users that can be stored in the database to only 1000. This is to prevent the database from becoming too large, especially during hosting. The function `limit_num_fake_users()` is called in the `index` view function each time the resource is requested.
348
347
349
348
350
349
### Display template data
351
350
352
351
353
-
At this point, we are ready to render the ``index.html`` template. If you are curious as to how the table looks like that, I would like to introduce you to [Grid.js](https://gridjs.io/). It is a JavaScript table plugin that allows you to display a table in a gridlike fashion. To use it, all you need to do is to include its JavaScript and CSS files in your template.
352
+
At this point, we are ready to render the ``index.html`` template. If you are curious as to how the table looks like that, I would like to introduce you to [Grid.js](https://gridjs.io/). It is a JavaScript table plugin that allows you to display a table in a grid-like fashion. To use it, all you need to do is to include its JavaScript and CSS files in your template.
354
353
355
354
`app/templates/base.html`: Add Grid.js
356
355
@@ -369,7 +368,7 @@ At this point, we are ready to render the ``index.html`` template. If you are cu
369
368
370
369
```
371
370
372
-
The reason I have chosen to add these files in the `base.html` template is because I want all Grid.js JavaScript and CSS files to be inherited by the `index.html` template (which currently is the only child template). The `gridjs_scripts` block will be used only in the `index.html` template.
371
+
The reason I have chosen to add these files in the `base.html` template is that I want all Grid.js JavaScript and CSS files to be inherited by the `index.html` template (which currently is the only child template). The `gridjs_scripts` block will be used only in the `index.html` template.
373
372
374
373
`app/templates/index.html`: Define user table
375
374
@@ -418,15 +417,15 @@ The reason I have chosen to add these files in the `base.html` template is becau
418
417
{% endblock %}
419
418
```
420
419
421
-
So, what exaxtly is going on here? To use Grid.js, all we need to do is pass an `id` selector to an element we want to display our table. In this case, it is the `div` element. Typically, we would have to define a parent `table` element with all its `tr` and `td` elements. However, Grid.js will take care of that for us.
420
+
So, what exactly is going on here? To use Grid.js, all we need to do is pass an `id` selector to an element we want to display in our table. In this case, it is the `div` element. Typically, we would have to define a parent `table` element with all its `tr` and `td` elements. However, Grid.js takes care of that for us.
422
421
423
-
We begin by initializing a Grid object. This object has a [columns](https://gridjs.io/docs/config/columns/) option as well as a [data](https://gridjs.io/docs/config/data/) option. Each column has an `id` and a `name`. The `id` is the identifier of the column and the `name` is the name that appears in column header. The `data` option is an array of objects that will be used to populate the table. Each value in the data array is configured to use a key matching the `id` of the column.
422
+
We begin by initializing a Grid object. This object has a [columns](https://gridjs.io/docs/config/columns/) option as well as a [data](https://gridjs.io/docs/config/data/) option. Each column has an `id` and a `name`. The `id` is the identifier of the column and the `name` is the name that appears in the column header. The `data` option is an array of objects that will be used to populate the table. Each value in the data array is configured to use a key matching the `id` of the column.
424
423
425
424
There are a handful more options that can be used to customize the table. I have used the `search` option to enable a search bar. The `sort` option is used to enable sorting by clicking on the column header. The `pagination` option is used to enable pagination where you can see the buttons at the bottom right of the table.
426
425
427
426
Finally, we render the table using the `render` method. This method takes a selector as an argument. The selector is the `id` of the element where the table will be displayed.
428
427
429
-
The table I am displaying here is a very basic table, useful when table data is minimal. However, if you would like to work with large amounts of data, this table is not the best choice. Users spanning in the hundrends of thousands and even millions will cause a considerable lag between the time the resource is requested and the time it is finally displayed. If you would like to learn more on how to create the best user experiences when working with varing amounts of data in a table, check out the [interactive flask tables with Grid.js tutorial](flask_tables/gridjs.md).
428
+
The table I am displaying here is a very basic table, useful when table data is minimal. However, if you would like to work with large amounts of data, this table is not the best choice. Users spanning in the hundreds of thousands and even millions will cause a considerable lag between the time the resource is requested and the time it is finally displayed. If you would like to learn more on how to create the best user experiences when working with varying amounts of data in a table, check out the [interactive flask tables with Grid.js tutorial](flask_tables/gridjs.md).
430
429
431
430
432
431
@@ -454,16 +453,16 @@ def download_users():
454
453
455
454
```
456
455
457
-
`download_users_data()` function will be called everytime the download link in the home page is clicked. This function is found in a new module called `download_users_pdf.py`. Once again, I have separated the logic used to handle the download of the database data from the rest of the application in a bid to improve scalability should the application grow. Ensure that you create this module within the ``app`` directory.
456
+
`download_users_data()` function will be called every time the download link on the home page is clicked. This function is found in a new module called `download_users_pdf.py`. Once again, I have separated the logic used to handle the download of the database data from the rest of the application in a bid to improve scalability should the application grow. Ensure that you create this module within the ``app`` directory.
458
457
459
458
460
459
```python
461
460
(venv)$ touch app/download_users_pdf.py
462
461
```
463
462
464
-
### Understanding PyPDF2
463
+
### Understanding fpdf
465
464
466
-
We will utilize the [fpdf](https://pyfpdf.readthedocs.io/en/latest/) package to create a PDF file. To understand how it works, let us look at this minimal exmaple:
465
+
We will utilize the [fpdf](https://pyfpdf.readthedocs.io/en/latest/) package to create a PDF file. To understand how it works, let us look at this minimal example:
467
466
468
467
```python
469
468
@@ -483,11 +482,11 @@ You need to make sure that you have installed the package in your virtual enviro
483
482
(venv)$ pip3 install fpdf
484
483
```
485
484
486
-
We begin by creating a `pdf` object from the PyPDF2 package. We then add a page to the document using `pdf.add_page()`. Whatever we are going to write will go to a blank page, and therefore, it makes sense to first create a canvas. If the page is already present, this method will call the [footer](https://pyfpdf.readthedocs.io/en/latest/reference/footer/index.html) method to output the footer, usually a page number. Content will be displayed from the top left margin before the [header](https://pyfpdf.readthedocs.io/en/latest/reference/header/index.html) method is called.
485
+
We begin by creating a `pdf` object from the `fpdf` package. We then add a page to the document using `pdf.add_page()`. Whatever we are going to write will go to a blank page, and therefore, it makes sense to first create a canvas. If the page is already present, this method will call the [footer](https://pyfpdf.readthedocs.io/en/latest/reference/footer/index.html) method to output the footer, usually a page number. Content will be displayed from the top left margin before the [header](https://pyfpdf.readthedocs.io/en/latest/reference/header/index.html) method is called.
487
486
488
487
The `set_font()` method begins by defining the font type we would like to use, and the font size. The `B` indicates that we would like to use a bold font. If you prefer not to use bold or italics, you can leave this option blank.
489
488
490
-
Contents are normally printed in rectangular boxes called [cells](https://pyfpdf.readthedocs.io/en/latest/reference/cell/index.html). The `cell()` method takes three arguments: the width of the cell, the height of the cell, and the text to be displayed. If you woul like a border around a cell, you can optionally add "1" to the end of the arguments.
489
+
Contents are normally printed in rectangular boxes called [cells](https://pyfpdf.readthedocs.io/en/latest/reference/cell/index.html). The `cell()` method takes three arguments: the width of the cell, the height of the cell, and the text to be displayed. If you would like a border around a cell, you can optionally add "1" to the end of the arguments.
491
490
492
491
The document is finally closed using the `output()` method. This method takes two arguments: the name of the file to be created, and the destination. The destination can be either `F` for a file or `S` for a string.
493
492
@@ -570,7 +569,7 @@ I think everything is self-explanatory. Notice how the output file is created in
570
569
571
570
## Encrypt the PDF copy
572
571
573
-
Encryption is a way to protect the PDF file from being read by anyone. Do not be frightened by the word. It is actually easy to encrypt a pdf. The last package we are going to use is the [PyPDF2](https://pyfpdf.readthedocs.io/en/latest/Tutorial/index.html).
572
+
Encryption is a way to protect the PDF file from being read by anyone. Do not be frightened by the word. It is easy to encrypt a pdf. The last package we are going to use is the [PyPDF2](https://pyfpdf.readthedocs.io/en/latest/Tutorial/index.html).
`encrypt_pdf()` takes in two arguments: the name of the input PDF file, and the password to be used for encryption. It then opens a file reader and writer, and loops through the pages of the input PDF. Adding each page to the writer, it encrypts the PDF using the password. Finally, it closes the file reader and writer.
608
+
`encrypt_pdf()` takes in two arguments: the name of the input PDF file, and the password to be used for encryption. It then opens a file reader and writer and loops through the pages of the input PDF. Adding each page to the writer, it encrypts the PDF using the password. Finally, it closes the file reader and writer.
610
609
611
610
This encryption function will then be called in the `download_users()` view function to immediately password-protect the just downloaded file.
612
611
@@ -626,6 +625,6 @@ def download_users():
626
625
return redirect(url_for('index'))
627
626
```
628
627
629
-
I am passing in the password as a simple string. In a production environment, you would want to store the password in a more secure way, or use other user identification methods such as their phone number.
628
+
I am passing in the password as a simple string. In a production environment, you would want to store the password more securely or use other user identification methods such as their phone number.
0 commit comments