the flask mega-tutorial
TRANSCRIPT
-
8/20/2019 The Flask Mega-Tutorial
1/159
The FlaskMega-
Tutorial
-
8/20/2019 The Flask Mega-Tutorial
2/159
index of all the articles in the series that have been published to date:
• Part I: Hello, World!
• Part II: Templates
• Part III: Web Forms
• Part IV: Database
• Part V: ser o"ins
• Part VI: Profile Pa"e #nd #vatars
• Part VII: nit Testin"
• Part VIII: Follo$ers, %ontacts #nd Friends
• Part I&: Pa"ination
• Part &: Full Text 'earch
• Part &I: (mail 'upport
• Part &II: Facelift
•
Part &III: Dates and Times • Part &IV: I)*n and )+n
• Part &V: #ax
• Part &VI: Debu""in", Testin" and Profilin"
• Part &VII: Deplo-ment on inux .even on the /aspberr- Pi!0
• Part &VIII: Deplo-ment on the Hero1u %loud
http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-worldhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ii-templateshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iii-web-formshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-databasehttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-loginshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-loginshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vi-profile-page-and-avatarshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-unit-testinghttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-viii-followers-contacts-and-friendshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-viii-followers-contacts-and-friendshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ix-paginationhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-x-full-text-searchhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-x-full-text-searchhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xi-email-supporthttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xii-facelifthttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xiii-dates-and-timeshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xiv-i18n-and-l10nhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xv-ajaxhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvi-debugging-testing-and-profilinghttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvi-debugging-testing-and-profilinghttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvii-deployment-on-linux-even-on-the-raspberry-pihttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xviii-deployment-on-the-heroku-cloudhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ii-templateshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iii-web-formshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-iv-databasehttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-v-user-loginshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vi-profile-page-and-avatarshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-unit-testinghttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-viii-followers-contacts-and-friendshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-ix-paginationhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-x-full-text-searchhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xi-email-supporthttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xii-facelifthttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xiii-dates-and-timeshttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xiv-i18n-and-l10nhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xv-ajaxhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvi-debugging-testing-and-profilinghttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvii-deployment-on-linux-even-on-the-raspberry-pihttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xviii-deployment-on-the-heroku-cloudhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
-
8/20/2019 The Flask Mega-Tutorial
3/159
Part I: Hello, World!
My background
I2m a soft$are en"ineer $ith double di"it -ears of experience developin" complex applications in
several lan"ua"es3 I first learned P-thon as part of an effort to create bindin"s for a %44 librar- at
$or13
In addition to P-thon, I2ve $ritten $eb apps in PHP, /ub-, 'malltal1 and believe it or not, also in %443
5f all these, the P-thon6Flas1 combination is the one that I2ve found to be the most flexible3
UPDT: I have $ritten a boo1 titled 7Flas1 Web Development7, published in 8+)9 b- 52/eill-
edia3 The boo1 and the tutorial complement each other, the boo1 presents a more updated usa"e of
Flas1 and is, in "eneral, more advanced than the tutorial, but some topics are onl- covered in the
tutorial3 Visit http:66flas1boo13com for more information3
The a""lication
The application I2m "oin" to develop as part of this tutorial is a decentl- featured microblo""in" server
that I decided to call microblog3 Prett- creative, I 1no$3
These are some of the topics I $ill cover as $e ma1e pro"ress $ith this proect:
• ser mana"ement, includin" mana"in" lo"ins, sessions, user roles, profiles and user avatars3
• Database mana"ement, includin" mi"ration handlin"3
•
Web form support, includin" field validation3• Pa"ination of lon" lists of items3
• Full text search3
• (mail notifications to users3
• HT templates3
• 'upport for multiple lan"ua"es3
• %achin" and other performance optimi;ations3
• Debu""in" techni
-
8/20/2019 The Flask Mega-Tutorial
4/159
#e$uire%ents
If -ou have a computer that runs P-thon then -ou are probabl- "ood to "o3 The tutorial application
should run ust fine on Windo$s, 5' & and inux3 nless noted, the code presented in these articles
has been tested a"ainst P-thon 83= and >393
The tutorial assumes that -ou are familiar $ith the terminal $indo$ .command prompt for Windo$susers0 and 1no$ the basic command line file mana"ement functions of -our operatin" s-stem3 If -ou
don2t, then I recommend that -ou learn ho$ to create directories, cop- files, etc3 usin" the command
line before continuin"3
Finall-, -ou should be some$hat comfortable $ritin" P-thon code3 Familiarit- $ith P-thon modules
and pac1a"es is also recommended3
Installing Flask
51a-, let2s "et started!
If -ou haven2t -et, "o ahead and install P-thon3
?o$ $e have to install Flas1 and several extensions that $e $ill be usin"3 - preferred $a- to do this
is to create a virtual environment $here ever-thin" "ets installed, so that -our main P-thon installation
is not affected3 #s an added benefit, -ou $on2t need root access to do the installation in this $a-3
'o, open up a terminal $indo$, choose a location $here -ou $ant -our application to live and create a
ne$ folder there to contain it3 et2s call the application folder microblog3
If -ou are usin" P-thon >39, then cd into the microblog folder and then create a virtual environment
$ith the follo$in" command:
$ python -m venv flask
?ote that in some operatin" s-stems -ou ma- need to use python3 instead of python3 The above
command creates a private version of -our P-thon interpreter inside a folder named flask3
If -ou are usin" an- other version of P-thon older than >39, then -ou need to do$nload and install
virtualenv3p- before -ou can create a virtual environment3 If -ou are on ac 5' &, then -ou can install
it $ith the follo$in" command:
$ sudo easy_install virtualenv
5n inux -ou li1el- have a pac1a"e for -our distribution3 For example, if -ou use buntu:
$ sudo apt-get install python-virtualenv
http://docs.python.org/tutorial/modules.htmlhttp://docs.python.org/tutorial/modules.htmlhttp://python.org/download/http://python.org/download/http://pypi.python.org/pypi/virtualenvhttp://virtualenv.readthedocs.org/en/latest/virtualenv.html#installationhttp://docs.python.org/tutorial/modules.htmlhttp://docs.python.org/tutorial/modules.htmlhttp://python.org/download/http://pypi.python.org/pypi/virtualenvhttp://virtualenv.readthedocs.org/en/latest/virtualenv.html#installation
-
8/20/2019 The Flask Mega-Tutorial
5/159
Windo$s users have the most difficult- in installin" virtualenv, so if -ou $ant to avoid the trouble then
install P-thon >393 If -ou $ant to install virtualenv on Windo$s then the easiest $a- is b-
installin" pip first, as explaned in this pa"e3 5nce pip is installed, the following
command installsvirtualenv@:
$ pip install virtualenv
We2ve seen above ho$ to create a virtual environment in P-thon >393 For older versions of P-thon that
have been expanded $ith virtualenv, the command that creates a virtual environment is the
follo$in":
$ virtualenv flask
/e"ardless of the method -ou use to create the virtual environment, -ou $ill end up $ith a folder
named flask that contains a complete P-thon environment read- to be used for this proect3
Virtual environments can be activated and deactivated, if desired3 #n activated environment adds the
location of its bin folder to the s-stem path, so that for example, $hen -ou t-pe python -ou "et the
environment2s version and not the s-stem2s one3 Aut activatin" a virtual environment is not necessar-, it
is e
-
8/20/2019 The Flask Mega-Tutorial
6/159
These commands $ill do$nload and install all the pac1a"es that $e $ill use for our application3
&Hello, World& in Flask
Bou no$ have a flask subCfolder inside -our microblog folder that is populated $ith a P-thon
interpreter and the Flas1 frame$or1 and extensions that $e $ill use for this application3 ?o$ it2s time
to $rite our first $eb application!
#fter -ou cd to the microblog folder, let2s create the basic folder structure for our application:
$ mkdir app$ mkdir app/static$ mkdir app/templates$ mkdir tmp
The app folder $ill be $here $e $ill put our application pac1a"e3 The static subCfolder is $here
$e $ill store static files li1e ima"es, avascripts, and cascadin" st-le sheets3 The templates subC
folder is obviousl- $here our templates $ill "o3
et2s start b- creatin" a simple init script for our app pac1a"e .file app/__init__!py0:
from flask import "lask
app # "lask__name__%from app import views
The script above simpl- creates the application obect .of class "lask0 and then imports the vie$smodule, $hich $e haven2t $ritten -et3 Do not confuse app the variable .$hich "ets assi"ned the
"lask instance0 $ith app the pac1a"e .from $hich $e import the views module03
If -ou are $onderin" $h- the import statement is at the end and not at the be"innin" of the script as
it is al$a-s done, the reason is to avoid circular references, because -ou are "oin" to see that the
views module needs to import the app variable defined in this script3 Puttin" the import at the end
avoids the circular import error3
The vie$s are the handlers that respond to re
-
8/20/2019 The Flask Mega-Tutorial
7/159
This vie$ is actuall- prett- simple, it ust returns a strin", to be displa-ed on the client2s $eb bro$ser3
The t$o route decorators above the function create the mappin"s from /s / and /inde( to this
function3
The final step to have a full- $or1in" $eb application is to create a script that starts up the
development $eb server $ith our application3 et2s call this script run!py, and put it in the root
folder:
.flask/bin/pythonfrom app import appapp!rundebug#rue%
The script simpl- imports the app variable from our app pac1a"e and invo1es its run method to start
the server3 /emember that the app variable holds the "lask instance that $e created it above3
To start the app -ou ust run this script3 5n 5' &, inux and %-"$in -ou have to indicate that this is an
executable file before -ou can run it:
$ chmod a0( run!py
Then the script can simpl- be executed as follo$s:
!/run!py
5n Windo$s the process is a bit different3 There is no need to indicate the file is executable3 Instead
-ou have to run the script as an ar"ument to the P-thon interpreter from the virtual environment:
$ flask\cripts\python run!py
#fter the server initiali;es it $ill listen on port +++ $aitin" for connections3 ?o$ open up -our $eb
bro$ser and enter the follo$in" / in the address field:
http)//localhost)1222
#lternativel- -ou can use the follo$in" /:
http)//localhost)1222/inde(
Do -ou see the route mappin"s in actionE The first / maps to /, $hile the second maps to /inde(3
Aoth routes are associated $ith our vie$ function, so the- produce the same result3 If -ou enter an-
other / -ou $ill "et an error, since onl- these t$o have been defined3
When -ou are done pla-in" $ith the server -ou can ust hit %trlC% to stop it3
#nd $ith this I conclude this first installment of this tutorial3
For those of -ou that are la;- t-pists, -ou can do$nload the code from this tutorial belo$:
Do$nload microblo"C+3)3;ip3
?ote that -ou still need to install Flas1 as indicated above before -ou can run the application3
https://github.com/miguelgrinberg/microblog/archive/version-0.1.ziphttps://github.com/miguelgrinberg/microblog/archive/version-0.1.zip
-
8/20/2019 The Flask Mega-Tutorial
8/159
What's ne(t
In the next part of the series $e $ill modif- our little application to use HT templates3
I hope to see -ou in the next chapter3
i"uel
-
8/20/2019 The Flask Mega-Tutorial
9/159
Part II: Te%"lates
#eca"
If -ou follo$ed the previous chapter -ou should have a full- $or1in", -et ver- simple $eb application
that has the follo$in" file structure:
microblog\ flask\ virtual environment files4 app\ static\ templates\ __init__!py views!py tmp\ run!py
To run the application -ou execute the run!py script and then open the
http)//localhost)1222 / on -our $eb bro$ser3
We are pic1in" up exactl- from $here $e left off, so -ou ma- $ant to ma1e sure -ou have the above
application correctl- installed and $or1in"3
Why )e need te%"lates
et2s consider ho$ $e can expand our little application3
We $ant the home pa"e of our microblo""in" app to have a headin" that $elcomes the lo""ed in user,
that2s prett- standard for applications of this 1ind3 I"nore for no$ the fact that $e have no $a- to lo" a
user in, I2ll present a $or1around for this issue in a moment3
#n eas- option to output a nice and bi" headin" $ould be to chan"e our vie$ function to output
HT, ma-be somethin" li1e this:
from app import app
&app!route'/'%&app!route'/inde('%
def inde(%) user # 5'nickname') '6iguel'7 . fake user return '''html4 head4 title4+ome 8age/title4 /head4 body4 h94+ello, ''' 0 user:'nickname'; 0 '''/h94 /body4/html4
http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-worldhttp://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
-
8/20/2019 The Flask Mega-Tutorial
10/159
'''
ive the application a tr- to see ho$ this loo1s in -our bro$ser3
'ince $e don2t have support for users -et I have resorted to usin" a placeholder user obect, sometimes
called fa1e or moc1 obect3 This allo$s us to concentrate on certain aspects of our application that
depend on parts of the s-stem that haven2t been built -et3
I hope -ou a"ree $ith me that the solution used above to deliver HT to the bro$ser is ver- u"l-3
%onsider ho$ complex the code $ill become if -ou have to return a lar"e and complex HT pa"e
$ith lots of d-namic content3 #nd $hat if -ou need to chan"e the la-out of -our $eb site in a lar"e app
that has do;ens of vie$s, each returnin" HT directl-E This is clearl- not a scalable option3
Te%"lates to the rescue
If -ou could 1eep the lo"ic of -our application separate from the la-out or presentation of -our $eb
pa"es thin"s $ould be much better or"ani;ed, don2t -ou thin1E Bou could even hire a $eb desi"ner to
create a 1iller $eb site $hile -ou code the site2s behaviors in P-thon3 Templates help implement this
separation3
et2s $rite our first template .file app/templates/inde(!html0:
html4 head4 title455 title 77 - microblog/title4 /head4 body4 h94+ello, 55 user!nickname 77/h94 /body4
/html4
#s -ou see above, $e ust $rote a mostl- standard HT pa"e, $ith the onl- difference that there are
some placeholders for the d-namic content enclosed in 55 !!! 77 sections3
?o$ let2s see ho$ $e use this template from our vie$ function .file app/views!py0:
from flask import render_templatefrom app import app
&app!route'/'%&app!route'/inde('%def inde(%)
user # 5'nickname') '6iguel'7 . fake user return render_template'inde(!html', title#'+ome', user#user%
Tr- the application at this point to see ho$ the template $or1s3 5nce -ou have the rendered pa"e in
-our bro$ser -ou ma- $ant to vie$ the source HT and compare it a"ainst the ori"inal template3
To render the template $e had to import a ne$ function from the Flas1 frame$or1 called
-
8/20/2019 The Flask Mega-Tutorial
11/159
render_template3 This function ta1es a template filename and a variable list of template
ar"uments and returns the rendered template, $ith all the ar"uments replaced3
nder the covers, the render_template function invo1es the Gina8 templatin" en"ine that is part
of the Flas1 frame$or13 Gina8 substitutes 55!!!77 bloc1s $ith the correspondin" values provided as
template ar"uments3
*ontrol state%ents in te%"lates
The Gina8 templates also support control statements, "iven inside 5
statement to our template .file app/templates/inde(!html0:
html4 head4 5< if title
-
8/20/2019 The Flask Mega-Tutorial
12/159
posts#posts%
To represent user posts $e are usin" a list, $here each element has author and body fields3 When
$e "et to implement a real database $e $ill preserve these field names, so $e can desi"n and test our
template usin" the fa1e obects $ithout havin" to $orr- about updatin" it $hen $e move to a database3
5n the template side $e have to solve a ne$ problem3 The list can have an- number of elements, it $illbe up to the vie$ function to decide ho$ man- posts need to be presented3 The template cannot ma1e
an- assumptions about the number of posts, so it needs to be prepared to render as man- posts as the
vie$ sends3
'o let2s see ho$ $e do this usin" a for control structure .file app/templates/inde(!html0:
html4 head4 5< if title
-
8/20/2019 The Flask Mega-Tutorial
13/159
head4 5< if title
-
8/20/2019 The Flask Mega-Tutorial
14/159
Part III: Web For%s
#eca"
In the previous chapter of the series $e defined a simple template for the home pa"e and used fa1e
obects as placeholders for thin"s $e don2t have -et, li1e users or blo" posts3
In this article $e are "oin" to fill one of those man- holes $e still have in our app, $e $ill be loo1in"
at ho$ to $or1 $ith $eb forms3
Web forms are one of the most basic buildin" bloc1s in an- $eb application3 We $ill be usin" forms to
allo$ users to $rite blo" posts, and also for lo""in" in to the application3
To follo$ this chapter alon" -ou need to have the microblog app as $e left it at the end of the
previous chapter3 Please ma1e sure the app is installed and runnin"3
*oniguration
To handle our $eb forms $e are "oin" to use the Flas1CWTF extension, $hich in turn $raps the
WTForms proect in a $a- that inte"rates nicel- $ith Flas1 apps3
an- Flas1 extensions reDBE # rueB@AB_FBG # 'you-will-never-guess'
Prett- simple, it2s ust t$o settin"s that our Flas1CWTF extension needs3 The "_@A"_BC?>DBE
settin" activates the crossCsite re
-
8/20/2019 The Flask Mega-Tutorial
15/159
The user login or%
Web forms are represented in Flas1CWTF as classes, subclassed from base class "orm3 # form subclass
simpl- defines the fields of the form as class variables3
?o$ $e $ill create a lo"in form that users $ill use to identif- $ith the s-stem3 The lo"in mechanism
that $e $ill support in our app is not the standard username6pass$ord t-pe, $e $ill have our userslo"in usin" their 5penID3 5penIDs have the benefit that the authentication is done b- the provider of
the 5penID, so $e don2t have to validate pass$ords, $hich ma1es our site more secure to our users3
The 5penID lo"in onl- reoolean"ield
from wtforms!validators import EataAequired
class Dogin"orm"orm%) openid # tring"ield'openid', validators#:EataAequired%;% remember_me # >oolean"ield'remember_me', default#"alse%
I believe the class is prett- much selfCexplanator-3 We imported the "orm class, and the t$o form field
classes that $e need, tring"ield and >oolean"ield3
The EataAequired import is a validator, a function that can be attached to a field to perform
validation on the data submitted b- the user3 The EataAequired validator simpl- chec1s that the
field is not submitted empt-3 There are man- more validators included $ith Flas1CWTF, $e $ill usesome more in the future3
For% te%"lates
We $ill also need a template that contains the HT that produces the form3 The "ood ne$s is that the
Dogin"orm class that $e ust created 1no$s ho$ to render form fields as HT, so $e ust need to
concentrate on the la-out3 Here is our lo"in template .file app/templates/login!html0:
-- e(tend from base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
16/159
/form45< endblock
-
8/20/2019 The Flask Mega-Tutorial
17/159
The onl- other thin" that is ne$ here is the methods ar"ument in the route decorator3 This tells Flas1
that this vie$ function accepts (T and P5'T re
-
8/20/2019 The Flask Mega-Tutorial
18/159
re"ardin" an action3
The flashed messa"es $ill not appear automaticall- in our pa"e, our templates need to displa- the
messa"es in a $a- that $or1s for the site la-out3 We $ill add these messa"es to the base template, so
that all our templates inherit this functionalit-3 This is the updated base template .file
app/templates/base!html0:
html4 head4 5< if title
-
8/20/2019 The Flask Mega-Tutorial
19/159
When a field fails validation Flas1CWTF adds a descriptive error messa"e to the form obect3 These
messa"es are available to the template, so $e ust need to add a bit of lo"ic that renders them3
Here is our lo"in template $ith field validation messa"es .file app/templates/login!html0:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
20/159
?o$ let2s see ho$ $e use this arra- in our lo"in vie$ function:
&app!route'/login', methods#:'MB', '8J';%def login%) form # Dogin"orm% if form!validate_on_submit%) flash'Dogin requested for JpenIE#*
-
8/20/2019 The Flask Mega-Tutorial
21/159
The template "ot some$hat lon" $ith this chan"e3 'ome 5penIDs include the user2s username, so for
those $e have to have a bit of avascript ma"ic that prompts the user for the username and then
composes the 5penID $ith it3 When the user clic1s on an 5penID provider lin1 and .optionall-0 enters
the username, the 5penID for that provider is inserted in the text field3
Aelo$ is a screenshot of our lo"in screen after clic1in" the oo"le 5penID lin1:
Final Words
While $e have made a lot of pro"ress $ith our lo"in form, $e haven2t actuall- done an-thin" to lo"in
users into our s-stem, all $e2ve done so far had to do $ith the I aspects of the lo"in process3 This is
because before $e can do real lo"ins $e need to have a database $here $e can record our users3
In the next chapter $e $ill "et our database up and runnin", and shortl- after $e $ill complete our
lo"in s-stem, so sta- tuned for the follo$ up articles3
The microblog application in its current state is available for do$nload here:
Do$nload microblo"C+3>3;ip3
/emember that the Flas1 virtual environment is not included in the ;ip file3 For instructions on ho$ to
set it up see the first chapter of the series3
Feel free to leave comments or
-
8/20/2019 The Flask Mega-Tutorial
22/159
Part I/: Database
#eca"
In the previous chapter of the series $e created our lo"in form, complete $ith submission and
validation3
In this article $e are "oin" to create our database and set it up so that $e can record our users in it3
To follo$ this chapter alon" -ou need to have the microblog app as $e left it at the end of the
previous chapter3 Please ma1e sure the app is installed and runnin"3
#unning Python scri"ts ro% the co%%and line
In this chapter $e are "oin" to $rite a fe$ scripts that simplif- the mana"ement of our database3 Aefore$e "et into that let2s revie$ ho$ a P-thon script is executed on the command line3
If -ou are on inux or 5' &, then scripts have to be "iven executable permission, li1e this:
$ chmod a0( script!py
The script has a sheban" line, $hich points to the interpreter that should be used3 # script that has been
"iven executable permission and has a sheban" line can be executed simpl- li1e this:
!/script!py arguments4
5n Windo$s, ho$ever, this does not $or1, and instead -ou have to provide the script as an ar"ument tothe chosen P-thon interpreter:
$ flask\cripts\python script!py arguments4
To avoid havin" to t-pe the path to the P-thon interpreter -ou can add -our
microblog/flask/cripts director- to the s-stem path, ma1in" sure it appears before -our
re"ular P-thon interpreter3 This can be temporaril- achieved b- activatin" the virtual environment $ith
the follo$in" command:
$ flask\cripts\activate
From no$ on, in this tutorial the inux65' & s-ntax $ill be used for brevit-3 If -ou are on Windo$s
remember to convert the s-ntax appropriatel-3
Databases in Flask
We $ill use the Flas1C'#lchem- extension to mana"e our application3 This extension provides a
$rapper for the '#lchem- proect, $hich is an 5bect /elational apper or 5/3
http://en.wikipedia.org/wiki/Shebang_(Unix)http://packages.python.org/Flask-SQLAlchemyhttp://www.sqlalchemy.org/http://en.wikipedia.org/wiki/Object-relational_mappinghttp://en.wikipedia.org/wiki/Shebang_(Unix)http://packages.python.org/Flask-SQLAlchemyhttp://www.sqlalchemy.org/http://en.wikipedia.org/wiki/Object-relational_mapping
-
8/20/2019 The Flask Mega-Tutorial
23/159
5/s allo$ database applications to $or1 $ith obects instead of tables and '3 The operations
performed on the obects are translated into database commands transparentl- b- the 5/3 no$in"
' can be ver- helpful $hen $or1in" $ith 5/s, but $e $ill not be learnin" ' in this tutorial,
$e $ill let Flas1C'#lchem- spea1 ' for us3
Migrationsost database tutorials I2ve seen cover creation and use of a database, but do not ade
-
8/20/2019 The Flask Mega-Tutorial
24/159
from app import views, models
?ote the t$o chan"es $e have made to our init script3 We are no$ creatin" a db obect that $ill be our
database, and $e are also importin" a ne$ module called models3 We $ill $rite this module next3
The database %odelThe data that $e $ill store in our database $ill be represented b- a collection of classes that are
referred to as the database models3 The 5/ la-er $ill do the translations re
-
8/20/2019 The Flask Mega-Tutorial
25/159
a$1$ard to use, so instead I have $ritten m- o$n set of little P-thon scripts that invo1e the mi"ration
#PIs3
Here is a script that creates the database .file db_create!py0:
.flask/bin/pythonfrom migrate!versioning import api
from config import QD?D@+B6G_E??>?B_RAIfrom config import QD?D@+B6G_6IMA?B_AB8Jfrom app import dbimport os!pathdb!create_all%if not os!path!e(istsQD?D@+B6G_6IMA?B_AB8J%) api!createQD?D@+B6G_6IMA?B_AB8J, 'database repository'% api!version_controlQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%else) api!version_controlQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J,api!versionQD?D@+B6G_6IMA?B_AB8J%%
?ote ho$ this script is completel- "eneric3 #ll the application specific pathnames are imported from
the confi" file3 When -ou start -our o$n proect -ou can ust cop- the script to the ne$ app2s director-and it $ill $or1 ri"ht a$a-3
To create the database -ou ust need to execute this script .remember that if -ou are on Windo$s the
command is sli"htl- different0:
!/db_create!py
#fter -ou run the command -ou $ill have a ne$ app!db file3 This is an empt- s?B_RAIfrom config import QD?D@+B6G_6IMA?B_AB8Jv # api!db_versionQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%migration # QD?D@+B6G_6IMA?B_AB8J 0 '/versions/
-
8/20/2019 The Flask Mega-Tutorial
26/159
e(ecold_model, tmp_module!__dict__%script # api!make_update_script_for_modelQD?D@+B6G_E??>?B_RAI,QD?D@+B6G_6IMA?B_AB8J, tmp_module!meta, db!metadata%openmigration, *wt*%!writescript%api!upgradeQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%v # api!db_versionQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%print'Cew migration saved as ' 0 migration%print'@urrent database version) ' 0 strv%%
The script loo1s complicated, but it doesn2t reall- do much3 The $a- '#lchem-Cmi"rate creates a
mi"ration is b- comparin" the structure of the database .obtained in our case from file app!db0
a"ainst the structure of our models .obtained from file app/models!py03 The differences bet$een
the t$o are recorded as a mi"ration script inside the mi"ration repositor-3 The mi"ration script 1no$s
ho$ to appl- a mi"ration or undo it, so it is al$a-s possible to up"rade or do$n"rade a database
format3
While I have never had problems "eneratin" mi"rations automaticall- $ith the above script, I could see
that sometimes it $ould be hard to determine $hat chan"es $ere made ust b- comparin" the old and
the ne$ format3 To ma1e it eas- for '#lchem-Cmi"rate to determine the chan"es I never rename
existin" fields, I limit m- chan"es to addin" or removin" models or fields, or chan"in" t-pes of
existin" fields3 #nd I al$a-s revie$ the "enerated mi"ration script to ma1e sure it is ri"ht3
It "oes $ithout sa-in" that -ou should never attempt to mi"rate -our database $ithout havin" a bac1up,
in case somethin" "oes $ron"3 #lso never run a mi"ration for the first time on a production database,
al$a-s ma1e sure the mi"ration $or1s correctl- on a development database3
'o let2s "o ahead and record our mi"ration:
$ !/db_migrate!py
#nd the output from the script $ill be:
Cew migration saved as db_repository/versions/229_migration!py@urrent database version) 9
The script sho$s $here the mi"ration script $as stored, and also prints the current database version3
The empt- database version $as version +, after $e mi"rated to include users $e are at version )3
Database u"grades and do)ngrades
A- no$ -ou ma- be $onderin" $h- is it that important to "o throu"h the extra hassle of recordin"database mi"rations3
Ima"ine that -ou have -our application in -our development machine, and also have a cop- deplo-ed
to a production server that is online and in use3
et2s sa- that for the next release of -our app -ou have to introduce a chan"e to -our models, for
example a ne$ table needs to be added3 Without mi"rations -ou $ould need to fi"ure out ho$ to
chan"e the format of -our database, both in -our development machine and then a"ain in -our server,
-
8/20/2019 The Flask Mega-Tutorial
27/159
and this could be a lot of $or13
If -ou have database mi"ration support, then $hen -ou are read- to release the ne$ version of the app
to -our production server -ou ust need to record a ne$ mi"ration, cop- the mi"ration scripts to -our
production server and run a simple script that applies the chan"es for -ou3 The database up"rade can be
done $ith this little P-thon script .file db_upgrade!py0:
.flask/bin/pythonfrom migrate!versioning import apifrom config import QD?D@+B6G_E??>?B_RAIfrom config import QD?D@+B6G_6IMA?B_AB8Japi!upgradeQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%v # api!db_versionQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%print'@urrent database version) ' 0 strv%%
When -ou run the above script, the database $ill be up"raded to the latest revision, b- appl-in" the
mi"ration scripts stored in the database repositor-3
It is not a common need to have to do$n"rade a database to an old format, but ust in case,
'#lchem-Cmi"rate supports this as $ell .file db_downgrade!py0:
.flask/bin/pythonfrom migrate!versioning import apifrom config import QD?D@+B6G_E??>?B_RAIfrom config import QD?D@+B6G_6IMA?B_AB8Jv # api!db_versionQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%api!downgradeQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J, v - 9%v # api!db_versionQD?D@+B6G_E??>?B_RAI, QD?D@+B6G_6IMA?B_AB8J%print'@urrent database version) ' 0 strv%%
This script $ill do$n"rade the database one revision3 Bou can run it multiple times to do$n"rade
several revisions3
Database relationshi"s
/elational databases are "ood at storin" relations bet$een data items3 %onsider the case of a user
$ritin" a blo" post3 The user $ill have a record in the users table, and the post $ill have a record in
the posts table3 The most efficient $a- to record $ho $rote a "iven post is to lin1 the t$o related
records3
5nce a lin1 bet$een a user and a post is established there are t$o t-pes of
-
8/20/2019 The Flask Mega-Tutorial
28/159
5ur posts table $ill have the re
-
8/20/2019 The Flask Mega-Tutorial
29/159
@urrent database version) U
It isn2t reall- necessar- to record each little chan"e to the database model as a separate mi"ration, a
mi"ration is normall- onl- recorded at si"nificant points in the histor- of the proect3 We are doin"
more mi"rations than necessar- here onl- to sho$ ho$ the mi"ration s-stem $or1s3
Play ti%e
We have spent a lot of time definin" our database, but $e haven2t seen ho$ it $or1s -et3 'ince our app
does not have database code -et let2s ma1e use of our brand ne$ database in the P-thon interpreter3
'o "o ahead and fire up P-thon3 5n inux or 5' &:
flask/bin/python
5r on Windo$s:
flask\cripts\python
5nce in the P-thon prompt enter the follo$in":
444 from app import db, models444
This brin"s our database and models into memor-3
et2s create a ne$ user:
444 u # models!Rsernickname#'Hohn', email#'Hohn&email!com'%444 db!session!addu%444 db!session!commit%
444
%han"es to a database are done in the context of a session3 ultiple chan"es can be accumulated in a
session and once all the chan"es have been re"istered -ou can issue a sin"le
db!session!commit%, $hich $rites the chan"es atomicall-3 If at an- time $hile $or1in" on a
session there is an error, a call to db!session!rollback% $ill revert the database to its state
before the session $as started3 If neither commit nor rollback are issued then the s-stem b- default
$ill roll bac1 the session3 'essions "uarantee that the database $ill never be left in an inconsistent
state3
et2s add another user:
444 u # models!Rsernickname#'susan', email#'susan&email!com'%444 db!session!addu%444 db!session!commit%444
?o$ $e can
-
8/20/2019 The Flask Mega-Tutorial
30/159
444 users:Rser u'Hohn'4, Rser u'susan'4;444 for u in users)!!! printu!id,u!nickname%!!!9 HohnU susan444
For this $e have used the query member, $hich is available in all model classes3 ?ote ho$ the id
member $as automaticall- set for us3
Here is another $a- to do
-
8/20/2019 The Flask Mega-Tutorial
31/159
. a user that has no posts444 u # models!Rser!query!getU%444 uRser u'susan'4444 u!posts!all%:;
. get all users in reverse alphabetical order444 models!Rser!query!order_by'nickname desc'%!all%:Rser u'susan'4, Rser u'Hohn'4;444
The Flas1C'#lchem- documentation is the best place to learn about the man- options that are
available to
-
8/20/2019 The Flask Mega-Tutorial
32/159
Part /: User +ogins
#eca"
In the previous chapter of the series $e created our database and learned ho$ to populate it $ith users
and posts, but $e haven2t hoo1ed up an- of that into our app -et3 #nd t$o chapters a"o $e2ve seen ho$
to create $eb forms and left $ith a full- implemented lo"in form3
In this article $e are "oin" to build on $hat $e learned about $eb forms and databases and $rite our
user lo"in s-stem3 #t the end of this tutorial our little application $ill re"ister ne$ users and lo" them
in and out3
To follo$ this chapter alon" -ou need to have the microblog app as $e left it at the end of the
previous chapter3 Please ma1e sure the app is installed and runnin"3
*oniguration
#s in previous chapters, $e start b- confi"urin" the Flas1 extensions that $e $ill use3 For the lo"in
s-stem $e $ill use t$o extensions, Flas1Co"in and Flas1C5penID3 Flas1Co"in $ill handle our users
lo""ed in state, $hile Flas1C5penID $ill provide authentication3 These extensions are confi"ured as
follo$s .file app/__init__!py0:
import osfrom flask!e(t!login import Dogin6anagerfrom flask!e(t!openid import JpenIE
from config import basedir
lm # Dogin6anager%lm!init_appapp%oid # JpenIEapp, os!path!Hoinbasedir, 'tmp'%%
The Flas1C5penID extension re then -ou have to install the development version from itHub:
$ flask/bin/pip uninstall flask-openid$ flask/bin/pip install git0git)//github!com/mitsuhiko/flask-openid!git
-
8/20/2019 The Flask Mega-Tutorial
33/159
?ote that -ou need to have git installed for this to $or13
#eisiting our User %odel
The Flas1Co"in extension expects certain methods to be implemented in our Rser class3 5utside of
these methods there are no re
-
8/20/2019 The Flask Mega-Tutorial
34/159
Flas1Co"in .file app/views!py0:
&lm!user_loaderdef load_userid%) return Rser!query!getintid%%
?ote ho$ this function is re"istered $ith Flas1Co"in throu"h the lm!user_loader decorator3 #lso
remember that user ids in Flas1Co"in are al$a-s unicode strin"s, so a conversion to an inte"er isnecessar- before $e can send the id to Flas1C'#lchem-3
The login ie) unction
?ext let2s update our lo"in vie$ function .file app/views!py0:
from flask import render_template, flash, redirect, session, url_for, request, gfrom flask!e(t!login import login_user, logout_user, current_user, login_requiredfrom app import app, db, lm, oidfrom !forms import Dogin"ormfrom !models import Rser
&app!route'/login', methods#:'MB', '8J';%&oid!loginhandlerdef login%) if g!user is not Cone and g!user!is_authenticated%) return redirecturl_for'inde('%% form # Dogin"orm% if form!validate_on_submit%) session:'remember_me'; # form!remember_me!data return oid!try_loginform!openid!data, ask_for#:'nickname', 'email';% return render_template'login!html',
title#'ign In', form#form,
providers#app!config:'J8BCIE_8AJOIEBA';%
?otice $e have imported several ne$ modules, some of $hich $e $ill use later3
The chan"es from our previous version are ver- small3 We have added a ne$ decorator to our vie$
function3 The oid!loginhandler tells Flas1C5penID that this is our lo"in vie$ function3
#t the top of the function bod- $e chec1 if g!user is set to an authenticated user, and in that case $e
redirect to the index pa"e3 The idea here is that if there is a lo""ed in user alread- $e $ill not do a
second lo"in on top3
The g "lobal is setup b- Flas1 as a place to store and share data durin" the life of a re
-
8/20/2019 The Flask Mega-Tutorial
35/159
db!session from Flas1C'#lchem-3 We2ve seen that the flask!g obect stores and shares data
thou"h the life of a re
-
8/20/2019 The Flask Mega-Tutorial
36/159
#fter that $e load the remember_me value from the Flas1 session, this is the boolean that $e stored
in the lo"in vie$ function, if it is available3
Then $e call Flas1Co"in2s login_user function, to re"ister this is a valid lo"in3
Finall-, in the last line $e redirect to the next pa"e, or the index pa"e if a next pa"e $as not provided in
the re
-
8/20/2019 The Flask Mega-Tutorial
37/159
5'author') 5'nickname') '=ohn'7,'body') '>eautiful day in 8ortland'
7, 5
'author') 5'nickname') 'usan'7,'body') 'he ?vengers movie was so cool'
7
; return render_template'inde(!html', title#'+ome', user#user, posts#posts%
There are onl- t$o chan"es to this function3 First, $e have added the login_required decorator3
This $ill ensure that this pa"e is onl- seen b- lo""ed in users3
The other chan"e is that $e pass g!user do$n to the template, instead of the fa1e obect $e used in
the past3
This is a "ood time to run the application3
When -ou navi"ate to http)//localhost)1222 -ou $ill instead "et the lo"in pa"e3 eep in
mind that to lo"in $ith 5penID -ou have to use the 5penID / from -our provider3 Bou can use one
of the 5penID provider lin1s belo$ the / text field to "enerate the correct / for -ou3
#s part of the lo"in process -ou $ill be redirected to -our provider2s $eb site, $here -ou $ill
authenticate and authori;e the sharin" of some information $ith our application .ust the email and
nic1name that $e re
-
8/20/2019 The Flask Mega-Tutorial
38/159
5< else
-
8/20/2019 The Flask Mega-Tutorial
39/159
Part /I: Proile Page
#eca"
In the previous chapter of this tutorial $e created our user lo"in s-stem, so $e can no$ have users lo"
in and out of the $ebsite usin" their 5penIDs3
Toda-, $e are "oin" to $or1 on the user profiles3 First, $e2ll create the user profile pa"e, $hich sho$s
the user2s information and more recent blo" posts, and as part of that $e $ill learn ho$ to sho$ user
avatars3 Then $e are "oin" to create a $eb form for users to edit their profiles3
User Proile Page
%reatin" a user profile pa"e does not reall- re
-
8/20/2019 The Flask Mega-Tutorial
40/159
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
41/159
The avatar method of Rser returns the / of the user2s avatar ima"e, scaled to the re
-
8/20/2019 The Flask Mega-Tutorial
42/159
tr valign#*top*4 td4img src#*55 user!avatar9UL% 77*4/td4 td4h94Rser) 55 user!nickname 77/h94/td4 /tr4 /table4 hr4 5< for post in posts
-
8/20/2019 The Flask Mega-Tutorial
43/159
tr valign#*top*4 td4img src#*55 user!avatar9UL% 77*4/td4 td4h94Rser) 55 user!nickname 77/h94/td4 /tr4 /table4 hr4 5< for post in posts
-
8/20/2019 The Flask Mega-Tutorial
44/159
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
45/159
diting the "roile inor%ation
#ddin" a profile form is surprisin"l- eas-3 We start b- creatin" the $eb form .file app/forms!py0:
from flask!e(t!wtf import "ormfrom wtforms import tring"ield, >oolean"ield, e(t?rea"ieldfrom wtforms!validators import EataAequired, Dength
class Bdit"orm"orm%) nickname # tring"ield'nickname', validators#:EataAequired%;% about_me # e(t?rea"ield'about_me', validators#:Dengthmin#2, ma(#9T2%;%
Then the vie$ template .file app/templates/edit!html0:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
46/159
To ma1e this pa"e eas- to reach, $e also add a lin1 to it from the user profile pa"e .file
app/templates/user!html0:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
47/159
chapter, ust put it in the correct location and then run db_upgrade!py to up"rade it3 If -ou don2t
have a previous database then call db_create!py to ma1e a brand ne$ one3
Than1 -ou for follo$in" m- tutorial3 I hope to see -ou a"ain in the next installment3
i"uel
-
8/20/2019 The Flask Mega-Tutorial
48/159
Part /II: Unit Testing
#eca"
In the previous chapters of this tutorial $e $ere concentratin" in addin" functionalit- to our little
application, a step at a time3 A- no$ $e have a database enabled application that can re"ister users, lo"
them in and out and let them vie$ and edit their profiles3
In this session $e are not "oin" to add an- ne$ features to our application3 Instead, $e are "oin" to
find $a-s to add robustness to the code that $e have alread- $ritten, and $e $ill also create a testin"
frame$or1 that $ill help us prevent failures and re"ressions in the future3
+et's ind a bug
I mentioned at the end of the last chapter that I have intentionall- introduced a bu" in the application3
et me describe $hat the bu" is, then $e $ill use it to see $hat happens to our application $hen it does
not $or1 as expected3
The problem in the application is that there is no effort to 1eep the nic1names of our users uni
-
8/20/2019 The Flask Mega-Tutorial
49/159
• lo"in $ith -our first account
• "o to the edit profile pa"e and chan"e the nic1name to 2dup2
• lo"out
• lo"in $ith -our second account
• "o to the edit profile pa"e and chan"e the nic1name to 2dup2
5ops! We2ve "ot an exception from s
-
8/20/2019 The Flask Mega-Tutorial
50/159
$hen and if a user experiences a failure in our application because $hen debu""in" is turned off
application failures are silentl- dismissed3 uc1il- there are eas- $a-s to address both problems3
*usto% HTTP error handlers
Flas1 provides a mechanism for an application to install its o$n error pa"es3 #s an example, let2s define
custom error pa"es for the HTTP errors 9+9 and ++, the t$o most common ones3 Definin" pa"es for
other errors $or1s in the same $a-3
To declare a custom error handler the errorhandler decorator is used .file app/views!py0:
&app!errorhandlerT2T%def not_found_errorerror%) return render_template'T2T!html'%, T2T
&app!errorhandler122%def internal_errorerror%) db!session!rollback% return render_template'122!html'%, 122
?ot much to tal1 about for these, as the- are almost selfCexplanator-3 The onl- interestin" bit is the
rollback statement in the error ++ handler3 This is necessar- because this function $ill be called as
a result of an exception3 If the exception $as tri""ered b- a database error then the database session is
"oin" to arrive in an invalid state, so $e have to roll it bac1 in case a $or1in" session is needed for the
renderin" of the template for the ++ error3
Here is the template for the 9+9 error:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
51/159
2ending errors ia e%ail
To address our second problem $e are "oin" to confi"ure t$o reportin" mechanisms for application
errors3 The first of them is to have the application send us an email each time an error occurs3
Aefore $e "et into this let2s confi"ure an email server and an administrator list in our application .file
config!py0:. mail server settings6?ID_BAOBA # 'localhost'6?ID_8JA # U16?ID_RBAC?6B # Cone6?ID_8?JAE # Cone
. administrator list?E6IC # :'you&e(ample!com';
5f course it $ill be up to -ou to chan"e these to $hat ma1es sense3
Flas1 uses the re"ular P-thon logging module, so settin" up an email $hen there is an exception isprett- eas- .file app/__init__!py0:
from config import basedir, ?E6IC, 6?ID_BAOBA, 6?ID_8JA, 6?ID_RBAC?6B,6?ID_8?JAE
if not app!debug) import logging from logging!handlers import 68+andler credentials # Cone if 6?ID_RBAC?6B or 6?ID_8?JAE) credentials # 6?ID_RBAC?6B, 6?ID_8?JAE% mail_handler # 68+andler6?ID_BAOBA, 6?ID_8JA%, 'no-reply&' 0 6?ID_BAOBA,
?E6IC, 'microblog failure', credentials% mail_handler!setDevellogging!BAAJA% app!logger!add+andlermail_handler%
?ote that $e are onl- enablin" the emails $hen $e run $ithout debu""in"3
Testin" this on a development P% that does not have an email server is eas-, than1s to P-thon2s 'TP
debu""in" server3 Gust open a ne$ console $indo$ .command prompt for Windo$s users0 and run the
follo$in" to start a fa1e email server:
python -m smtpd -n -c Eebuggingerver localhost)U1
When this is runnin", the emails sent b- the application $ill be received and displa-ed in the console$indo$3
+ogging to a ile
/eceivin" errors via email is nice, but sometimes this isn2t enou"h3 There are some failure conditions
that do not end in an exception and aren2t a maor problem, -et $e ma- $ant to 1eep trac1 of them in a
lo" in case $e need to do some debu""in"3
-
8/20/2019 The Flask Mega-Tutorial
52/159
For this reason, $e are also "oin" to maintain a lo" file for the application3
(nablin" file lo""in" is similar to the email lo""in" .file app/__init__!py0:
if not app!debug) import logging from logging!handlers import Aotating"ile+andler file_handler # Aotating"ile+andler'tmp/microblog!log', 'a', 9 X 92UT X 92UT,
92% file_handler!set"ormatterlogging!"ormatter'
-
8/20/2019 The Flask Mega-Tutorial
53/159
The $a- $e solve the problem is b- lettin" the ser class pic1 a uni
-
8/20/2019 The Flask Mega-Tutorial
54/159
uses it to determine if the nic1name has chan"ed or not3 If it hasn2t chan"ed then it accepts it3 If it has
chan"ed, then it ma1es sure the ne$ nic1name does not exist in the database3
?ext $e add the ne$ constructor ar"ument to the vie$ function:
&app!route'/edit', methods#:'MB', '8J';%&login_required
def edit%) form # Bdit"ormg!user!nickname% . !!!
To complete this chan"e $e have to enable field errors to sho$ in our template for the form .file
app/templates/edit!html0:
td4Gour nickname)/td4 td4 55 form!nicknamesiKe#UT% 77 5< for error in form!errors!nickname
-
8/20/2019 The Flask Mega-Tutorial
55/159
class est@aseunittest!est@ase%) def setRpself%) app!config:'BICM'; # rue app!config:'"_@A"_BC?>DBE'; # "alse app!config:'QD?D@+B6G_E??>?B_RAI'; # 'sqlite)///' 0os!path!Hoinbasedir, 'test!db'% self!app # app!test_client% db!create_all%
def tearEownself%) db!session!remove% db!drop_all%
def test_avatarself%) u # Rsernickname#'Hohn', email#'Hohn&e(ample!com'% avatar # u!avatar9UL% e(pected #'http)//www!gravatar!com/avatar/dTcYT1ZTdLT993Z3ULSZ1Y1SSTLbSbdS' assert avatar:2)lene(pected%; ## e(pected
def test_make_unique_nicknameself%) u # Rsernickname#'Hohn', email#'Hohn&e(ample!com'% db!session!addu% db!session!commit% nickname # Rser!make_unique_nickname'Hohn'% assert nickname # 'Hohn' u # Rsernickname#nickname, email#'susan&e(ample!com'% db!session!addu% db!session!commit% nicknameU # Rser!make_unique_nickname'Hohn'% assert nicknameU # 'Hohn' assert nicknameU # nickname
if __name__ ## '__main__') unittest!main%
Discussin" the unittest module is outside the scope of this article3 et2s ust sa- that class
est@ase holds our tests3 The setRp and tearEown methods are special, these are run before and
after each test respectivel-3 # more complex setup could include several "roups of tests each
represented b- a unittest!est@ase subclass, and each "roup then $ould have independent
setRp and tearEown methods3
These particular setRp and tearEown methods are prett- "eneric3 In setRp the confi"uration is
edited a bit3 For instance, $e $ant the testin" database to be different that the main database3 In
tearEown $e ust reset the database contents3
Tests are implemented as methods3 # test is supposed to run some function of the application that has a
1no$n outcome, and should assert if the result is different than the expected one3
'o far $e have t$o tests in the testin" frame$or13 The first one verifies that the ravatar avatar /s
from the previous article are "enerated correctl-3 ?ote ho$ the expected avatar is hardcoded in the test
and chec1ed a"ainst the one returned b- the Rser class3
The second test verifies the make_unique_nickname method $e ust $rote, also in the Rser
-
8/20/2019 The Flask Mega-Tutorial
56/159
class3 This test is a bit more elaborate, it creates a ne$ user and $rites it to the database, then ensures
the same name is not allo$ed as a uni
-
8/20/2019 The Flask Mega-Tutorial
57/159
Part /III: Follo)ers, *ontacts nd Friends
#eca"
5ur microblog application has been "ro$in" little b- little, and b- no$ $e have touched on most of
the topics that are re
-
8/20/2019 The Flask Mega-Tutorial
58/159
The t$o entities associated $ith this relationship are users and posts3 We sa- that a user has many
posts, and a post has one user3 The relationship is represented in the database $ith the use of a foreign
key on the 7man-7 side3 In the above example the forei"n 1e- is the user_id field added to the
posts table3 This field lin1s each post to the record of its author in the user table3
It is prett- clear that the user_id field provides direct access to the author of a "iven post, but $hat
about the reverseE For the relationship to be useful $e should be able to "et the list of posts $ritten b-
a "iven user3 Turns out the user_id field in the posts table is enou"h to ans$er this
-
8/20/2019 The Flask Mega-Tutorial
59/159
t-pes, since an- time one record in a table maps to one record in another table it can be ar"ued that it
ma- ma1e sense for these t$o tables to be mer"ed into one3
#e"resenting ollo)ers and ollo)ed
From the above relationships $e can easil- determine that the proper data model is the man-CtoCman-
relationship, because a user follo$s many users, and a user has many follo$ers3 Aut there is a t$ist3 We
$ant to represent users follo$in" other users, so $e ust have users3 'o $hat should $e use as the
second entit- of the man-CtoCman- relationshipE
Well, the second entit- of the relationship is also the users3 # relationship in $hich instances of an
entit- are lin1ed to other instances of the same entit- is called a self-referential relationship, and that is
exactl- $hat $e need here3
Here is a dia"ram of our man-CtoCman- relationship:
The followers table is our association table3 The forei"n 1e-s are both pointin" to the user table,
since $e are lin1in" users to users3 (ach record in this table represents one lin1 bet$een a follo$er user
and a follo$ed user3 i1e the students and teachers example, a setup li1e this one allo$s our database
to ans$er all the
-
8/20/2019 The Flask Mega-Tutorial
60/159
secondary#followers,primaryHoin#followers!c!follower_id ## id%,secondaryHoin#followers!c!followed_id ## id%,backref#db!backref'followers', laKy#'dynamic'%,laKy#'dynamic'%
The setup of the relationship is nonCtrivial and re
-
8/20/2019 The Flask Mega-Tutorial
61/159
model instead of doin" it directl- in vie$ functions3 That $a- $e can use this feature for the actual
application .invo1in" it from the vie$ functions0 and also from our unit testin" frame$or13 #s a matter
of principle, it is al$a-s best to move the lo"ic of our application a$a- from vie$ functions and into
models, because that simplifies the testin"3 Bou $ant to have -our vie$ functions be as simple as
possible, because those are harder to test in an automated $a-3
Aelo$ is the code to add and remove relationships, defined as methods of the Rser model .fileapp/models!py0:
class Rserdb!6odel%) .!!! def followself, user%) if not self!is_followinguser%) self!followed!appenduser% return self
def unfollowself, user%) if self!is_followinguser%) self!followed!removeuser%
return self
def is_followingself, user%) return self!followed!filterfollowers!c!followed_id ## user!id%!count% 4 2
These methods are ama;in"l- simple, than1s to the po$er of s
-
8/20/2019 The Flask Mega-Tutorial
62/159
class est@aseunittest!est@ase%) .!!! def test_followself%) u9 # Rsernickname#'Hohn', email#'Hohn&e(ample!com'% uU # Rsernickname#'susan', email#'susan&e(ample!com'% db!session!addu9% db!session!adduU% db!session!commit%
assert u9!unfollowuU% is Cone u # u9!followuU% db!session!addu% db!session!commit% assert u9!followuU% is Cone assert u9!is_followinguU% assert u9!followed!count% ## 9 assert u9!followed!first%!nickname ## 'susan' assert uU!followers!count% ## 9 assert uU!followers!first%!nickname ## 'Hohn' u # u9!unfollowuU% assert u is not Cone db!session!addu% db!session!commit% assert not u9!is_followinguU% assert u9!followed!count% ## 2 assert uU!followers!count% ## 2
#fter addin" this test to the testin" frame$or1 $e can run the entire test suite $ith the follo$in"
command:
!/tests!py
#nd if ever-thin" $or1s it should sa- that all our tests pass3
Database $ueries5ur current database model supports most of the re
-
8/20/2019 The Flask Mega-Tutorial
63/159
While this collectin" and sortin" $or1 needs to be done someho$, us doin" it results in a ver-
inefficient process3 This 1ind of $or1 is $hat relational databases excel at3 The database has indexes
that allo$ it to perform the
> 9
#nd finall-, our 8ost table contains one post from each user:
-
8/20/2019 The Flask Mega-Tutorial
64/159
Post
id te(t user3id
) post from susan 8
8 post from mar- >
> post from david 9
9 post from ohn )Here a"ain there are some fields that are omitted to 1eep the example simple3
Aelo$ is the oin portion of our
-
8/20/2019 The Flask Mega-Tutorial
65/159
/emember that the
-
8/20/2019 The Flask Mega-Tutorial
66/159
db!session!addu3% db!session!adduT% . make four posts utcnow # datetime!utcnow% p9 # 8ostbody#*post from Hohn*, author#u9, timestamp#utcnow 0timedeltaseconds#9%% pU # 8ostbody#*post from susan*, author#uU, timestamp#utcnow 0timedeltaseconds#U%%
p3 # 8ostbody#*post from mary*, author#u3, timestamp#utcnow 0timedeltaseconds#3%% pT # 8ostbody#*post from david*, author#uT, timestamp#utcnow 0timedeltaseconds#T%% db!session!addp9% db!session!addpU% db!session!addp3% db!session!addpT% db!session!commit% . setup the followers u9!followu9% . Hohn follows himself u9!followuU% . Hohn follows susan u9!followuT% . Hohn follows david uU!followuU% . susan follows herself uU!followu3% . susan follows mary u3!followu3% . mary follows herself u3!followuT% . mary follows david uT!followuT% . david follows himself db!session!addu9% db!session!adduU% db!session!addu3% db!session!adduT% db!session!commit% . check the followed posts of each user f9 # u9!followed_posts%!all% fU # uU!followed_posts%!all% f3 # u3!followed_posts%!all%
fT # uT!followed_posts%!all% assert lenf9% ## 3 assert lenfU% ## U assert lenf3% ## U assert lenfT% ## 9 assert f9 ## :pT, pU, p9; assert fU ## :p3, pU; assert f3 ## :pT, p3; assert fT ## :pT;
This test has a lot of setup code but the actual test is prett- short3 We first chec1 that the number of
follo$ed posts returned for each user is the expected one3 Then for each user $e chec1 that the correct
posts $ere returned and that the- came in the correct order .note that $e inserted the posts $ithtimestamps that are "uaranteed to al$a-s order in the same $a-03
?ote the usa"e of the followed_posts% method3 This method returns a
-
8/20/2019 The Flask Mega-Tutorial
67/159
count% runs the
-
8/20/2019 The Flask Mega-Tutorial
68/159
return redirecturl_for'login'%% user # Rser!query!filter_byemail#resp!email%!first% if user is Cone) nickname # resp!nickname if nickname is Cone or nickname ## **) nickname # resp!email!split'&'%:2; nickname # Rser!make_unique_nicknamenickname% user # Rsernickname#nickname, email#resp!email%
db!session!adduser% db!session!commit% . make the user follow him/herself db!session!adduser!followuser%% db!session!commit% remember_me # "alse if 'remember_me' in session) remember_me # session:'remember_me'; session!pop'remember_me', Cone% login_useruser, remember#remember_me% return redirectrequest!args!get'ne(t'% or url_for'inde('%%
Follow and Unfollow links?ext, $e $ill define vie$ functions that follo$ and unfollo$ a user .file app/views!py0:
&app!route'/follow/nickname4'%&login_requireddef follownickname%) user # Rser!query!filter_bynickname#nickname%!first% if user is Cone) flash'Rser
-
8/20/2019 The Flask Mega-Tutorial
69/159
db!session!commit% flash'Gou have stopped following ' 0 nickname 0 '!'% return redirecturl_for'user', nickname#nickname%%
These should be selfCexplanator-, but note ho$ there is error chec1in" all around, to prevent
unexpected problems and tr- to provide a messa"e to the user and a redirection $hen a problem has
occurred3
?o$ $e have the vie$ functions, so $e can hoo1 them up3 The lin1s to follo$ and unfollo$ a user $ill
appear in the profile pa"e of each user .file app/templates/user!html0:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
70/159
important piece of the pu;;le before $e can do that, so this $ill have to $ait until the next chapter3
Final )ords
We have implemented a core piece of our application toda-3 The topic of database relationships and
-
8/20/2019 The Flask Mega-Tutorial
71/159
Part I4: Pagination
#eca"
In the previous article in the series $e2ve made all the database chan"es necessar- to support the
2follo$er2 paradi"m, $here users choose other users to follo$3
Toda- $e $ill build on $hat $e did last time and enable our application to accept and deliver real
content to its users3 We are sa-in" "oodb-e to the last of our fa1e obects toda-!
2ub%ission o blog "osts
et2s start $ith somethin" simple3 The home pa"e should have a form for users to submit ne$ posts3
First $e define a sin"le field form obect .file app/forms!py0:class 8ost"orm"orm%) post # tring"ield'post', validators#:EataAequired%;%
?ext, $e add the form to the template .file app/templates/inde(!html0:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
72/159
?othin" earth shatterin" so far, as -ou can see3 We are simpl- addin" -et another form, li1e the ones
$e2ve done before3
ast of all, the vie$ function that ties ever-thin" to"ether is expanded to handle the form .file
app/views!py0:
from forms import Dogin"orm, Bdit"orm, 8ost"orm
from models import Rser, 8ost
&app!route'/', methods#:'MB', '8J';%&app!route'/inde(', methods#:'MB', '8J';%&login_requireddef inde(%) form # 8ost"orm% if form!validate_on_submit%) post # 8ostbody#form!post!data, timestamp#datetime!utcnow%,author#g!user% db!session!addpost% db!session!commit% flash'Gour post is now live'%
return redirecturl_for'inde('%% posts # : 5
'author') 5'nickname') '=ohn'7,'body') '>eautiful day in 8ortland'
7, 5
'author') 5'nickname') 'usan'7,'body') 'he ?vengers movie was so cool'
7 ; return render_template'inde(!html', title#'+ome', form#form,
posts#posts%
et2s revie$ the chan"es in this function one b- one:
• We are no$ importin" the 8ost and 8ost"orm classes
• We accept P5'T re
-
8/20/2019 The Flask Mega-Tutorial
73/159
'o, $h- the redirectE %onsider $hat happens after the user $rites a blo" post, submits it and then hits
the bro$ser2s refresh 1e-3 What $ill the refresh command doE Aro$sers resend the last issued reeautiful day in 8ortland'
7, 5
'author') 5'nickname') 'usan'7,'body') 'he ?vengers movie was so cool'
7 ;
Aut in the last article $e created the
-
8/20/2019 The Flask Mega-Tutorial
74/159
Pagination
The application is loo1in" better than ever, but $e have a problem3 We are sho$in" all of the follo$ed
posts in the home pa"e3 What happens if a user has a thousand follo$ed postsE 5r a millionE #s -ou
can ima"ine, "rabbin" and handlin" such a lar"e list of obects $ill be extremel- inefficient3
Instead, $e are "oin" to sho$ this potentiall- lar"e number of posts in "roups, or pages3Flas1C'#lchem- comes $ith ver- "ood support for pagination3 If for example, $e $anted to "et the
first three follo$ed posts of some user $e can do this:
posts # g!user!followed_posts%!paginate9, 3, "alse%!items
The paginate method can be called on an-
-
8/20/2019 The Flask Mega-Tutorial
75/159
&app!route'/', methods#:'MB', '8J';%&app!route'/inde(', methods#:'MB', '8J';%&app!route'/inde(/int)page4', methods#:'MB', '8J';%&login_requireddef inde(page#9%) form # 8ost"orm% if form!validate_on_submit%) post # 8ostbody#form!post!data, timestamp#datetime!utcnow%,
author#g!user% db!session!addpost% db!session!commit% flash'Gour post is now live'% return redirecturl_for'inde('%% posts # g!user!followed_posts%!paginatepage, 8J_8BA_8?MB, "alse%!items return render_template'inde(!html', title#'+ome', form#form, posts#posts%
5ur ne$ route ta1es the pa"e ar"ument, and declares it as an inte"er3 We also need to add the page
ar"ument to the inde( function, and $e have to "ive it a default value because t$o of the three routesdo not have this ar"ument, so for those the default $ill al$a-s be used3
#nd no$ that $e have a pa"e number available to us $e can easil- hoo1 it up to our
followed_posts
-
8/20/2019 The Flask Mega-Tutorial
76/159
posts # g!user!followed_posts%!paginatepage, 8J_8BA_8?MB, "alse%
To compensate for this chan"e, $e have to modif- the template .file
app/templates/inde(!html0:
-- posts is a 8aginate obHect --45< for post in posts!items
-
8/20/2019 The Flask Mega-Tutorial
77/159
doin" toda-, it is surprisin"l- simple .file app/templates/inde(!html0:
-- posts is a 8aginate obHect --45< for post in posts!items
-
8/20/2019 The Flask Mega-Tutorial
78/159
5< if posts!has_prev
-
8/20/2019 The Flask Mega-Tutorial
79/159
Part 4: Full Te(t 2earch
#eca"
In the previous article in the series $e2ve enhanced our database
-
8/20/2019 The Flask Mega-Tutorial
80/159
Flas1CWhoosh#lchem-, $hich inte"rates a Whoosh database $ith Flas1C'#lchem- models3
Python 0 *o%"atibility
nfortunatel-, $e have a problem $ith P-thon > and these pac1a"es3 The Flas1CWhoosh#lchem-
extension $as never made compatible $ith P-thon >3 I have for1ed this extension and made a fe$
chan"es to ma1e it $or1, so if -ou are on P-thon > -ou $ill need to uninstall the official version and
install m- for1:
$ flask/bin/pip uninstall flask-whooshalchemy$ flask/bin/pip install git0git)//github!com/miguelgrinberg/flask-whooshalchemy!git
'adl- this isn2t the onl- problem3 Whoosh also has issues $ith P-thon >, it seems3 In m- testin" I have
encontered this bu", and to m- 1no$led"e there isn2t a solution available, $hich means that at this time
the full text search capabilit- does not $or1 $ell on P-thon >3 I $ill update this section once the issues
are resolved3
*oniguration
%onfi"uration for Flas1CWhoosh#lchem- is prett- simple3 We ust need to tell the extension $hat is the
name of the full text search database .file config!py0:
+JJ+_>?B # os!path!Hoinbasedir, 'search!db'%
Model changes
'ince Flas1CWhoosh#lchem- inte"rates $ith Flas1C'#lchem-, $e indicate $hat data is to be
indexed for searchin" in the proper model class .file app/models!py0:
from app import app
import sysif sys!version_info 4# 3, 2%) enable_search # "alseelse) enable_search # rue import flask!e(t!whooshalchemy as whooshalchemy
class 8ostdb!6odel%) __searchable__ # :'body';
id # db!@olumndb!Integer, primary_key#rue% body # db!@olumndb!tring9T2%% timestamp # db!@olumndb!Eateime% user_id # db!@olumndb!Integer, db!"oreignFey'user!id'%%
def __repr__self%) return '8ost
-
8/20/2019 The Flask Mega-Tutorial
81/159
The model has a ne$ __searchable__ field, $hich is an arra- $ith all the database fields that $ill
be in the searchable index3 In our case $e onl- $ant to index the bod- field of our posts3
We also have to initiali;e the full text index for this model b- callin" the whoosh_inde( function3
?ote that since $e 1no$ that the search capabilit- currentl- does not $or1 on P-thon > $e have to s1ip
its initiali;ation3 5nce the problems in Whoosh are fixed the lo"ic around enable_search can be
removed3
'ince this isn2t a chan"e that affects the format of our relational database $e do not need to record a
ne$ mi"ration3
nfortunatel- an- posts that $ere in the database before the full text en"ine $as added $ill not be
indexed3 To ma1e sure the database and the full text en"ine are s-nchroni;ed $e are "oin" to delete all
posts from the database and start over3 First $e start the P-thon interpreter3 For Windo$s users:
flask\cripts\python
#nd for ever-one else:flask/bin/python
Then in the P-thon prompt $e delete all the posts:
444 from app!models import 8ost444 from app import db444 for post in 8ost!query!all%)!!! db!session!deletepost%444 db!session!commit%
2earching#nd no$ $e are read- to start searchin"3 First let2s add a fe$ ne$ posts to the database3 We have t$o
options to do this3 We can ust start the application and enter posts via the $eb bro$ser, as re"ular users
$ould do, or $e can also do it in the P-thon prompt3
From the P-thon prompt $e can do it as follo$s:
444 from app!models import Rser, 8ost444 from app import db444 import datetime444 u # Rser!query!get9%
444 p # 8ostbody#'my first post', timestamp#datetime!datetime!utcnow%, author#u%444 db!session!addp%444 p # 8ostbody#'my second post', timestamp#datetime!datetime!utcnow%, author#u%444 db!session!addp%444 p # 8ostbody#'my third and last post', timestamp#datetime!datetime!utcnow%,author#u%444 db!session!addp%444 db!session!commit%
The Flas1CWhoosh#lchem- extension is nice, because it hoo1s up into Flas1C'#lchem- commits
-
8/20/2019 The Flask Mega-Tutorial
82/159
automaticall-3 We do not need to maintain the full text index, it is all done for us transparentl-3
?o$ that $e have a fe$ posts in our full text index $e can issue searches:
444 8ost!query!whoosh_search'post'%!all%:8ost u'my second post'4, 8ost u'my first post'4, 8ost u'my third and lastpost'4;444 8ost!query!whoosh_search'second'%!all%
:8ost u'my second post'4;444 8ost!query!whoosh_search'second JA last'%!all%:8ost u'my second post'4, 8ost u'my third and last post'4;
#s -ou can see in the examples above, the
-
8/20/2019 The Flask Mega-Tutorial
83/159
Then $e add the form to our template .file app/templates/base!html0:
div46icroblog) a href#*55 url_for'inde('% 77*4+ome/a4 5< if g!user!is_authenticated%
-
8/20/2019 The Flask Mega-Tutorial
84/159
ust the first fift-3
The final piece is the search results template .file app/templates/search_results!html0:
-- e(tend base layout --45< e(tends *base!html*
-
8/20/2019 The Flask Mega-Tutorial
85/159
Part 4I: %ail 2u""ort
#eca"
In the most recent installments of this tutorial $e2ve been loo1in" at improvements that mostl- had to
do $ith our database3
Toda- $e are lettin" our database rest for a bit, and instead $e2ll loo1 at another important function that
most $eb applications have: the abilit- to send emails to its users3
In our little microblog application $e are "oin" to implement one email related function, $e $ill
send an email to a user each time he6she "ets a ne$ follo$er3 There are several more $a-s in $hich
email support can be useful, so $e2ll ma1e sure $e desi"n a "eneric frame$or1 for sendin" emails that
can be reused3
*oniguration
uc1il- for us, Flas1 alread- has an extension that handles email called Flas1Cail, and $hile it $ill
not ta1e us )++N of the $a-, it "ets us prett- close3
Aac1 $hen $e loo1ed at unit testin", $e added confi"uration for Flas1 to send us an email should an
error occur in the production version of our application3 That same information is used for sendin"
application related emails3
Gust as a reminder, $hat $e need is t$o pieces of information:
• the email server that $ill be used to send the emails, alon" $ith an- re
-
8/20/2019 The Flask Mega-Tutorial
86/159
6?ID_8JA # TS16?ID_RB_D # "alse6?ID_RB_D # rue6?ID_RBAC?6B # os!environ!get'6?ID_RBAC?6B'%6?ID_8?JAE # os!environ!get'6?ID_8?JAE'%
. administrator list?E6IC # :'your-gmail-username&gmail!com';
?ote that the username and pass$ord are read from environment variables3 Bou $ill need to set
6?ID_RBAC?6B and 6?ID_8?JAE to -our mail lo"in credentials3 Puttin" sensitive
information in environment variables is safer than $ritin" do$n the information on a source file3
We also need to initiali;e a 6ail obect, as this $ill be the obect that $ill connect to the 'TP server
and send the emails for us .file app/__init__!py0:
from flask!e(t!mail import 6ailmail # 6ailapp%
+et's send an e%ail!
To learn ho$ Flas1Cail $or1s $e2ll ust send an email from the command line3 'o let2s fire up P-thon
from our virtual environment and run the follo$in":
444 from flask!e(t!mail import 6essage444 from app import app, mail444 from config import ?E6IC444 msg # 6essage'test subHect', sender#?E6IC:2;, recipients#?E6IC%444 msg!body # 'te(t body'444 msg!html # 'b4+6D/b4 body'444 with app!app_conte(t%)
!!! mail!sendmsg%!!!!
The snippet of code above $ill send an email to the list of admins that are confi"ured in config!py3
The sender $ill be the first admin in the list3 The email $ill have text and HT versions, so
dependin" on ho$ -our email client is setup -ou ma- see one or the other3 ?ote that $e needed to
create an app_conte(t to send the email3 /ecent releases of Flas1Cail re
-
8/20/2019 The Flask Mega-Tutorial
87/159
from app import mail
def send_emailsubHect, sender, recipients, te(t_body, html_body%) msg # 6essagesubHect, sender#sender, recipients#recipients% msg!body # te(t_body msg!html # html_body mail!sendmsg%
?ote that Flas1Cail support "oes be-ond $hat $e are usin"3 Acc lists and attachments are available,
for example, but $e $on2t use them in this application3
Follo)er notiications
?o$ that $e have the basic frame$or1 to send an email in place, $e can $rite the function that sends
out the follo$er notification .file app/emails!py0:
from flask import render_templatefrom config import ?E6IC
def follower_notificationfollowed, follower%) send_email*:microblog;
-
8/20/2019 The Flask Mega-Tutorial
88/159
_e(ternal#rue% 77*455 follower!nickname 77/a4 is now a follower!/p4table4 tr valign#*top*4 td4img src#*55 follower!avatar12% 77*4/td4 td4 a href#*55 url_for'user', nickname#follower!nickname, _e(ternal#rue%77*455 follower!nickname 77/a4br /4 55 follower!about_me 77
/td4 /tr4/table4p4Aegards,/p4p4he code4microblog/code4 admin/p4
?ote the _e(ternal#rue ar"ument to url_for in the above templates3 A- default, the
url_for function "enerates /s that are relative to the domain from $hich the current pa"e comes
from3 For example, the return value from url_for*inde(*% $ill be /inde(, $hile in this case
$e $ant http)//localhost)1222/inde(3 In an email there is no domain context, so $e have
to force full-
-
8/20/2019 The Flask Mega-Tutorial
89/159
This is a terrible limitation, sendin" an email should be a bac1"round tas1 that does not interfere $ith
the $eb server, so let2s see ho$ $e can fix this3
synchronous calls in Python
What $e reall- $ant is for the send_email function to return immediatel-, $hile the $or1 of
sendin" the email is moved to a bac1"round process3
Turns out P-thon alread- has support for runnin" as-nchronous tas1s, actuall- in more than one $a-3
The threading and multiprocessing modules can both do this3
'tartin" a thread each time $e need to send an email is much less resource intensive than startin" a
brand ne$ process, so let2s move the mail!sendmsg% call into thread .file app/emails!py0:
from threading import hreadfrom app import app
def send_async_emailapp, msg%)
with app!app_conte(t%) mail!sendmsg%
def send_emailsubHect, sender, recipients, te(t_body, html_body%) msg # 6essagesubHect, sender#sender, recipients#recipients% msg!body # te(t_body msg!html # html_body thr # hreadtarget#send_async_email, args#:app, msg;% thr!start%
The send_async_email function no$ runs in a bac1"round thread3 Aecause it is a separate thread,
the application context re
-
8/20/2019 The Flask Mega-Tutorial
90/159
msg!body # te(t_body msg!html # html_body send_async_emailapp, msg%
uch nicer, ri"htE
The code that allo$s this ma"ic is actuall- prett- simple3 We $ill put it in a ne$ source file .file
app/decorators!py0:
from threading import hread
def asyncf%) def wrapperXargs, XXkwargs%) thr # hreadtarget#f, args#args, kwargs#kwargs% thr!start% return wrapper
#nd no$ that $e indirectl- have created a useful frame$or1 for as-nchronous tas1s $e can sa- $e are
done!
Gust as an exercise, let2s consider ho$ this solution $ould loo1 usin" processes instead of threads3 We
do not $ant a ne$ process started for each email that $e need to send, so instead $e could use the
8ool class from the multiprocessing module3 This class creates a specified number of processes
.$hich are for1s of the main process0 and all those processes $ait to receive obs to run, "iven to the
pool via the apply_async method3 This could be an interestin" approach for a bus- site, but $e $ill
sta- $ith the threads for no$3
Final )ords
The source code for the updated microblog application is available belo$:
Do$nload microblo"C+3))3;ip3
I2ve "ot a fe$ re
-
8/20/2019 The Flask Mega-Tutorial
91/159
Part 4II: Facelit
Introduction
If -ou have been pla-in" $ith the microblog application -ou must have noticed that $e haven2t
spent too much time on its loo1s3 p to no$ the templates $e put to"ether $ere prett- basic, $ith
absolutel- no st-lin"3 This $as useful, because $e did not $ant the distraction of havin" to $rite "ood
loo1in" HT $hen $e $ere codin"3
Aut $e2ve been hard at $or1 codin" for a $hile no$, so toda- $e are ta1in" a brea1 and $ill see $hat
$e can do to ma1e our application loo1 a bit more appealin" to our users3
This article is "oin" to be different than previous ones because $ritin" "ood loo1in" HT6%'' is a
vast topic that falls outside of the intended scope of this series3 There $on2t be an- detailed HT or
%'', $e $ill ust discuss basic "uidelines and ideas so on ho$ to approach the tas13
Ho) do )e do this5
While $e can ar"ue that codin" is hard, our pains are nothin" compared to those of $eb desi"ners, $ho
have to $rite templates that have a nice and consistent loo1 on a list of $eb bro$sers, most $ith
obscure bu"s or
-
8/20/2019 The Flask Mega-Tutorial
92/159
• Full- st-led forms
• #nd much, much more333
6ootstra""ing microblog
Aefore $e can add Aootstrap to our application $e have to install the Aootstrap %'', Gavascript and
ima"e files in a place $here our $eb server can find them3
In Flas1 applications the app/static folder is $here re"ular files "o3 The $eb server 1no$s to "o
loo1 for files in these location $hen a / has a /static prefix3
For example, if $e store a file named image!png in /app/static then in an HT template $e
can displa- the ima"e $ith the follo$in" ta":
img src#*/static/image!png* /4
We $ill install the Aootstrap frame$or1 accordin" to the follo$in" structure:
/app /static /css bootstrap!min!css bootstrap-responsive!min!css /img glyphicons-halflings!png glyphicons-halflings-white!png /Hs bootstrap!min!Hs
Then in the head section of our base template $e load the frame$or1 accordin" to the instructions:
EJ@G8B html4html lang#*en*4 head4 !!! link href#*/static/css/bootstrap!min!css* rel#*stylesheet* media#*screen*4 link href#*/static/css/bootstrap-responsive!min!css* rel#*stylesheet*4 script src#*http)//code!Hquery!com/Hquery-latest!Hs*4/script4 script src#*/static/Hs/bootstrap!min!Hs*4/script4 meta name#*viewport* content#*width#device-width, initial-scale#9!2*4 !!! /head4 !!!/html4
The link and script ta"s load the %'' and Gavascript files that come $ith Aootstrap3 ?ote that one
of the re
-
8/20/2019 The Flask Mega-Tutorial
93/159
Aootstrap, $hich simpl- involves chan"in" the HT in our templates3
The chan"es that $e $ill ma1e are:
• (nclose the entire pa"e contents in a sin"le column fixed la-out $ith responsive features3
• #dapt all forms to use Aootstrap form st-les3
•
/eplace our navi"ation bar $ith a ?avbar3• %onvert the previous and next pa"ination lin1s to Pa"er buttons3
• se the Aoostrap alert st-les for flashed messa"es3
• se st-led ima"es to represent the su""ested 5penID providers in the lo"in form3
We $ill not discuss the specific chan"es to achieve the above since these are prett- simple3 For those
interested, the actual chan"es can be vie$ed in diff form on this "ithub commit3 The Aootstrap
reference documentation $ill be useful $hen tr-in" to anal-;e the ne$ microblog templates3
7ote: #t the time this tutorial $as $ritten the current version of Aootstrap $as 83>383 T$itter has
released a ne$ maor version since then, and $ith it several of the %'' classes have chan"ed3 Visit theAootstrap site for more information3
Final )ords
Toda- $e2ve made the promise to not $rite a sin"le line of code, and $e stuc1 to it3 #ll the
improvements $e2ve made $ere done $ith edits to the template files3
To "ive -ou an idea of the ma"nitude of the transformation, here are a fe$ before and after screenshots3
%lic1 on the ima"es to enlar"e3
http://getbootstrap.com/2.3.2/scaffolding.html#layoutshttp://getbootstrap.com/2.3.2/scaffolding.html#responsivehttp://getbootstrap.com/2.3.2/scaffolding.html#responsivehttp://getbootstrap.com/2.3.2/base-css.html#formshttp://getbootstrap.com/2.3.2/components.html#navbarhttp://getbootstrap.com/2.3.2/components.html#paginationhttp://getbootstrap.com/2.3.2/components.html#alertshttp://getbootstrap.com/2.3.2/base-css.html#imageshttps://github.com/miguelgrinberg/microblog/commit/56d1d46326a79d9a3b9b6178ac02fe75c89625adhttp://getbootstrap.com/2.3.2/scaffolding.htmlhttp://getbootstrap.com/http://blog.miguelgrinberg.com/static/images/flask-mega-tutorial-part-xii-1.jpghttp://getbootstrap.com/2.3.2/scaffolding.html#layoutshttp://getbootstrap.com/2.3.2/scaffolding.html#responsivehttp://getbootstrap.com/2.3.2/base-css.html#formshttp://getbootstrap.com/2.3.2/components.html#navbarhttp://getbootstrap.com/2.3.2/components.html#paginationhttp://getbootstrap.com/2.3.2/components.html#alertshttp://getbootstrap.com/2.3.2/base-css.html#imageshttps://github.com/miguelgrinberg/microblog/commit/56d1d46326a79d9a3b9b6178ac02fe75c89625adhttp://getbootstrap.com/2.3.2/scaffolding.htmlhttp://getbootstrap.com/
-
8/20/2019 The Flask Mega-Tutorial
94/159
http://blog.miguelgrinberg.com/static/images/flask-mega-tutorial-part-xii-3.jpghttp://blog.miguelgrinberg.com/static/images/flask-mega-tutorial-part-xii-2.jpg
-
8/20/2019 The Flask Mega-Tutorial
95/159
The updated application can be do$nloaded belo$:
Do$nload microblo"C+3)83;ip3
In the next chapter $e $ill loo1 at improvin" the formattin" of dates and times in our application3 I
loo1 for$ard to see -ou then!
i"uel
https://github.com/miguelgrinberg/microblog/archive/version-0.12.ziphttp://blog.miguelgrinberg.com/static/images/flask-mega-tutorial-part-xii-4.jpghttps://github.com/miguelgrinberg/microblog/archive/version-0.12.zip
-
8/20/2019 The Flask Mega-Tutorial
96/159
Part 4III: Dates and Ti%es
$uick note about github
For those that did not notice, I recentl- moved the hostin" of the microblog application to "ithub3
Bou can find the repositor- at this location:
https:66"ithub3com6mi"uel"rinber"6microblo"
I have added ta"s that point to each tutorial step for -our convenience3
The "roble% )ith ti%esta%"s
5ne of the aspects of our microblog application that $e have left i"nored for a lon" time is the
displa- of dates and times3
ntil no$, $e ust trusted P-thon to render the datetime obects in our Rser and 8ost obects on
its o$n, and that isn2t reall- a "ood solution3
%onsider the follo$in" example3 I2m $ritin" this at >:9P on December >)st, 8+)83 - time;one is
P'T .or T%C* if -ou prefe