Skip to content

Commit c8cc46d

Browse files
committed
Doc: Fix grammar and spelling errors
1 parent 8bcadb4 commit c8cc46d

File tree

1 file changed

+25
-26
lines changed

1 file changed

+25
-26
lines changed

download_encrypted_pdf.md

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ With a software application capable of storing user data in a database, a user m
66

77
## Background Story
88

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.
1010
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.
1212

1313
![Mpesa statement](images/download_encrypted_pdf/mpesa_statement.png)
1414

@@ -25,8 +25,7 @@ This tutorial does not intend to reproduce a clone of the MPesa statement, but r
2525
5. [Download a copy of the database data](#download-a-copy-of-the-database-data)
2626
6. [Encrypt the PDF copy](#encrypt-the-pdf-copy)
2727

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).
3029
## Create a simple flask application
3130

3231
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
6665

6766
## Add a database table to store user data
6867

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.
7069

7170
### Needed Packages
7271

7372

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 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.
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.
7574

7675
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.
7776

7877
```python
7978
(venv)$ pip3 install flask-sqlalchemy
8079
```
8180

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.
8382

8483
```python
8584
(venv)$ pip3 install flask-migrate
@@ -115,7 +114,7 @@ migrate = Migrate(app, db)
115114
### Define the database schema
116115

117116

118-
At this point we can define our database schema.
117+
At this point, we can define our database schema.
119118

120119

121120
`app/models.py`: Create database schema
@@ -162,7 +161,7 @@ User: John Doe
162161
### Configure the database
163162

164163

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.
166165

167166
`config.py`: Database configurations
168167

@@ -182,7 +181,7 @@ class Config(object):
182181

183182
```
184183

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.
186185

187186

188187
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.
225224
## Create fake users and store them in the database
226225

227226

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.
229228

230229
```python
231230

232231
(venv)$ pip3 install faker
233232
```
234233

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:
236235

237236

238237
```python
@@ -344,13 +343,13 @@ def index():
344343
return render_template('index.html', all_users=all_users)
345344
```
346345

347-
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.
348347

349348

350349
### Display template data
351350

352351

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 grid like 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.
354353

355354
`app/templates/base.html`: Add Grid.js
356355

@@ -369,7 +368,7 @@ At this point, we are ready to render the ``index.html`` template. If you are cu
369368

370369
```
371370

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.
373372

374373
`app/templates/index.html`: Define user table
375374

@@ -418,15 +417,15 @@ The reason I have chosen to add these files in the `base.html` template is becau
418417
{% endblock %}
419418
```
420419

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.
422421

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.
424423

425424
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.
426425

427426
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.
428427

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).
430429

431430

432431

@@ -454,16 +453,16 @@ def download_users():
454453

455454
```
456455

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.
458457

459458

460459
```python
461460
(venv)$ touch app/download_users_pdf.py
462461
```
463462

464-
### Understanding PyPDF2
463+
### Understanding fpdf
465464

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:
467466

468467
```python
469468

@@ -483,11 +482,11 @@ You need to make sure that you have installed the package in your virtual enviro
483482
(venv)$ pip3 install fpdf
484483
```
485484

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.
487486

488487
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.
489488

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.
491490

492491
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.
493492

@@ -570,7 +569,7 @@ I think everything is self-explanatory. Notice how the output file is created in
570569

571570
## Encrypt the PDF copy
572571

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).
574573

575574
```python
576575
(venv)$ pip3 install PyPDF2
@@ -606,7 +605,7 @@ def encrypt_pdf(input_pdf, password):
606605
f.close()
607606
```
608607

609-
`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.
610609

611610
This encryption function will then be called in the `download_users()` view function to immediately password-protect the just downloaded file.
612611

@@ -626,6 +625,6 @@ def download_users():
626625
return redirect(url_for('index'))
627626
```
628627

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.
630629

631630
![Downloaded PDF](images/download_encrypted_pdf/password_protected_pdf.gif)

0 commit comments

Comments
 (0)