AppEngine 06 Templates

download AppEngine 06 Templates

of 30

Transcript of AppEngine 06 Templates

  • 8/6/2019 AppEngine 06 Templates

    1/30

    1

    Chapter6Templates

    BuildingCloudApplicationswithGoogleAppEngine

    CharlesSeverance

    WhileitispossibletogeneratealloftheHMTLofyourapplicationfromwithin

    stringsinthePython,thisisgenerallyapoorwaytoauthorHTML.Inparticular,it

    meansthateverytimeyouwanttochangeabitofthegeneratedHTMLyouneedtodigthroughtheprogram,findthestringsandthenchangetheHTMLcode:

    formstring = '''

    Enter Guess:

    '''

    Atthesametime,ourwebapplicationneedstohavesomepartsofthewebpagesbegenerateddynamicallyaspartofthecodeofthewebapplicationeitherbasedon

    theusersinputorbasedonsomeinformationretrievedfromtheDataStore.

    Thecompromisethatsolvesbothproblemsistointroducethenotionofa

    template.AtemplateisafilethatcontainsmostlyHTMLwithspeciallymarkedareasofthetemplatethatarereplacedbydatapassedintothetemplatefromthe

    Pythoncodewhenthetemplateisrendered.

    Therearemanydifferenttemplatelanguagesandsyntaxes.Thedefaulttemplate

    syntaxusedbyGoogleAppEngineisborrowedfromtheDjangoproject(http://www.djangoproject.com/).

    TemplateSyntax

    ThetemplatesyntaxinGoogleAppEngineaugmentstheHTMLbyusingcurly

    bracestoidentifywherewearegivingcommandstothetemplatesystem.ThetemplateforournumberguessprogramwhenwearerespondingtoaGETrequest

    isinthefileindex.htm:

    {{ hint }}

    Enter Guess:

    TemplateshavespecialhotareaswheretextfromPythoncanbesubstitutedintotheoutputproducedbythetemplate.Theareainthetemplatethatwillbereplaced

    withdatafromPythonisbetweendoublecurlybraces.Inbetweenthedoublecurly

  • 8/6/2019 AppEngine 06 Templates

    2/30

    2

    braceshintisakeythatisusedtodeterminewhichpieceofdatafromthePython

    codetoputintothetemplatetoreplace{{hint}}.

    Weuseadifferenttemplateaftertheuserhasmadeaguesswewanttoshowboththeirguessandthenhintinthetemplateguess.htm:

    Your Guess: {{ stguess }}

    {{ hint }}

    Enter Guess:

    ThistemplatehastwoareastobereplacedwithdataprovidedfromPython.

    Byconvention,weputthetemplatesintoadirectorynamedtemplates.ThiswaywecaneasilykeepHTMLtemplatesseparatefromthePythoncode.

    Namingthefoldertemplatesisnotaruleitisaconvention.Followingtheconventionisagoodideabecauseitmeansthatotherdeveloperswillimmediately

    knowwheretofindthetemplatesinyourapplication.

    UsingtheTemplatesfromPython

    TodisplaythetemplateinPythonweaddcodetoindex.pytorenderthetemplate

    andthenprinttheoutputoftherenderprocesstotheHTTPresponse.Torendering

    atemplateAppEnginereadsthroughthetemplatefile,looksforthemarkedareastosubstitutewiththevaluesfromPythonandproducesthemergedoutput.

    ThefollowingaretheneededchangestomakeournumberguessingprogramuseatemplateinsteadofwritingouttheHTMLdirectlyfromPython:

    import osimport loggingimport wsgiref.handlersfrom google.appengine.ext import webapp

  • 8/6/2019 AppEngine 06 Templates

    3/30

    3

    from google.appengine.ext.webapp import template

    class MainHandler(webapp.RequestHandler):

    def get(self):

    temp = os.path.join(os.path.dirname(__file__),'templates/index.htm')

    outstr = template.render(temp,{'hint': 'Good luck!'})

    self.response.out.write(outstr)Weaddanimportstatementtomakethetemplatelibraryavailabletoour

    application.

    Withintheget()method,thefirstlineislookingupthefullpathtothetemplatefile

    byusingthepathtothecurrentlyexecutingfileandaddingtemplates/index.htmtotheendofthecurrentfilespath.

    Theactualtemplateprocessingisdoneinthetemplate.render()line.Thistakestwoparametersthefirstisthelocationofthetemplatefilefromthepreviousstep

    (storedinthevariabletemp)andthesecondisaPythondictionaryobjectwhichcontainsthestringstobeplacedinthetemplatewherethe{{hint}}entriesare

    found.Theresultsofthesubstitutionofthevariablesintothetemplatearereturned

    asastringinthevariableoutstr.

    Thetextreturnedfromthetemplate.render()inoutstrwilllookasfollows:

    Good Luck!

    Enter Guess:

    ThedatafromthePythondictionaryisnowsubstitutedintothetemplateasitis

    rendered.

    Thefollowingisadiagramofthetemplaterenderingprocess:

  • 8/6/2019 AppEngine 06 Templates

    4/30

    4

    Theprocessissimplerenderenginelooksforthehotspotsinthetemplateandwhenitfindsaspotwhereasubstitutionisrequired,therendererlooksinthe

    providedPythondictionarytofindthereplacementtext.

    outstr = template.render(temp,{'hint': 'Good luck!'})

    self.response.out.write(outstr)

    Thefinalstepinourget()methodistowriteoutstrtotheHTTPResponse.

    Thetemplatelanguageisactuallyquitesophisticated.Wewilllookatmoreofthecapabilitiesofthetemplatelanguagelater.

    TheNumberGuessingGameUsingTemplates

    WecaneasilyrewriteournumberguessinggametousetemplatesinsteadofgeneratingtheHTMLfromstringsinPython.Wecreatethetwotemplatefiles

    (index.htmandguess.htm)asshownaboveandplacetheminthetemplates

    folder.

    WethenmakethefollowingchangestoourMainHandlercode:

    from google.appengine.ext.webapp import template

    class MainHandler(webapp.RequestHandler):

    def get(self):temp = os.path.join(

    os.path.dirname(__file__),'templates/index.htm')

    outstr = template.render(temp,{'hint': 'Good luck!'})

  • 8/6/2019 AppEngine 06 Templates

    5/30

    5

    self.response.out.write(outstr)

    def post(self):stguess = self.request.get('guess')msg = ''

    guess = -1try:guess = int(stguess)

    except:guess = -1

    answer = 42if guess == answer:msg = 'Congratulations'

    elif guess < 0 :msg = 'Please provide a number'

    elif guess < answer:

    msg = 'Your guess is too low'else:msg = 'Your guess is too high'

    temp = os.path.join(os.path.dirname(__file__),'templates/guess.htm')

    outstr = template.render(temp,{'hint': msg, 'stguess': stguess})

    self.response.out.write(outstr)

    Wemakesuretoimportthetemplatelibraryatthebeginningofourprogramandthenintheget()methodwerenderthetemplate index.htmpassinginthestring

    GoodLuck!asthetestwhichwillbesubstitutedfor{{hint}}inthetemplate.

    Intheput()methodwerendertheguess.htmtemplateandwepassintwostrings.

    Wepassinthehintvaluewhichcomesfromthelogicoftheprograminthemsgvariableandwepassintheguessprovidedbytheuserunderthelabelofstguessso

    itcanbeplacedinthetemplatewherethe {{stguess}}isfound.

    Ineachcase,wecallself.response.out.write()tosendbacktheresultsofthe

    renderprocesstothebrowser.

    Whenwefirststarttheapplicationitlooksasfollows:

  • 8/6/2019 AppEngine 06 Templates

    6/30

    6

    IfweweretoviewthesourcetothepagewewouldseetheHTMLthatisproduced

    bytherenderstep:

    Good luck!

    Enter Guess:

    WecannotdetectwhichpartsoftheHMTLcamefromthetemplateandwhichpartsoftheHTMLcamefromthePythoncodeourbrowsersimplyreceivesanddisplays

    thepostmergeHTML.

    Whiletemplatesmayinitiallyseemmoreofabotherthanahelp,asyourprogram

    growsinsizeandcomplexitytemplatesareanimportantwaytokeepyourprogram

    organized.

    AbstractionandSeparationofConcernsModelViewController

    TheconceptoftemplatesisnotuniquetoGoogleAppEngine.Sinceweb

    applicationscanquicklygetquitecomplex,itisveryimportanttobeorganizedandforeachareaoffunctionalitytohaveitsplace.Followingcommonlyunderstood

    patternshelpsuskeeptrackofthebitsoftheprogram.Awellknownpatternhelpsyouunderstandyourowncodeaswellashelpsotherprogrammersunderstand

    yourcodesotheycanhelpyoudeveloporreviewyourapplication.

    Oneofthemostcommonprogrammingpatternsinwebbasedapplicationsiscalled

    ModelViewControllerorMVCforshort.ManywebframeworkssuchasRubyonRailsandSpringMVCfollowtheModelViewControllerpattern.

    TheMVCpatternbreaksthecodeinawebapplicationintothreebasicareas:

    ControllerThecodethatdoesthethinkinganddecisionmaking

  • 8/6/2019 AppEngine 06 Templates

    7/30

    7

    ViewTheHTML,CSS,etc.whichmakesupthelookandfeeloftheapplication

    ModelThepersistentdatathatwekeepinthedatastore

    InaGoogleAppEngineprogram,thevarious"handlers"inourindex.pyfileareexamplesofControllercodeandtheHTMLinthe templatesisanexampleofaView.

    WewillencountertheModelinalaterchapterandthenwewillrevisittheMVCpatternandexploreitinmoredetail.

    BuildingaMulti ScreenApplication

    WewillnowleaveournumberguessinggamebehindfortherestofthebookandpickupwhereweleftoffintheHTMLandCSSchapterwherewewerebuildinga

    multiscreenapplicationwithnavigationbetweenthescreens.Wewilluseandexpandthisapplicationastheexamplefortheremainderofthebook.

    Ifyourecall,wehadthreewebpagesandclevernavigationbetweenthepages.WeusedCSStostylethepagesandtuckthenavigationupintoatopbaracrosseach

    page.Wehadevenchangedthestylingofthecurrentlyselectedpagetogivethe

    useravisualclueastowhichwasthecurrentpage.

    IntheHTMLandCSSchapterweeditedandviewedthesefilesasstaticHTMLand

    CSSinasingledirectory.NowwewillsimplyputthesefilesintoanAppEngineapplication.Wewillsetupthedirectoriesasfollows:

  • 8/6/2019 AppEngine 06 Templates

    8/30

    8

    WeputtheHTMLfilesinthetemplatesfolderandplacetheCSSfile(glike.css)into

    anewfoldercalledstatic.ThestaticfoldercontainsfilesthatdonotchangetheymaybeCSS,JavaScript,orimagefiles.

    Wewillonlyneedtomakeslightchangestothefilesinitially,wesimplycopyin

    theCSSandHTMLfilesinthetheirrespectivefoldersinanewAppEngineapplication.

    StaticFilesinAppEngine

    Weindicatethatthestaticfolderholdstheseunchangingfilesandgivethema

    URLbyaddinganentrytotheapp.yamlfile:

    application: ae-05-templatesversion: 1runtime: pythonapi_version: 1

    handlers:- url: /static

    static_dir: static

    - url: /.*script: index.py

    WeaddanewhandlerentrywiththespecialindicatortomapURLswhichstartwith/statictothespecialstaticfolder.Andweindicatethatthematerialinthestatic

    folderisnotdynamicallygenerated.

    TheorderoftheURLentriesinthehandlersectionisimportant.Intheabove

    app.yamlfile,AppEnginefirstcheckstoseeifanincomingURLstartswith/staticandifthereisamatchthecontentisservedfromthestaticfolder.Ifthereisno

    match,thecatchallURL(/.*)routesallotherURLstotheindex.pyscript.Since

    thehandlerentriesarecheckedintheorderthattheyappearinthefile,itisimportantthatthecatchallURLbethelastentryinthefile.

    Theconventionistonamethefolderstatic.Youtechnicallycouldnamethefolderandpath(/static)anythingyoulike.Thestatic_dirdirective(likethescript

    directive)isanapp.yamldirectiveandcannotbechanged.Butthebestapproachis

    tofollowthepatternandnamethefolderandpathstatic.

    Theadvantageofplacingfilesinastaticfolderisthatitdoesnotuseyourprogram(index.py)forservingthesefiles.Sinceyoumaybepayingfortheprocessorusage

    oftheGoogleserversavoidingprocessorusageonservingstaticcontentisagood

    idea.Thestaticcontentstillcountsagainstyourdatatransferreditjustdoesnotincurextraprocessorcostsbyrunningthesefilesthroughindex.py.

  • 8/6/2019 AppEngine 06 Templates

    9/30

    9

    Evenmoreimportantly,whenyouindicatethatfilesarestatic_dir,itallowsGoogle

    todistributethesefilestomanydifferentserversgeographicallyandleavethefilesthere.Thismeansthatretrievingthesefilesfromdifferentcontinentsmaybeusing

    Googleserversclosesttotheuserofyourapplication.Thismeansthatyourapplicationcanscaletofarmoreusersefficiently.

    ReferencingStaticFiles

    Theonlychangeweneedtomakeistoproperlyreferencetheglike.cssCSSfilefrom

    theindex.htm,sites.htm,andtopics.htmfiles.SincetheCSSfileisnowinthestaticfolderwitha/staticpath,wemakethefollowingchangetoeachoftheHMTL

    files:App Engine - HTML

    WhenAppEngineseestheURLwhichstartswith/static,itroutesittooneofits

    manydistributedcopiesoftheCSSfileandservesupthecontent.

    GeneralizingTemplateLookupwithMultipleTemplates

    Inthenumberguessingexampletherewasonlytwotemplatessowehardcoded

    thenameofthetemplatewhenwewantedtodotherenderoperation.Sometimes

    youareinahandlerthatknowstheexactnameofthetemplatesoitcanfollowthehardcodedpattern.

    Othertimes,youwanttohaveabunchoftemplatesandservethemupwithpaths

    suchas:

    http://localhost:8080/index.htmhttp://localhost:8080/topics.htmhttp://localhost:8080/sites.htm

    Wecancreategeneralpurposecodetolookupatemplatebasedontheincoming

    pathoftherequest(i.e.thedocumentwhichisbeingrequested).

    WecantakeadvantageofthefactthatpartoftheincomingHTTPRequestisthe

    pathtotherequesteddocument.Forexample,ifthebrowserisrequestingthepagehttp://localhost:8080/topics.htm,thedocumentbeingrequestedis/topics.htm.

    Wecanretrievethedocumentstringfromtheincomingrequestusingself.request.path.Wecanthensimplytochecktoseeifwehaveatemplatethat

    matchestheincomingdocumentnameandrenderthattemplate.Ifwearegivena

  • 8/6/2019 AppEngine 06 Templates

    10/30

    10

    pathforwhichwedonothaveatemplatewesimplygivethemtheindex.htm

    template.

    HereistheControllercodetoaccomplishthisusingself.request.path:

    class MainHandler(webapp.RequestHandler):

    def get(self):path = self.request.path

    temp = os.path.join(os.path.dirname(__file__),'templates/' + path)

    if not os.path.isfile(temp):temp = os.path.join(

    os.path.dirname(__file__),'templates/index.htm')

    outstr = template.render(temp, { })self.response.out.write(outstr)

    Intheget()methodofMainHandler,wefirstpullinthedocumentthatwas

    requestedbythebrowserfromself.request.pathandconcatenatethecurrentworkingdirectoryofindex.pywithtemplatesandtherequesteddocumentusing

    os.path.join().

    Thenwechecktoseeifthisfileexistsifthefiledoesnotexist,weusethetemplate

    index.htm.

    Oncewehavechosenourtemplate,wecalltemplate.render().Fornowwehave

    nosubstitutablevaluesinthetemplatessowesendinanemptydictionary{}asthesecondparametertotherendercall.

    Oncetherenderiscomplete,wesendtherenderedoutputtotheHTTPresponse

    usingself.response.out.write().

    Wearetakingadvantageofthefactthattheself.request.pathvariableshowsthe

    path/documentthatisbeingrequestedbythebrowserforeachrequest.Inthelogoutputbelow,youcanseeeachpathbeingrequestedusingaGETrequest.

  • 8/6/2019 AppEngine 06 Templates

    11/30

    11

    Youcanseethebrowserrequestingapathlike/sites.htmwhichrendersfromthetemplateandthenwhenthebrowserseesthereferencetothe/static/glike.cssthe

    browserthendoesanotherGETrequesttoretrievetheCSSwhichisstoredinthe

    staticfolder.

    Theself.request.pathstartswithaslash(/)sowhenitisappendedtotemplates

    thefilepathwehandtotherenderengineasitsfirstparameterlookslike:

    templates/sites.htm

    Whichisexactlywherewehavestoredourtemplates.

    ExtendingBaseTemplates

    Wehaveonlybeguntoscratchthesurfaceofthecapabilitiesofthetemplate

    language.Oncewehavesuccessfullyseparatedourviews(HTMLandCSS)from

    thecontroller(Python),wecanstartlookingatwaystomanageourviewsmoreeffectively.

    IfyoulookattheexampleHTMLfilesusedastemplatesinthisapplication,youwillfindthatthefilesarenearlyidenticalexceptforafewsmalldifferencesbetween

    eachfile.Mostofthecontentofthefileisidenticalandcopiedbetweenfiles.App Engine - HTML

  • 8/6/2019 AppEngine 06 Templates

    12/30

    12

    App Engine

    SitesTopics

    App Engine: About

    Welcome to the site dedicated tolearning the Google App Engine.We hope you find www.appenginelearn.com useful.

    Theonlythingsthatchangebetweenthefilesare(1)whichlinkisselected(i.e.

    class=selected)and(2)theinformationinthebodycontentdiv.Allthematerialintheareaandnearlyallmaterialinthe headerdivareidentical

    betweenfiles.

    Wecauseasignificantmaintenanceproblemforourselveswhenwerepeatthis

    commontextinmany(perhapshundreds)filesinourapplication.Whenwewant

    tomakeachangetothiscommontextwehavetocarefullyeditallthefilesandmakethechangethisbecomestediousanderrorprone.Italsomeansthatwe

    havetotesteachscreenseparatelytomakesureitisupdatedandworkingproperly.

    Tosolvethis,wecreateaspecialtemplatethatcontainsthecommonmaterialforeachpage.Thenthepagefilesonlyincludethematerialthatisdifferent.Hereisasamplepagefilethatismakinguseofthebasetemplate.Hereisthenewindex.htm

    templatefile:

    {% extends "_base.htm" %}{% block bodycontent %}

    App Engine: About

    Welcome to the site dedicated tolearning the Google App Engine.We hope you find www.appenginelearn.com useful.

    {% endblock %}

    Thetemplatelanguageusescurlybracesandpercentsignstoindicateour

    commandstotherenderengine.Thefirstlinesaysthispagestartswiththetextcontainedinthefile_base.htm.Wearestartingwith_base.htmandthen

    extendingit.

  • 8/6/2019 AppEngine 06 Templates

    13/30

    13

    Thesecondlinesays,whenyoufindanareamarkedasthe bodycontentblockin

    the_base.htmfilereplacethatblockwiththetextinbetweentheblockandendblocktemplatecommands.

    The_base.htmfileisplacedinthetemplatedirectoryalongwithalloftherestofthe

    templatefiles:

    Thecontentsofthe_base.htmfilearethecommontextwewanttoputintoeach

    pageplusanindicationofwherethebodycontentistobeplaced:

    App Engine - HTML

  • 8/6/2019 AppEngine 06 Templates

    14/30

    14

    Ourapplicationhasseveralpagesandwhilewehavemovedmostoftherepeated

    textintoabasefile,thereisoneareainthe_base.htmthatneedstochangebetweenfiles.Ifwelookatthepages,weseethataswemovebetweenpages,wewantto

    havethenavigationlinkscoloreddifferentlytoindicatewhichpagewearecurrentlylookingat.

    WemakethischangebyusingtheselectedclassinthegeneratedHTML.For

    exampleonthetopics.htmfileweneedtheTopicslinktobeindicatedas

    selected:

    SitesTopics

    Weneedtogeneratethistextdifferentlyoneachpageandthegeneratedtext

    dependsonthefilewearerendering.ThepathindicateswhichpageweareonsowhenweareontheTopicspage,thepathis/topics.htm.

    Wemakeasmallchangetothehandlertopassthecurrentpathintotherenderprocessasfollows:

    class MainHandler(webapp.RequestHandler):

    def get(self):path = self.request.pathtemp = os.path.join(

    os.path.dirname(__file__),'templates/' + path)

    if not os.path.isfile(temp):temp = os.path.join(

    os.path.dirname(__file__),'templates/index.htm')

    outstr = template.render(temp, { 'path': path })self.response.out.write(outstr)

  • 8/6/2019 AppEngine 06 Templates

    15/30

    15

    Insteadofpassinganemptydictionaryasthesecondparameterto

    template.render(),wegiveeverytemplatethecurrentpathinavariablenamedpath.

    Withthischange,thetemplatecodehasaccesstothecurrentpathfortherequest.

    Wethenmakethefollowingchangetothetemplate:

    Sites

    Topics

    Thisinitiallylooksabitcomplicated.Atahighlevel,allitisdoingisaddingthetext

    class=selectedtotheanchor()tagwhenthecurrentpathmatches

    /topic.htmor/sites.htmrespectively.

    WearetakingadvantageofthefactthatwhitespaceandendlinesdonotmatterinHTML.Thegeneratedcodewilllookoneofthefollowingtwoways:

    Topics

    or

    Topics

    WhileitlooksalittlechoppyitisvalidHTMLandourclass=selectedappears

    whenappropriate.Lookingatthecodeinthetemplate,wecanexaminetheifequal

    templatedirective:

    {% ifequal path '/topics.htm' %}class="selected"

    {% endifequal %}

    Theifequaldirectivecomparesthecontentsofthepathvariablewiththestring

    /topics.htmandconditionallyincludestheclass=selectedinthegeneratedoutput.

    Thecombinationofthetwoifequaldirectivesmeansthatthelinksgiveusthe

    properlygeneratednavigationHTMLbasedonwhichpageisbeinggenerated.This

  • 8/6/2019 AppEngine 06 Templates

    16/30

    16

    isquitenicebecausenowtheentirenavigationcanbeincludedinthe_base.htm

    file,makingthepagetemplatesverycleanandsimple:

    {% extends "_base.htm" %}{% block bodycontent %}

    App Engine: About

    Welcome to the site dedicated tolearning the Google App Engine.We hope you find www.appenginelearn.com useful.

    {% endblock %}

    Withthesechanges,whenwenavigatetothetopicspage,itlooksasfollows:

    Ifweviewthesourcecodeofthispage,itlooksasfollows:

    App Engine HTML

    App Engine

    SitesTopics

  • 8/6/2019 AppEngine 06 Templates

    17/30

    17

    App Engine: Topics

    Python BasicsPython FunctionsPython Python Objects

    Hello WorldThe WebApp FrameworkUsing Templates

    Therearesomeblanklinesintheoutputwheretheifequalandendifequaldirectiveswerefoundinthetemplates.Youcanalteryourtemplatestoremove

    theseblankslinesbutitisprobablynotworthit.Havingeasilyreadtemplatesis

    valuable.

    Thisapproachmakesitverysimpletoaddanewpageormakeachangeacrossallpages.Ingeneralwhenwecanavoidrepeatingthesamecodeoverandover,our

    codeiseasiertomaintainandmodify.

    ReplacingMoreThanOneBlockinTheBaseTemplate

    Youmayhavenoticedthatallofourpageshavethesametitle.Thisisbecausewe

    havethetaginthe_base.htmfile.Sinceeverypageextendsthispagewe

    endupwiththesametitleoneverypage.

    Youmaynotwantthisyoumaywanttochangethetitleonsomepagesandnotonotherpages.Wecandothisbyputtingthetitleinitsownblockin_base.htmas

    followsbymakingthefollowingchangestothesectionof_base.htm:

    {% block title %}App Engine - HTML{% endblock %}

    Weusetemplatedirectivestodefinethetitleblockinourfile.Noticethatthe

    templatedirectivesdonothavetobeonseparatelines.Inbetweentheblockand

    endblockdirectivesweplacethetextAppEngineHTML.Thisisthetextthatwillbeplacedintheblockiftheextendingtemplatedoesnotprovidetextforatitle

    block.

  • 8/6/2019 AppEngine 06 Templates

    18/30

    18

    Wemakenochangestoourindex.htmtemplatesothispagerendersasfollows:

    ThetitleisAppEngineHTMLthedefaulttext.Thenwemakethefollowingchangetothetopics.htmtemplate:

    {% extends "_base.htm" %}{% block title %}App Engine - Topics

    {% endblock %}{% block bodycontent %}

    App Engine: TopicsPython BasicsPython FunctionsPython Python Objects

    Hello WorldThe WebApp FrameworkUsing Templates

    {% endblock %}

    Inadditiontoprovidingtextforthebodycontentblock,wealsoprovidetextforthetitleblocktooverridethedefaulttitletextin_base.htm.Withthischange,the

    topics.htmpagerendersasfollows:

  • 8/6/2019 AppEngine 06 Templates

    19/30

    19

    Notethatthetitlehasindeedchangedonwhentopics.htmisrendered.Bydefining

    blocksinthe_base.htmtemplatewithdefaulttextandoptionallyreplacingthattext

    inthetemplatewhichextendsthe_base.htmtemplateyouhavegreatflexibilityinhowyougenerateyourHTMLwithoutscatteringrepeatedcodethroughoutyour

    application.

    Thisalsomakesaddinganewpagetoyourapplicationmuchsimplerand

    straightforward.

    ExtendingourApplication

    Nowthatweunderstandhowtohandletemplatesingeneral,wecanstarttobuilda

    realapplication.Ourfirstimprovementwillbetoaddasimplescreentopromptfortheusersaccountandpassword.

    Fornow,sincewedontreallyhaveanyuseraccountswewilljustpretendthatall

    passwordsarethewordsecret.

    Forthisapplication,uptonowithavemostlybeenHTTPGETrequestsaswenavigatefromonepagetoanother.NowwewillbehandlingPOSTrequestsas

  • 8/6/2019 AppEngine 06 Templates

    20/30

    20

    well.Inasense,thisscreenissomewhatlikethenumberguessinggameinsteadit

    isapasswordguessinggame.

    WewillmakeitsothatourloginpageisattheURL/loginwewillbeabletodoaGETtothe/loginURLtogettheaccount/passwordformandwecandoaPOSTto

    thisURLtosubmittheaccountandpasswordforchecking.Sincethisloginactivityisawholenewchunkofwork,wewillmakeanewhandler

    calledLoginHandlerandroutethe/loginURLtotheLoginHander.

    Theapp.yamlfileisprettymuchunchangedexceptfortheapplicationname:

    application: ae-08-loginversion: 1runtime: pythonapi_version: 1

    handlers:- url: /staticstatic_dir: static

    - url: /.*script: index.py

    Wemakeseveralimprovementsofourindex.pyfile:

    import os

    import loggingimport wsgiref.handlersfrom google.appengine.ext import webappfrom google.appengine.ext.webapp import template

    def doRender(handler, tname='index.htm', values={}):temp = os.path.join(

    os.path.dirname(__file__),'templates/' + tname)

    if not os.path.isfile(temp):return False

    # Make a copy of the dictionary and add the pathnewval = dict(values)newval['path'] = handler.request.path

    outstr = template.render(temp, newval)handler.response.out.write(outstr)return True

  • 8/6/2019 AppEngine 06 Templates

    21/30

    21

    class LoginHandler(webapp.RequestHandler):

    def get(self):doRender(self, 'loginscreen.htm')

    def post(self):acct = self.request.get('account')pw = self.request.get('password')

    if pw == '' or acct == '':doRender(

    self,'loginscreen.htm',{'error' : 'Please specify Acct and PW'} )

    elif pw == 'secret':doRender(self,'loggedin.htm',{ } )

    else:

    doRender(self,'loginscreen.htm',{'error' : 'Incorrect password'} )

    class MainHandler(webapp.RequestHandler):

    def get(self):path = self.request.pathif doRender(self,path) :return

    if doRender(self,'index.htm') :return

    self.response.out.write('Error - unable to findindex.htm')

    def main():application = webapp.WSGIApplication([

    ('/login', LoginHandler),('/.*', MainHandler)],debug=True)

    wsgiref.handlers.CGIHandler().run(application)

    if __name__ == '__main__':main()

    Thefirstchangewemakein index.pyistocreateanewfunctioncalleddoRender()whichisareusablebitofcodetakenfromMainHandler().

    def doRender(handler, tname='index.htm', values={}):

  • 8/6/2019 AppEngine 06 Templates

    22/30

    22

    temp = os.path.join(os.path.dirname(__file__),'templates/' + tname)

    if not os.path.isfile(temp):return False

    # Make a copy of the dictionary and add the pathnewval = dict(values)newval['path'] = handler.request.path

    outstr = template.render(temp, newval)handler.response.out.write(outstr)return True

    Thisnewfunctiontakesthreeparameters:(1)handler,(2)atemplatename,and

    (3)adictionaryofvaluestobepassedintotemplate.render().Thissyntaxofthefunctiondefinitionalsoshowshowafunctioncanprovidedefaultvaluesfor

    parametersifthefunctioniscalledwithoutoneormoreparameters.Inthisdefinition,ifthesecondparameterisomitteditisassumedtobeindex.htmandif

    thethirdparameterisomitted,valuesisassumedtobeanemptydictionary.

    ThecodeindoRender()firstcheckstoseeifthetemplatefileexistsifthefiledoes

    notexist,thelogicalconstantFalseisreturnedinindicatethattherenderwasnot

    successful.

    Ifthetemplatefileexists,wemakeacopyofthedictionaryandthenaddthevalueofself.request.pathtothedictionaryasthatalltemplateshaveaccesstothecurrent

    documentbeingrequested.Thetemplate.render()iscalledandtheresponseiswrittenoutusing

    self.response.out.write().ThefunctionfinishesandreturnsTruetoindicateasuccessfulrenderwascompleted.

    Withthisnewhelperfunction,wecansimplifytheMainHandlercodethatrendersourtemplatesasfollows:

    class MainHandler(webapp.RequestHandler):

    def get(self):if doRender(self,self.request.path) :return

    doRender(self,'index.htm')

    WhenMainHandlerreceivesaGETrequest,ittriestorenderthedocumentthat

    wasrequestedbythebrowser.Ifthisrendersucceeds(Trueisreturned)weareall

  • 8/6/2019 AppEngine 06 Templates

    23/30

    23

    doneandweusereturntoexittheget()method.Ifthefirstrenderfails,werender

    theindex.htmtemplate.

    Note:Thekeywordsclassanddefhaveverydifferentpurposes.Whenweuseclass,wearedefiningapatternwhichweusetomakeinstances/objectsoftype

    MainHandlerthevalueinparenthesis(webapp.RequestHandler)isabaseclasswhichweareextendingtocreateMainHandler.Afunctiondefinitionissimplyareusablebitofcodethatwegiveaname.Thevaluesintheparenthesison

    afunctiondefinition(self)aretheparameterstothefunction.Inthisprogramwe

    haveaglobalfunctionwhichcanbeusedthroughoutthefile(i.e.doRender())andalsoafunctionwhichispartoftheMainHandlerclass(i.e.get()).

    NowthatwehaveournewdoRender()functionwecanaddanewhandler.First

    wemakesomechangestotheURLroutinginthemain()code:

    def main():

    application = webapp.WSGIApplication([('/login', LoginHandler),('/.*', MainHandler)],debug=True)

    wsgiref.handlers.CGIHandler().run(application)

    Weaddanewroutingruletothelistofroutes.Ifthebrowserrequestsapath/documentof/login,werouteallofthoserequeststotheLoginHandler.And

    allotherpathslike/topics.htmfallthroughandareroutedtoMainHandler.

    WeneedtodefinethenewLoginHandlerorourprogramwillnotrun.Wecanstart

    withasimpleLoginHandlerthatsimplyhandlesGETrequestsandrendersthetemplateloginscreen.htmasfollows:

    class LoginHandler(webapp.RequestHandler):

    def get(self):doRender(self, 'loginscreen.htm')

    WhenourLoginHandlerreceivesaGETrequestitsimplyrenderstheloginscreen.htmtemplateusingourniftydoRender()function.Wewillcome

    backandaddtherestoftheLoginHandlerinabitbutfornow,letstakealookat

    theournewtemplatecalledloginscreen.htm:{% extends "_base.htm" %}{% block bodycontent %}

    Please Log In

    Please enter your id and password to log in to this site.

  • 8/6/2019 AppEngine 06 Templates

    24/30

    24

    Account:
    Password:

    Cancel

    {% if error %}

    {{ error }}

    {% endif %}

    {% endblock %}

    Letstakealookatsomeofthenewbitsofthiscode.

    Firstwecreateaformwithtwotextinputfieldsnamedaccountandpassword.Weusetype=passwordonthepasswordfieldtotellthebrowsernottoshowthe

    actualtypedcharacters,butinsteadshowingasterisks(*)foreachcharacter.WewillsubmittheformdataforthispageusingthePOSTmethodtothe/login

    URLwhenSubmitispressed.

    InadditiontotheSubmitbuttonwealsocreateaCancelbuttonusingalittlebitofJavaScript.

    Cancel

    TheJavaScriptthatrunswhentheCancelbuttonisclicked,navigatesourbrowser

    (window.location)backtothemainurl(/).Thepurposeofthereturnfalse;inthecodesnippetistomakesurethattheformdataisnotsubmittedwhenthe

    Cancelbuttonisclicked.ThisJavaScriptcodeistriggeredwhentheonclickeventhappens(i.e.whenthebuttonisclicked).

    Inadditiontothenewbutton,wemakeprovisionsforprintinganerrormessageifwehavebeenrenderedwithanerrormessage:

    {% if error %}

    {{ error }}

    {% endif %}
  • 8/6/2019 AppEngine 06 Templates

    25/30

    25

    Thissequencecheckstoseeifwehaveavalueforerrorandifwehavebeenpassed

    avalueweaddabitofHTMLtothepagewhichprintstheerror.ThisallowsustodetectanerrorinthePythoncodeandsendtheuserbacktotheloginscreen.htm

    filewithanerrormessagesotheycanhopefullymaketheappropriateconnectionsandresubmit.

    Thenextchangeweneedtomakeistothe _base.htmfile.Weneedtoaddanewnavigationoptionsoouruserscangettothe/loginURLsotheycanbepresented

    theloginscreen.

    Wemakethefollowingchangetothenavigationcodein_base.htm:

    SitesTopics

    Login

    Weaddanewentrytoourlistofnavigationoptions.WhenLoginisclickedwe

    sendtheusertothe/loginURLwithaGETrequest.Andweusetheifequalpatterncheckingthecurrentpathtodetermineisweshouldaddthe

    class=selectedtothelinktocoloritdifferently.

    Withthesechangesinplace,ourmainpageandloginpagelookasfollows:

  • 8/6/2019 AppEngine 06 Templates

    26/30

    26

    WeshouldtesttomakesurethatpressingtheCancelbuttonnavigatesbacktothemainpage.WecannottestSubmitbecausewehavenotyetwrittenthecodeto

    handlePOSTrequestsinourLoginHandler.

    Thepost()methodintheLoginHandlerclassneedstotaketheaccountand

    passwordfieldsfromtheformandcheckthemtomakesurethattheuserknowsthesecretpasswordbeforetheycanlogin.Iftheymakeamistake,wewillsend

    themanerrormessageandletthemretry.

    Rememberthatforthisprogram,thepasswordisalwaysthewordsecret.The

    completeLoginHandlerisasfollows:

    class LoginHandler(webapp.RequestHandler):

    def get(self):doRender(self, 'loginscreen.htm')

    def post(self):acct = self.request.get('account')pw = self.request.get('password')logging.info('Checking account='+acct+' pw='+pw)

    if pw == '' or acct == '':doRender(self,'loginscreen.htm',

    {'error' : 'Please specify Acct and PW'} )elif pw == "secret":doRender(self,'loggedin.htm',{ } )

    else:

    doRender(self,'loginscreen.htm',{'error' : 'Incorrect password'} )

  • 8/6/2019 AppEngine 06 Templates

    27/30

    27

    Inthepost()method1fortheLoginHandlerwefirstpulltheaccountand

    passwordvaluesfromtheincomingformdatausingself.request.get().Thenwedoaseriesofifcheckstoseeiftheuserhasenteredtherightinformation.

    Firstwegetupsetifeithertheaccountorpasswordareblankwesendthemright

    backtologinscreen.htmwithahelpfulerrormessage:

    if pw == '' or acct == '':doRender(

    self,'loginscreen.htm',{'error' : 'Please specify Acct and PW'} )

    NotethatinordertomakethePythoncodemorereadable,webreakthe

    doRender()lineintoseverallines.Ifyouareinthemiddleofaparameterlistinparenthesis(),Pythonallowsyoutobreakthelineandindentationiseffectively

    ignoreduntiltheclosingparenthesisofthefunctioncallisencountered.Iftheaccountandpasswordarenonblankwechecktoseeifthepassword

    matchessecret.Ifso,wesendtheusertotheloggedin.htmwithnoerrormessage.Theloggedin.htmtemplateisrathersimple,showinghowniceitistobe

    abletogetallofyournavigationfroma_base.htmtemplatewithasingleextends

    statement:{% extends "_base.htm" %}{% block bodycontent %}

    Login successful.

    {% endblock %}

    Iftheuserdidnothavethecorrectpasswordwefallthroughtotheelse:statement

    andsendthembackto loginscreen.htmagainwithanappropriateerrormessage.

    else:doRender(

    self,

    'loginscreen.htm',{'error' : 'Incorrect password'} )

    Oncewehavemadeallofthesechanges,ournewloginscreenshouldbefullyfunctional.Hereareafewscreenshotsofthenewlogincapabilityinaction:

    1Whenwehaveafunctionthatispartofofaclassthatfunctionispartofthatclassandwecallthisoneofthemethodsoftheclass.

  • 8/6/2019 AppEngine 06 Templates

    28/30

    28

    Inordertoaddanewfunctionalitytoourapplicationwetakethefollowingsteps:

    DecideontheURLthatyouwanttouseforthenewfeature. Writeanewhandlerthehandlercaninitiallybesimpleandjusthandle

    aGETrequestwhichrendersanewtemplate

    Addaroutingentryinmain()tosendtheURLtoanewhandler Addnewtemplatesasneeded Updatethe_base.htmtoaddanynewnavigationthatisneeded Buildtherestofthehandlercodeandtestyournewfunctionality

    Nowthatwehaveagoodunderstandingoftheactofaddingahandlerandafew

    templatesaddingfunctionalitytotheapplicationisprettysimpleandcanbeaccomplishedwitharelativelyfewlinesandlittleornorepetitivecode.

    SyntaxErrors

    Bynow,ifyouhavebeenfollowingalongandimplementingthecodeaswego,youhavelikelymadesomeerrorsinyourprogramandseenthepurplescreenof

    death:

  • 8/6/2019 AppEngine 06 Templates

    29/30

    29

    Thiscanbeabitintimidatingandiscertainlyhardtoread.Themostimportantrule

    whenreadingthisscreenistogenerallyignoreeverythingexcepttheverylastfewlinesofthescreenthatwilllookasfollows:

    Themessagethatitgivesyoumayormaynotdescribeyourmistake.Butitusuallygivesyousomecluesastowheretolookfortheerror.Inthisexamplethemistake

    wasamissingcommaonline52ofmyindex.py.

    Ifyoumakeamistakeinsideofatemplateitisalsoprettyverboseandcryptic.

    Again,scrolltothebottomoftheerrormessageandworkbackwards.

    Inthisexample,Iforgottoclosea{%block%}tagasfollows:

    {% extends "_base.htm" %}{% block bodycontent %}

    App Engine: About

    Welcome to the site dedicated tolearning the Google App Engine.We hope you find www.appenginelearn.com useful.

  • 8/6/2019 AppEngine 06 Templates

    30/30

    Overtimeyoutheerrormessageswillmakeabitmoresense.Butinthemeantime,

    thebestdefenseistowriteyourprogramsandtemplatesslowlyandmakesuretotestafteryouhavemadeafewchanges.Soifsomethingbreaksyoucanbackand

    lookatthefewchangesyouhavemadesincetheprogramwaslastworking.MoreonTemplates

    Thischapteronlyscratchedthesurfaceofthetemplatedirectives.TheGoogleAppEnginetemplatelibrarycomesfromtheDjangoproject(www.django.org).Youcan

    readmoreaboutthetemplatelanguagefeaturesat:

    http://www.djangoproject.com/documentation/0.96/templates/

    Summary

    UsingtemplatesandseparatingyourView(HTML)fromyourController(Python)allowsyoutowriteincreasinglylargerprogramswithoutgoingcrazykeepingtrack

    ofdetails.Adoptingandfollowingpatternsinyoursoftwaredevelopmentwill

    makeyourapplicationsmorereadabletoyouandtoothers.

    GoogleAppEngineprovidesapowerfultemplatelanguagewhichwasadaptedfrom

    thesuccessfulDjangoprojectwhichallowstextandblockstobereplacedaswellassupportforbasetemplatesandtemplateswhichextendthosebasetemplates.

    ThismaterialisCopyrightAllRightsReservedCharlesSeverance

    [email protected]