the flask mega-tutorial

Upload: eduardo-lopez

Post on 07-Aug-2018

216 views

Category:

Documents


0 download

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