-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwrite-up.html
More file actions
2871 lines (2713 loc) · 184 KB
/
write-up.html
File metadata and controls
2871 lines (2713 loc) · 184 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta name="author" content="Daniel Agapov">
<title>Write-Up | Robotics Summative</title>
<link rel="stylesheet" href="./css/write-up-style.css">
</head>
<body>
<header>
<div class="container">
<div id="project">
<h1>Robotics Summative</h1>
</div>
<nav>
<ul>
<li><a href="home.html">Home</a></li>
<li class="current"><a href="write-up.html">Write-Up</a></li>
<li><a href="about-ravenscout.html">About RavenScout</a></li>
</ul>
</nav>
</div>
</header>
<section id="main">
<div class="container">
<article id="main-column">
<h1 class="page-title">Write-Up</h1>
<p>
For my summative write-up for the year 2020, I'll be going over
the material in <a
href="https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world">
The Flask Mega-Tutorial</a> (Chapters 1-12). To follow along and understand what's happening,
some previous
OOP (Object-Oriented Programming) knowledge is needed, as well as, what I'd classify as an
intermediate/expert
understanding of the Python and HTML5 programming languages. Although, the 'expert' level things can
be picked up on in the later chapters,
considering the fact that I personally came into learning this material as about an intermediate at
both languages.
For things unique to building a dynamic website, you're
hopefully going to find everything you need right here in this write-up.
</p>
<p>Below are clickable links for you to jump straight to
whichever chapter you're on in reading this write-up:</p>
<ul>
<li><a href="#chapter1">Chapter 1: Hello, World!</a></li>
<li><a href="#chapter2">Chapter 2: Templates</a></li>
<li><a href="#chapter3">Chapter 3: Web Forms</a></li>
<li><a href="#chapter4">Chapter 4: Database</a></li>
<li><a href="#chapter5">Chapter 5: User Logins</a></li>
<li><a href="#chapter6">Chapter 6: Profile Page and Avatars</a></li>
<li><a href="#chapter7">Chapter 7: Error Handling</a></li>
<li><a href="#chapter8">Chapter 8: Followers</a></li>
<li><a href="#chapter9">Chapter 9: Pagination</a></li>
<li><a href="#chapter10">Chapter 10: Email Support</a></li>
<li><a href="#chapter11">Chapter 11: Styling</a></li>
<li><a href="#chapter12">Chapter 12: Dates and Times</a></li>
</ul>
</article>
<section id="chapter1">
<br><br><br>
<h2><a name="chapter1">Chapter 1: Hello, World!</a></h2>
<h3><u>Installing Python</u></h3>
<p>Firstly, you need to ensure that <i><b>Python</b></i> is installed onto your machine.
This can be done by visiting the <a href="https://www.python.org/">python.org</a> website.
Make sure to have it be at least Python3.6 or later, since older versions
aren't supported by Flask. Also, make sure that you select "add to PATH" upon
installation. By installing Python, you also get pip, which will allow you to
install packages/libraries directly from whatever terminal window you're using.
You can also use your terminal window to check if python is installed by typing
in "python3", or "python" if that doesn't work. This'll display your current version
and enter you into the Python Interpreter, which will be used later for various
python commands, so remember how to access it.
<br><br><img src="./img/python into terminal.jpg">
</p>
<h3><u>Installing Flask</u></h3>
<p>Once Python is installed, along with pip, you can then use the command below to install packages
(like <b><i>Flask</i></b>).
<br><br><img src="./img/pip install package.jpg">
</p>
<p>If you're having issues with running this command later on, check to see if you are on
the administrator's account, since it won't work on any other user's account.
</p>
<p>In order to take care of issues such as having to maintain different versions of packages
for different applications, Python uses the concept of <i><b>virtual environments</b></i>.
It's essentially a copy of the python interpreter. If you install a package into this virtual
environment, it'll remain local and not affect the rest of
your system. Also, another huge benefit to having a virtual environment setup in your
folder ("microblog"), is the portability of your project to other users/computers. Lets say,
if you wanted to put all this code inside of a GitHub repository, like I did when first
making this website with Hassan, you'll be able to clone the repository, along with the venv.
This means that you won't have to go through the process of installing every single package and
such on every single device, but instead, they're ready to go inside of the venv folder.
</p>
<p>Yet another benefit to virtual environments, is that they are actually owned by the user that created
them. Which means, that if you faced the issue earlier of not being the administrator on
your computer, you can run commands in here freely.
</p>
<p>The first step to this is making a directory to have this virtual environment in.
<br><br><img src="./img/mkdir microblog.jpg">
</p>
<p>Then, you need to enter into the directory you've just made.
<br><br><img src="img/cd microblog.jpg">
</p>
<p>Now, you can make a new virtual environment, that'll be named "venv". The first word "venv"
is for the Python virtual environment package, while the second "venv" is for your
virtual environment's name, which you can change if you'd like.
<br><br><img src="./img/python3 -m venv venv.jpg">
</p>
<p>Note that in some operating systems you may need to use "python" instead of "python3" in
the command above, since some installations use "python" for Python2.x releases and "python3" for
the 3.x
releases, while others simply use "python" for the 3.x releases.
</p>
<p>Once you have your virtual environment created, you need to tell your system that you wish to use it.
To activate your virtual environment you need to type this command on a UNIX (Mac or Linux) system:
<br><br><img src="./img/unix activation of venv.jpg">
</p>
<p>If you are using the Microsoft Windows command prompt window (like I am), the activation command is
slightly different:
<br><br><img src="./img/windows activation.jpg">
</p>
<p>Once in your virtual environment, you can then use the "pip install (package-name)" command from
earlier to install flask.
<br><br><img src="./img/pip install flask.jpg">
</p>
<h3><u>"Hello, World" Flask Application</u></h3>
<p>
The tutorial I followed for this website made various Python and HTML5 files/folders by using
the terminal and making directories instead of just doing it the easy way. I found that a much
simpler method of doing this was by just doing it all in your IDE (Integrated Development
Environment), like Visual Studio Code. I'm assuming that anyone reading this uses this IDE,
considering it's the one we use at robotics and is among the world's most popular IDEs. If
you do choose to not use Visual Studio Code, just know that most of what I'm saying will apply
to any other IDE, or at least shouldn't be too difficult to figure out yourself. If you want to
follow along either: install an IDE such as VSC (<a
href="https://code.visualstudio.com/download">Visual Studio Code Installation</a>) or figure out
how to make folders and files within the terminal.
</p>
<p>So, let's get into setting up our first folder and Python file inside of it. Firstly,
you want to open a new window of Visual Studio Code, click "File" -> "Open Folder"
<br><br><img src="./img/open folder.jpg">
</p>
<p>Then, navigate through to your folder called "microblog". If you aren't sure where this folder is,
you can do a search or look at the file path from when you were doing commands in the terminal, as
shown below.
<br><br><img src="./img/microblog file path.jpg">
</p>
<p>Next, you just want to click "Select Folder". Once you have the folder open, you'll notice how the
"venv" folder has already been made inside of your "microblog" folder. It contains all the things
you've installed into your virtual environment thus far. Now, you want to make a new folder at the
same 'level' as this folder. You do this by clicking on the button shown below to make a new folder
directly inside the "microblog" one - make sure you haven't selected the "venv" folder and are
just in the "microblog" folder. You're going to name this new folder "app".
<br><br><img src="./img/app.jpg">
</p>
<p>Inside of the new app folder, you're going to make a new file by clicking on this button, and name it
"__init__.py". This'll let Visual Studio Code know about which kind of file it is by its file
extension.
This is ".py" in our case, which is short for Python. "__init__" signifies it as being the
initialization file.
<br><br><img src="./img/__init__ creation.jpg">
</p>
<p>Now, we finally get to the fun part of making the website - coding it! Feel free to copy the code
exactly to
follow along. Although, I would highly suggest typing it out yourself to get a feel for actually
doing so,
rather than copy-pasting it.
<br><br><img src="./img/c1 init code p1.jpg">
</p>
<p>The Python script above instantiates the application object from the Flask class. The __name__
variable used as
a parameter to the Flask class is a predefined variable in Python, which is set to the name of the
module in which
it's used. Later on, Flask will use the location of this module passed here as a starting point when
it
needs to load associated resources such as template files (HTML5 front-end files). We then import
"routes" from the
folder we made earlier called "app". In case you were curious, the reason as to why we're importing
routes at the bottom,
instead of at the top like usual, is because routes references the app variable created here in this
file. You don't need
to worry about this too much for now, but just know that this is a little work-around for a common
issue with Flask applications,
being that they mutually reference each other sometimes and can cause an error by doing so. However,
as of now, the routes file
has not yet been created. So, let's do that!
</p>
<p>Just like how you did it earlier, you're going to be making yet another python file inside of the
"app" folder, name
it "routes.py". Once you've done this, type this code into that file.
<br><br><img src="./img/routes.jpg">
</p>
<p>This file will be used to make various 'routes' to the URLs for the application, or its different
'pages'/'views'.
The routes are the different URLs that the application implements. With the Flask web framework,
handlers for the
application routes are written as Python functions, called view functions. These functions are
mapped (like a pointer)
to one or more route URLs so that Flask knows what to execute under that URL.
</p>
<p>This view function is very simple, all it does when a user accesses this URL (either '/' or '/index')
is return "Hello, World!"
The two @app.route lines above the function are what's known as decorators. They're not part of the
flask framework, rather
they're built into Python, meaning you might see them elsewhere in other projects of yours later on.
They essentially act
like modifiers for the function under them. This decorator take the URL as an argument inside of the
brackets and associates
it with the function. This means that when a user accesses either of the two URLs, the function
below them will be executed.
Most of the time, though, these functions only require one of these decorators in order to get
called. It's just that whether
the user is on the default (blank or '/') URL, or on the '/index' URL, we want it to run that same
function.
</p>
<p>To finally get the application up and running on your browser, there is one more Python script you
need to make. It should be
made a top 'level' of all your files inside of microblog. Meaning, it is on the same 'level' as
"app" or "venv". You do this
by clicking on the new file button after having clicked on the microblog folder so that you make a
file directly inside of
this folder.
<br><br><img src="./img/microblog.jpg">
</p>
<p>This one line of code just imports the application instance from the app package. I know it can be a
little confusing with having them
as the same name, but to re-clarify: the first "app" is the variable in which we instantiated the
app class object inside of, and the
second "app" is the package class it's getting instantiated from in the app.py file. Feel free to
change the variable name or importing
the app package as a different name by doing the following: "import app as (new name for it)" as
opposed to simply doing "import app"
at the top of the "__init__.py" file.
</p>
<p>The last step before running your application on a localhost is setting up the FLASK_APP environment
variable. On a windows machine,
you need to do this following command:
<br><br><img src="./img/set flask_app.jpg">
</p>
<p>On other machines, you need to type "export" instead of "set" like so:
<br><br><img src="./img/export flask_app.jpg">
</p>
<p>Get used to running this command in your virtual environment. You're going to have to do it every
single time you re-launch your
terminal to run this application. Also, if you're running multiple flask applications, you're going
to have to make sure you're
in the right flask_app environment. This has caused a lot of frustration in the past for me, when I
didn't realize I had actually
just been in the wrong flask_app environment. Save yourself the headache and always remember to do
this when opening up the terminal
for running your flask application.
</p>
<p>Finally, the last thing left to do is run your flask application.
<br><br><img src="./img/flask run.jpg">
</p>
<p>To access the application that's just been run on your computer, you can type "localhost:5000" into
your browser's address bar.
If you remember from earlier, though, we set up 2 different URL decorators to access this same page.
This was the first one ('/'),
alternatively, you can also type "localhost:5000/index". However, these are the only two URLs you
can type in at the moment.
In the terminal, once you're done, you can press Ctrl-C to stop the application from running.
</p>
<p>Before we leave off this chapter, there's one more thing to mention. If you'll find it tedious to
reset the flask_app environment
variable every single time you open up the terminal window, you can setup a file at the top 'level'
of your "microblog" folder
that'll be called ".flaskenv".
<br><br><img src="./img/flaskenv.jpg">
</p>
<p>Then, after doing this you need to type in this command into the terminal:
<br><br><img src="./img/pip install python-dotenv.jpg">
</p>
<p>Now, to run your flask application again in the future, all you need to do is go into the terminal
and follow these steps:
<ol>
<li>"cd (directory)" into the right directory, which should be "microblog" at the end of the file
path you're in</li>
<li>activate your virtual environment</li>
<li>if you didn't make the .flaskenv file and haven't automatically set your flask environment
variable, do so
by typing in the proper command: "set"/"export" FLASK_APP=microblog.py</li>
<li>type "flask run" into your terminal</li>
<li>visit the "localhost:5000" address in your browser</li>
<li>press Ctrl-C to stop deployment</li>
</ol>
</section>
<section id="chapter2">
<br><br><br>
<h2><a name="chapter2">Chapter 2: Templates</a></h2>
<h3><u>Introduction To Templates</u></h3>
<p>Since, we don't yet have users on our webpage, we don't have to worry about that yet, instead, we can
make mock ones.
We're going to be implementing these as Python dictionaries, linking a user's username to their
username. In our case,
we'll name our first mock user "Bogar". Having mock objects or components to your website is a
really useful thing in
web development. It allows you to concentrate on one part of the application without having to worry
about the other
parts of the system that don't exist yet, while still being able to test it out like it were
implemented already.
</p>
<p>Right now, we want to create our homepage that'll display a custom message based on our user's
username being "Bogar".
The first step to that is to set a mock username to the variable "user".
<br><br><img src="./img/making bogar user.jpg">
</p>
<p>Now, though, we need to return something from this view function and display it on the website. We'll
do this by having
an HTML5 file. To make this file, we first need to make a new folder called "templates" inside the
"app" folder. Then,
inside of the new "templates" folder, make a new file called "index.html". This is the standard name
among various HTML
applications to indicate the main page of a website.
<br><br><img src="./img/index html creation.jpg">
</p>
<p>Now, we need to give it a title to have displayed in the tab bar and our message inside of the body.
<br><br><img src="./img/index code.jpg">
</p>
<p>This is a pretty basic HTML page, other than the {{...}} symbols. These represent placeholders for
dynamic content, like variables.
</p>
<p>Now, we need to head back to the routes.py file and return something in the index view function. For
this, we'll be importing
a function from flask called "render_template". This function takes a template
filename ('index.html') and a variable list of
template arguments in our placeholders and returns the template, now with actual values instead of
placeholders for those values.
<br><br><img src="./img/importing render template.jpg">
</p>
<p>You can now, of course, try this in your browser once again, to see how the content got modified.
</p>
<h3><u>User Posts</u></h3>
<p>Once again, we're going to make use of mock users and also some new mock posts to display users and
their posts on the home page.
<br><br><img src="./img/regular adding posts.jpg">
</p>
<p>I know that this can seem pretty complex, but I'll try explaining these different data types and
colour-coding parts of it in the image.
To represent user posts we'll be using a list. Inside this list, there'll be two dictionaries with
"author" and "body" fields.
The first post dictionary's first item's "author" field has a dictionary as its value, and in this
dictionary, lies the key ('username') along
with the value ('Daniel').
<br><br><img src="./img/adding posts.jpg">
</p>
<p>Now, we have to update the index template. Since, the list of posts can have any number of elements,
it's up to the view function to decide how many posts are going to be presented in the page. The
template cannot
make any assumptions about how many posts there are, so it needs to be prepared to render as many
posts as the view sends.
We can do this by making a simple for loop that iterates through the posts and, for each post, has
the post author's username
and its post body to put in a paragraph tag. The little conditional I put in the head tag is pretty
simple to understand. It
just evaluates whether there's already a title for this page, and either uses it in the title before
"- Microblog" or generates
the generic title of "Welcome to Microblog".
<br><br><img src="./img/new index.jpg">
</p>
<h3><u>Template Inheritance</u></h3>
<p>Just like this website you're currently on, many have navigation bars at the top of their pages, with
a few links. The links
on this website are: "HOME", "WRITE-UP", and "ABOUT RAVENSCOUT". This isn't very hard to do normally
by just copy-pasting the
same links into every single file, but there's a simpler way to do this in HTML5. For good practice,
we want to avoid repetition
at all costs in programming if it's more efficient.
</p>
<p>Jinga2, which is included in Flask, has a template inheritance feature that helps us with avoiding
repetition in our HTML files.
It essentially allowed you to make one 'base' file, which we'll call "base.html" that'll have all
parts of the page layout that
are common to all templates, from which other templates are derived from.
</p>
<p>Let's go ahead and make this file in the templates folder and call it "base.html". It'll include a
simple navigation bar and the
title conditional logic implemented earlier into the index.html file.
<br><br><img src="./img/base html.jpg">
</p>
<p>In this main template, the block control statement (anything inside of {...}) is there to define the
place where the derived
templates can insert themselves into this one. Blocks are given a unique name, which derived
templates can reference when they
provide their proper content. Now, we can adapt the index.html file to now inherit from this base
template we've just made.
<br><br><img src="./img/new index inheritance.jpg">
</p>
<p>The base.html template file will now be taking care of all the general page structure parts, so all
that's required in the
index.html file is its proper content for that page. The "extends" control statement establishes the
inheritance link between the
two template files. In more complicated terms, this means that Jinja2 know that when it is asked to
render index.html it needs to
embed it inside base.html. Two two templates have matching block statements named "content", and
this is how Jinja2 knows how to
combine the two templates into one. Now, for any additional pages, this base.html template has you
covered and will just need to be
extended once again, in the same manner. This is how we can achieve the same look and feel of two
web sub-pages without the need for duplication.
</p>
</section>
<section id="chapter3">
<br><br><br>
<h2><a name="chapter3">Chapter 3: Web Forms</a></h2>
<h3><u>Introduction To Flask-WTF</u></h3>
<p>We are now going to be addressing our little issue of only having mock users. This will be done
through the use
of web forms by accepting input to store actual users. Web forms are key in most web applications,
being one
of the most basic building blocks to making a great web application. We'll be using forms to allow
users to submit
posts, and also for logging in to the application.
</p>
<p>This is where the Flask-WTF extension will come in handy. There will also be many more flask
extensions to come
in the future. Once again, you can install this into your virtual environment by using the command
seen below, like you've done previously.
<br><br><img src="./img/pip install flask-wtf.jpg">
</p>
<h3><u>Configuration</u></h3>
<p>The application we've made so far is very simple, and for that reason we don't need to worry about
its
configuration. However, in many cases with web apps, you might find yourself wanting to configure
some variables
that'll get passed to the Flask web framework to personalize your website.
</p>
<p>There's several formats for the application to specify configuration options. The format we're going
to be using
is to store configuration variables in a class. This file named "config.py" will be made directly
inside the main
"microblog" folder, on the same level as "microblog.py"
<br><br><img src="./img/config py.jpg">
</p>
<p>If we eventually get to need more than one configuration set, then there is an option to make
subclasses of this
configuration class.
</p>
<p>The SECRET_KEY configuration variable is used as a value for a cryptographic key. Flask uses this to
prevent
a Cross-Site Request Forgery attack or CSRF. The value of this secret key is set as an expression
with two
terms, joined by the or operator. The first term looks for the value of an environment variable,
also called
SECRET_KEY. The second term, is just a hardcoded string. This is a pttern that you'll see repeated
often for
config vaiables.
</p>
<p>The idea's that a value sourced from an environment variable is preferred, but if the environment
doesn't
define the variable, then the hardcoded string is used instead. When you're developing this web
application,
the security requirements are low, so you can just ignore this and let the hardcoded string be used.
However,
when it's deployed onto a production server, you should set a unique and difficult to guess value as
the
environment variable.
</p>
<p>Now that the config file's setup, we need to let Flask know about it. So, we need to go back to the
Flask application
instance in the __init__.py file. To do this, we need to call the method known as "from_object" and
have "Config"
as a parameter.
<br><br><img src="./img/__init__ py changes for config.jpg">
</p>
<p>In case you're confused about why it seems as though I'm importing something from itself, seeing as
"flask" and "Flask" or "config"
and "Config" seem like the same things, just know that I'm referring to the uppercase ones as their
respective classes, whereas
"flask" and "config" are modules (like the file "config.py").
</p>
<h3><u>User Login Form</u></h3>
<p>The Flask-WTF extension uses Python classes to represent web forms. A form class just defines the
fields of the form as class
variables. To implement this new feature, we're going to be creating a new file called "forms.py"
inside of the "app" folder.
This user login form will include: username, password, a "remember" me check box, and a submit
button.
<br><br><img src="./img/forms py creation.jpg">
</p>
<p>The four classes that represent the field types that we're using for this form are imported directly
from the WTForms package,
since the Flask-WTF extension doesn't provide customized versions. For each field, an object is
created as a class variable in
the LoginForm class. Each field's given a little description or label as a first argument.
</p>
<p>The optional validators argument that you see in some of the fields is used to attach validation
behaviors to fields. The
DataRequired validator simply checks that the field isn't submitted emtpy. There's many more
validators available, some of
which we're going to use in future forms.
</p>
<h3><u>Form Templates</u></h3>
<p>Now, that we have a functional back-end for the user login form, our next step is to add the form to
an HTML template so that
it can be rendered on a web page. For this, we're going to make a new HTML5 file inside the
"templates" folder called "login.html".
<br><br><img src="./img/login html.jpg">
</p>
<p>For this template, we're using the "base.html" template once again. This templates expects a form
object isntantiates from the
LoginForm class to be given as an argument, which you can see referenced as "form". This argument
will be sent by the login view
function, which isn't written yet in the "routes.py" file.
</p>
<p>The HTML "form" element in <> is used as a container for the web form content. The "action" attribute
of the form is used to tell
the browser the URL that should be used when submitting the information the user entered in the
fomr. When the action is set to an
empty string, the form is submitted to the URL that is currently in the address bar, which is
the URL that rendered this form on
the page. The "method" attribute specifies the HTTP request method that should be used when
submitting the form to the server. The
default is to send it with a GET request, but in almost all cases, using a POST request makes
for a better user experience because
requests of this type can submit the form data in the body of the request, while GET requests
add the form fields to the URL, cluttering
the browser address bar.
</p>
<p>The "form.hidden_tag()" template argument generates a hidden field that includes a token that's used
to protect the form against CSRF
attacks. All you need to do to have the form protected is include this hidden field and have the
SECRET_KEY variable defined in the
Flask configuration.
</p>
<p>The form object calls the fields as methods, and they're already programmed to know how to render
themselves as HTML code. {{ form.field_name.label }}
is used to include the field label, and {{ form.field_name() }} is used to get the field. For fields
that require additional HTML
attributes, those can be passed as arguments. The username and password fields in this template take
the "size" as an argument that'll
be added to the "input" HTML element as an attribute. This is how you can also attach CSS classes or
IDs to form fields.
</p>
<h3><u>Form Views</u></h3>
<p>Finally, we have to code a new view function in the application that renders the template made. This
is done in the "routes.py" file.
We're going to make another view function right below the previous one and import the LoginForm
class from the "forms" file inside of the "app"
folder.
<br><br><img src="./img/new login route.jpg">
</p>
<p>Here, we've instantiated an object of the LoginForm class form the "forms.py" file, and sent it to
the template. The "form=form" syntax
means that it's passing the "form" object created in the line above to the template named "form".
Now, we just need to add another
hyperlink on the top of every file to the new login page. All we need to do for this is to add it to
the base.html file, for all
other template to inherit form, making it so all templates have this new hyperlink.
<br><br><img src="./img/new login hyperlink.jpg">
</p>
<h3><u>Receiving Form Data</u></h3>
<p>As of right now, if you try hitting the submit button, the browser's going to display a "Method Not
Allowed" error. This is because
the inside the login view function, the submit button has no logic in place yet to process data
submitted by the user yet. So, we're
now going to be accepting and validating the data submitted by the user in this updated view
function. Also, there are new modules to
import at the top of the file.
<br><br><img src="./img/fixing login view function.jpg">
</p>
<p>The first new thing in this version, though, is the "methods" argument in the route decorator. This
tells Flask that this view
function accepts both GET and POST requests, overriding the default, which is to accept only GET
requests. The HTTP states that
GET requests are those that return information to the client (the web browser). All the requests in
the application so far are of
this type. POST requests are typically used when the browser submits form data to the server. Now
that we've enabled "POST" requests,
the "Method Not Allowed" error should be all fixed.
</p>
<p>The "flask()" function, imported from Flask, is a useful way to show a message to the user. This is
only a temporary solution for our
application, though, since we don't have a system setup to actually log users in yet.
</p>
<p>Although, the "flash" messages don't automatically appear in the browser, so we need to set up a way
for these
messages to display in the "base.html" file itself.
<br><br><img src="./img/showing flash messages.jpg">
</p>
<p>Here, similarly to how you'd do it in Python, we use a "with" construct to assign the result of
calling the "get_flashed_messages"
function to a messages variable. The conditional after this checks if "messages" has content, and if
it does, displays them in an
unordered list.
</p>
<h3><u>Improving Field Validation</u></h3>
<p>We're now going to tell the user which fields they're incorrectly supplied data into in the login
form, using style in the "login.html" page.
<br><br><img src="./img/adding red login.jpg">
</p>
<p>Now, if you try submitting the form with an empty username or password, this is what'll occur on your
browser tab:
<br><br><img src="./img/sign in errors implemented.jpg">
</p>
<h3><u>Generating Links</u></h3>
<p>Before leaving off this chapter, I'd like to talk about making links better in your application.
Let's say, for instance, you
decide to reorganize your link names, or just don't want to bother with having to remember them all
by name. Well, you can
simply use the "url_for()" method from Flask that will provide the link for certain pages. You'll
see this in the following two
files below:
</p>
<ol>
<li>base.html <br><br><img src="./img/new url_for method implementation.jpg"></li>
<li>routes.py <br><br><img src="./img/url_for implementation.jpg"></li>
</ol>
</section>
<section id="chapter4">
<br><br><br>
<h2><a name="chapter4">Chapter 4: Database</a></h2>
<h3><u>Flask Databases</u></h3>
<p>Flask doesn't actually support databases natively. This means you'll have the freedom to choose the
database that best fits.
There are two groups of databases, those that follow the relational model, and those that don't.
Ones that are relational
implement the popular relational query language SQL, and those that don't are called NoSQL.
Relational databases are a better
match for application that have structured data such as lists of users, blog posts, etc. while NoSQL
databases are better for
applications with less defined structure. We're going with a relational one.
</p>
<p>An extenstion we'll need to install is called "Flask-SQLAlchemy", which provides a Flask-friendly
wrapper to the popular SQLAlchemy
package, which is an Object Relational Mapper or ORM. ORMs allow applications to manage a database
using high-level entities such
as: class, objects, and methods, instead of tables and SQL. ORMs translate high-level operations
into database commands.
</p>
<p>To install this package into your virtual environment, execute this command in your terminal window:
<br><br><img src="./img/flask sqlalchemy.jpg">
<p>
<h3><u>Database Migrations</u></h3>
<p>Database migrations address the issue of trying to make updates to an existing database as the
application needs to change. This is
often difficult, seeing as relational databases are centered around structured data, so when the
structure itself changes, the data
that's already in the database needs to be migrate to the modified structure.
</p>
<p>So, the next extension we'll need to help with that is Flask-Migrate, created by Miguel Grinberg, the
guy who wrote the blog I'm
basing this write-up on. To install it, type in this command:
<br><br><img src="./img/flask migrate.jpg">
</p>
<h3><u>Configuring Flask-SQLAlchemy</u></h3>
<p>During development (as opposed to production), we're going to use a SQLite database. These are
convenient for developing small
applications. We now have two new configuration items to add to the config file:
<br><br><img src="./img/new config.jpg">
</p>
<p>This new extension takes the location of the application's database from the
"SQLALCHEMY_DATABASE_URI" config variable. Just like
we saw in <a href="#chapter3">Chapter 3</a>, it's a good practice to set configuration from
environment variables, and provide a
fallback value as the second parameter in case the environment doesn't define the variable. We're
actually configuring a database
named app.db located at the top level of the application, which is stored in the basedir variable.
This file will be automatically made
for us later, once we create the database.
</p>
<p>The "SQLALCHEMY_TRACK_MODIFICATIONS" configuration option is set to False to disable a feature of
Flask-SQLAlchemy that's not needed.
</p>
<p>The database is going to be represented in the application by the database instance. The database
migration engine will also have an
instance. These are objects that need to be created after the application, in the "__init__.py"
file.
<br><br><img src="./img/adding sqlalchemy to init.jpg">
</p>
<p>These are all the changes we've made to the init script:
<ul>
<li>Imported a couple more modules at the top of the file</li>
<li>We've added a db object that represents the database</li>
<li>We've added another object that represents the migration engine</li>
<li>Imported a new module called "models" at the bottom of the file, that will define the structure
of the database</li>
</ul>
</p>
<h3><u>Database Models</u></h3>
<p>The data that'll be stored in the database will be represented by a collection of classes, usually
called database models.
</p>
<p>Here I've made a model that represents users in a table. I did this using the <a
href="https://ondras.zarovi.cz/sql/demo/">
WWW SQL Designer Tool</a>. It includes the fields, field datatypes, and field sizes.
<br><br><img src="./img/users table.jpg">
</p>
<p>The id field is typical to all models, and is used as the primary key. Each user in the database will
be assigned a unique id value,
stored in this data field. Primary keys are, in most cases, automatically assigned by the database,
so we just need to provide the
field to house them in.
</p>
<p>The other three fields for: username, email, and password_hash are defined as strings (or "VARCHAR"
in
database lingo), and their
maximum lengths are specified so that the database can optimize its memory usage (which is important
to do in computer science).
</p>
<p>While the "username" and "email" fields are self-explanatory, the "password_hash" one has a little
more to it. It is used for security
reasons so that passwords get 'hashed' by the database they're stored in, as to not be vulnerable to
an attack. Although, this'll be done
in <a href="#chapter5">Chapter 5</a>.
</p>
<p>Now, let's go ahead and make a new file under the "app" folder and call it "models.py".
<br><br><img src="./img/models py creation.jpg">
</p>
<p>The "User" class created above inherits from "db.Model", which comes from Flask_SQLAlchemy and is its
base class. This class defines
several attributes. Fields are created as instances of the "db.Column" class, which takes the field
type as an argument, plus other
optional arguments, for example, allow us to indicate which fields are unique and indexes, which is
important so that database searches
are efficient.
</p>
<p>The "__repr__" method tells Python how to print objects of this class, which is going to be useful
for debugging.
</p>
<h3><u>Migration Repository Creation</u></h3>
<p>The model class created defines the initial database structure for this application, but as the
application continues to grow, there's
going to be a need to change that structure, whether that's adding, modifying, or removing items.
Alembic (the migration framework used by
Flask-Migrate), maintains a migration repository, which is a directory in which it stores its
migration scripts. Each time a change is
made to the database structure, a migration script is added to the repository with the details of
the change.
</p>
<p>The "flask db" sub-command to Flask's commands, added by Flask-Migrate manages everything related to
database migrations. So, lets' create
the migration repository for microblog by running this command:
<br><br><img src="./img/flask db init.jpg">
</p>
<p>After running this command, you will find a new migrations directory, with a few files and a versions
sub-directory inside.
</p>
<h3><u>First Database Migration</u></h3>
<p>This first migration will include the users table that maps the "User" database model. How Alembic
normally creates a database migration
is automatically. To generate a migration automatically, Alembic compares the database structure as
defined by the database models,
against the actual database structure currently used in the database. It then populates the
migration script with the changes necessary
to make the database schema match the application models. In this case, since there is no previous
database, the automatic migration will
add the entire "User" model to the migration script. The "flask db migrate" sub-command generates
these automatic migrations:
<br><br><img src="./img/flask db migrate -m users table.jpg">
</p>
<p>The -m "(...)" part is optional, but it adds a short descriptive text to whatever migration you're
currently implementing. The output of the
command gives you an idea of what Alembic included in the migration. The first two lines are
informational and can usually be ignored. It
then says that it found a user table and two indexes. Then it tells you where it wrote the migration
script. The 976200887022 code is an
automatically generated unique code I got for the migration.
</p>
<p>This migration script has two functions "upgrade()" and "downgrade()" to apply or remove migrations.
This command must be used after migrating
a new script to the database:
<br><br><img src="./img/flask db upgrade.jpg">
</p>
<h3><u>Database Relationships</u></h3>
<p>Relational databases are good at storing relations between data items, hence their name. Consider the
case of a user writing a blog post.
The user will have a record in the "users" table, and the post will have a record in the "posts"
table. The most efficient way to record
who wrote a given post is to link the two related records.
</p>
<p>Once a link between a user and a post is established, the database can answer queries about this
link, like which user wrote a
certain blog post. This would actually be a pretty easy one, but one where you try finding the
reverse would be complex. If you
have a user, you may want to know all the posts that this user has written. Flask-SQLAlchemy will
help with both types of queries.
</p>
<p>Now, I've expanded the database diagram to show the link between a user's id field and a post's
user_id field. Sounds confusing, but
that's because it is. Later on, in <a href="#chapter8">Chapter 8</a>, it'll get even harder when we
introduce followers. However, this
stuff about database relationships is very good to know, considering it's taught in university
computer science classes. For right now,
though, I hope you can follow, since I don't think it's too complex just yet.
<br><br><img src="./img/users and posts table.jpg">
</p>
<p>The "posts" table will have the required "id", the "body" of the post and a "timestamp". In addition,
though, is a "user_id" field, which
links the post to its author. The way to link a blog post to the user that authored it is to add a
reference to the user's "id", and
that's exactly what the "user_id" field's for. The "user_id" field is called a foreign key. The
database diagram above shows foreign
keys as a link between the field and the "id" field of the table it refers to. This kind of
relationship is known as a "one-to-many",
because 'one' user writes 'many' posts.
</p>
<p>Seeing as we now have another database table, we need to add a new corresponding class to the
"models.py" file for the posts.
<br><br><img src="./img/posts model added to models python.jpg">
</p>
<p>The new "Post" class will represent blog posts written by users. The "timestamp" field is going to be
indexed, which is useful if
you want to retrieve posts in chronological order. We're also added a "default" argument, and passed
the "datetime.utcnow" function.
When you pass a function as a default, SQLAlchemy will set the field to the value of calling that
function. This is done by not including
the (), to signal that we don't want the value from calling that function, rather the function
itself to be passed. In general, we want
to be working with UTC dates and times in a server application. This ensures that you're following
the standard regardless of where
the users are located. These timestamps will be converted to the user's local time when they're
displayed.
</p>
<p>The "user_id" field was initialized as a foreign key to "user.id", which means that it references an
"id" value from the users table.
In this reference the "user" part is the name of the database table for the model.
</p>
<p>The "User" class now also has a new "posts" field, that is initialized with "db.relationship". This
is not an actual
database field, but a high-level view of the relationship between users and posts, and for that
reason it isn't in the
database diagram. For a "one-to-many" relationship, a "db.relationship" is normally defined on the
"one" side, and is
used as a convenient way to get access to the "many".
</p>
<p>The "backref" argument defines the name of a field that will be added to the objects of the "many"
class that points back at the
"one" object. This will add a "post.author" expression that will return the user given a post. The
"lazy" argument defines
how the database query for the relationship will be issued, which will be discussed later.
</p>
<p>With a new update to the application models, a new update to the database migration needs to be made.
<br><br><img src="./img/flask db migrate -m posts table.jpg">
</p>
<p>Now, for the upgrade command to finish it off:
<br><br><img src="./img/flask db upgrade 2.jpg">
</p>
<h3><u>Testing Time</u></h3>
<p>After suffering all this time trying to get the database setup, it's time to see how it all works. We
could do this in the Python
interpreter, but the problem with that is: we would have to import our libraries before testing
inside the interpreter. A fix to
this is something the "flask shell". It is an exact copy of the Python interpreter, only it imports
things from the "microblog.py"
app for you by making a function for it in that file.
<br><br><img src="./img/flask shell setup.jpg">
</p>
<p>Now, with these configured symbols to pre-import before heading into the shell, we don't have to
worry about doing it ourselves.
Also, with the shell, comes the application instance pre-imported, which means that anything in the
"app.db" file for our database.
</p>
<p>The first step is getting into the shell by running the flask command.
<br><br><img src="./img/flask shell.jpg">
</p>
<p>Now that we're in, it should look exactly like the Python intrepreter. Let's start testing by
creating a new user.
<br><br><img src="./img/user in shell.jpg">
</p>
<p>Changes to a database are done in the context of a session, which can be accesses as "db.session".
Multiple changes can be
accumulated in a session and once all the changes have been registered you can issue a single
"db.session.commit()" command. If
at any time while working on a session there's an error, a call to "db.session.rollback()" will
abort the sesison and remove any
changes stored in said session.
</p>
<p>With all that said, let's add another user.
<br><br><img src="./img/second user in shell.jpg">
</p>
<p>Now that we have 2 users in our database, we can ask a query that returns all the users.
<br><br><img src="./img/user query all.jpg">
</p>
<p>All database models have a "query" attribute that's the entry point to run database queries. The most
basic query is
that one that returns all elements of that class. Also, since we indexed the "username" field, the
"id" fields were automatically
set to 1 and 2 when they were added.
</p>
<p>However, instead of doing a query to retrieve all users in a database, you can also locate one using
the "id" of that user.
<br><br><img src="./img/get user by index shell.jpg">
</p>
<p>Now, let's add a mock blog post.
<br><br><img src="./img/new mock post shell.jpg">
</p>
<p>Since, we set a default value for the "timestamp" class attribute, we don't need to set it. As for
the "user_id" field, we have a
"db.relationship" setup between the "User" class and the post, because the class adds a "posts"
attribute to users, and also an
author attribute to posts. We went about assigning an turho to this post using the "author" virtual
field instead of having to deal
with user IDs, which we don't have setup yet.
</p>
<p>Now, to end this chapter off, we need to clean the shell so that there's no existing users and posts
in our database for chapters to come.
<br><br><img src="./img/resetting shell, deleting posts and users.jpg">
</p>
</section>
<section id="chapter5">
<br><br><br>
<h2><a name="chapter5">Chapter 5: User Logins</a></h2>
<h3><u>Password Hashing</u></h3>
<p>Time to address the "password_hash" field of the user model, that's gone unused thus far. The purpose
of this field is to contain
a hash of the user password, which will be used to verify the password entered by the user during
login. Although, password hashing
is pretty complicated and advanced, there's several libraries to help us with that.
</p>
<p>Werkzeug, is a package we're going to implement to set and check a password. This'll be done in the
"models.py" file under two functions
inside of the User class.
<br><br><img src="./img/adding werkzeug.jpg">
</p>
<p>These two methods are pretty self explanatory by reading the function names inside of them. Just
don't forget to import the two packages from
werkzeyg.security.
</p>
<h3><u>Introduction To Flask-Login</u></h3>
<p>This popular Flask extension manages the user's logged-in state. This also provides the "remember me"
functionality that allows users
to remain logged in even after closing the browser window. The first step to all of this though, is
to - you guessed it, execute the install
command in the terminal for the extension.
<br><br><img src="./img/pip install flask-login.jpg">
</p>
<p>As with other extensions, Flask-Login needs to be created and initialized right after the application
instance in "__init__.py". This is how:
<br><br><img src="./img/adding flask login to init.jpg">
</p>
<h3><u>User Model Setup</u></h3>
<p>The Flask-Login extension works in tandam with the application's user model, and expects certain
properties and methods to be
implemented in it. The four required items are listed below:
<ul>
<li>"is_authenticated": a property that's True if the user has valid credentials or False otherwise
</li>
<li>"is_activate": a property that's True if the user's account is activate or False otherwise</li>
<li>"is_anonymous": a property that's False for regular users, and True for a sepcial, anonymous
user</li>
<li>"get_id": a method that returns a unique identifier for the user as a string</li>
</ul>
</p>
<p>These four items are simple to implement, but since since they're fairly generic, Flask-Login
provides a class called "UserMixin". It's first
imported from flask-login, then added as a parameter to the "User" class.