Post on 10-Aug-2021
transcript
GoogleApplicationEngineTemplatesforHTML
UniversityofMichigan–InformaticsCharlesSeverance
WhileitispossibletogeneratealloftheHMTLofyourapplicationfromwithinstringsinthePython,thisisgenerallyapoorwaytoauthorHTML.Inparticular,itmeansthateverytimeyouwanttochangeabitofthegeneratedHTMLyouneedtodigthroughtheprogram,findthestringsandthenchangetheHTMLcode: formstring = """<form method="post" action="/" enctype="multipart/form-data"> Zap Data: <input type="text" name="zap"><br> Zot Data: <input type="text" name="zot"><br> File Data: <input type="file" name="filedat"><br> <input type="submit"> </form>""" Atthesametime,ourwebapplicationneedstohavesomepartsofthewebpagesbegenerateddynamicallyaspartofthecodeofthewebapplication–eitherbasedontheuser’sinputorbasedonsomeinformationretrievedfromthedatabase.Thecompromiseforthisistointroducethenotionofa“template”.AtemplateisafilethatcontainsmostlyHTML–buttherearespeciallymarkedareasofthetemplatethatisreplacedbydatahandedtothetemplatefromthePythoncodewhenthetemplateistoberendered.Therearemanydifferenttemplatinglanguagesandsyntaxes.ThedefaulttemplatesyntaxusedbytheGoogleApplicationEngineisfromtheDjangoproject.TemplateSyntaxThetemplatesyntaxinGoogleApplicationaugmentstheHTMLusingcurlybracestoidentifytemplatedirectives.Thetemplateforourdumperprogram:
<form method="post" action="/" enctype="multipart/form-data"> Zap Data: <input type="text" name="zap"><br> Zot Data: <input type="text" name="zot"><br> File Data: <input type="file" name="filedat"><br> <input type="submit"> </form> <pre> Request Data: {{ dat }} </pre>
TheareainthetemplatethatwillbereplacedwithdatafromPythonistheareaindoublecurlybraces.Inbetweenthedoublecurlybraces”dat”isakeythatisusedtodeterminewhichpieceofdatafromthePythoncodetoputintothetemplatetoreplacethe{{dat}}inthetemplate.Byconvention,weputthetemplatesintoadirectorynamed“templates”.ThiswaywecaneasilykeepHTMLtemplatesseparatefromthePythoncode.
Namingthefolder“templates”isnotarule–itisaconvention.Itisagoodconventionbecauseitmeansthatotherdeveloperswillimmediatelyknowwheretofindthetemplates.UsingtheTemplatesfromPythonTodisplaythetemplateinPythonweaddcodeto“render”thetemplateandthenprinttheoutputoftherenderprocesstotheHTTPresponse.Hereissomesimplecodewhichaccomplishesrenderstheindex.htmfile: import os from google.appengine.ext.webapp import template temp = os.path.join(os.path.dirname(__file__), 'templates/index.htm') outstr = template.render(temp, {'dat': ‘Hello There’}) self.response.out.write(outstr)
Thefirstlineisabitcomplexlooking–itcallsthePythonoperatingsystemlibrary(os)tolookupthelocationoftheindex.htmfileinthefoldernamedtemplates.Therealworkisdoneinthetemplate.render()line.Thistakestwoparameters–thefirstisthelocationofthetemplatefileandthesecondisaPythondictionaryobjectwhichcontainsthestringstobeplacedinthetemplatewherethe{{dat}}entriesarefound.Theresultsofthesubstitutionofthevariablesintothetemplatearereturnedasastringinthevariableoutstr.Thenthestringthat(outstr)isreturnedfromtherenderoperationisthenwrittenoutastheHTTPresponse.Thetextreturnedfromtherenderprocesswilllookasfollows:
<form method="post" action="/"
enctype="multipart/form-data"> Zap Data: <input type="text" name="zap"><br> Zot Data: <input type="text" name="zot"><br> File Data: <input type="file" name="filedat"><br> <input type="submit"> </form> <pre> Request Data: Hello There </pre>
ThedatafromthePythondictionaryisnowsubstitutedintothetemplateasitisrendered.Thefollowingisadiagramoftheprocess.
Theprocessissimple–renderenginelooksforthe“hotspots”inthetemplateandwhenitfindsaspotwhereasubstitutionisrequired,therendererlooksinthePythondictionarytofindthereplacementtext.Thetemplatelanguageisactuallyquitesophisticated–wewilllookatthecapabilitiesofthetemplatelanguageinmoredetaillater.OverallFlowTogobacktoourexampleandputitallincontext,wecanseehowourdumperprogramhasevolved: def dumper(self): prestr = ‘ ‘ for key in self.request.params.keys(): value = self.request.get(key) if len(value) < 100: prestr = prestr + key+':'+value+'\n' else: prestr = prestr + key+':'+str(len(value))+' (bytes long)\n'
temp = os.path.join(os.path.dirname(__file__), 'templates/index.htm’) outstr = template.render(temp, {'dat': prestr}) self.response.out.write(outstr)
Thefirsthalfofthedumper()methodloopsthroughtherequestparametersandinsteadofwritingtheparameterinformationtotheresponse,theinformationisappendedintothepredatstringincludinglabelinginformation,theparametersthemselvesandnewlines(“\n”).Thenthepredatstringisthenpassedintothetemplate.render()asinadictionaryunderthekey“dat”.TherenderenginethenmergesthedatafromthePythoncodewiththeHTMLtemplateandreturnstheresultingHTMLthatiswrittenoutastheresponse.AbstractionandSeparationofConcerns–“ModelViewController”Forcomplexwebprogramsitisveryimportanttobeorganized–andforeachareaoffunctionalitytohaveitsplace.Followingcommonlyunderstoodpatternshelpsuskeeptrackofthebitsoftheprogramasitbecomesincreasinglycomplex.Oneofthemostcommonprogrammingpatternsinweb‐basedapplicationsiscalled“Model‐View‐Controller”orMVCforshort.ManywebframeworkssuchasRubyonRailsandSpringMVCfollowtheModel‐View‐Controllerpattern.TheMVCpatternbreaksthecodeinawebapplicationintothreebasicareas:
• Controller‐Thecodethatdoesthethinkinganddecisionmaking
• View‐TheHTML,CSS,etc.whichmakesupthelookandfeeloftheapplication
• Model‐Thepersistentdatathatwekeepinthedatastore
InaGoogleApplicationEngineprogram,theindex.pyisanexampleofControllercodeandtheHTMLinthetemplatesisanexampleofaView.WewillencountertheModelinalaterchapterandthenwewillrevisittheMVCpatternandexploreitinmoredetail.MultipleTemplatesRealapplicationshavemanydifferentviews(screens)sonowwewillexplorehowtosupportmultipletemplatesinourapplication.Wewillbuildasimpleapplicationwhichhasthreescreensandnavigationbetweenthescreens.
ThisapplicationusesCSStomakethenavigationappearatthetopofthescreen.Lookingattheapplicationlayout,weseeonetemplateforeachHTMLpage.
Thetemplatesfoldercontainsthetemplatesthatareusedtocreateoutputdynamically(rendered)inthePythoncode(index.py)–thestaticfoldercontainsfilesthatdonotchange–theymaybeCSS,Javascript,orimagefiles.StaticFilesWeindicatethatthestaticfolderholdsthese“un‐changing”filesbyaddinganentrytotheapp.yamlfile:
application: ae-05-templates version: 1 runtime: python api_version: 1 handlers: - url: /static static_dir: static - url: /.* script: index.py
WeaddanewhandlerentrywiththespecialindicatortomapURLswhichstartwith/statictothespecialstaticfolder.Andweindicatethatthematerialinthestaticfolderisnon‐dynamic.TheorderoftheURLentriesinthehandlersectionisimportant–itfirstcheckstoseeifanincomingURLstartswith“/static”–ifthereisamatch–thecontentis
servedfromthestaticfolder.Ifthereisnomatch,the“catch‐all”URL(/.*)routesallURLstotheindex.pyscript.Theconventionistonamethefolder“static”.Youtechnicallycouldnamethefolderandpath(/static)anythingyoulike.Thestatic_dirdirective(likethescriptdirective)isadirectiveandcannotbechanged.Butthesimplethingistoalwaysfollowthepatternandnamethefolderandpath“static”.Theadvantageofplacingfilesinastaticfolderisthatitdoesnotuseyourprogram(index.py)forservingthesefiles.SinceyoumaybepayingfortheCPUusageoftheGoogleservers–avoidingCPUusageonservingstaticcontentisagoodidea.Thestaticcontentstillcountsagainstyourdatatransferred–itjustdoesnotincurCPUcosts.Evenmoreimportantly,whenyouindicatethatfilesarestatic,itallowsGoogletodistributethesefilestomanydifferentserversgeographicallyandleavethefilesthere.ThismeansthatretrievingthesefilesfromdifferentcontinentsmaybeusingGoogleserversclosesttotheuserofyourapplication.Thismeansthatyourapplicationcanscaletofarmoreusersefficiently.ReferencingStaticFilesOnceyouputafileinthestaticfolder,yousimplyreferencethosefileswithabsolutepathswhichstartwith/staticasfollows:<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>App Engine - HTML</title> <link href="/static/glike.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="header">
WhentheAppEngineseestheURL,itroutesittooneofitsmanydistributedcopiesofthefileandservesupthecontent.GeneralizingTemplateLookupwithMultipleTemplatesIntheaboveexample(ae‐04‐template)therewasonlyonetemplatesowehard‐codedthenameofthetemplatewhenwewantedtodotherenderoperation.Sometimesyouareinahandlerthatknowstheexactnameofthetemplatesoitcanfollowthatpattern.Othertimes,youwanttohaveabunchoftemplatesandservethemupwithpathssuchas:
http://localhost:8080/index.htm http://localhost:8080/topics.htm http://localhost:8080/sites.htm
Andwedonotwanttohavetowritespecialcodetolookupeachtemplateseparately.Wecancreategeneralpurposecodetolookupatemplatebasedontheincomingpathoftherequest–andifwefindamatchingtemplate,weusethattemplateandotherwiseweusetheindex.htmtemplate.Hereisthecontrollercodetoaccomplishthis:class MainHandler(webapp.RequestHandler): def get(self): path = self.request.path try: temp = os.path.join(os.path.dirname(__file__), 'templates' + path) outstr = template.render(temp, { }) self.response.out.write(outstr) except: temp = os.path.join(os.path.dirname(__file__), 'templates/index.htm') outstr = template.render(temp, { }) self.response.out.write(outstr)
Wefirstpullinthepathfromself.request.pathandappendthepathtotemplates–wetrytoperformarenderusingthisfile–andifitfails,wegototheexcept:processingandrenderusingtheindex.htmtemplate.Theself.request.pathvariableshowsthepathwithintheapplicationthatisbeingrequested.Inthelogoutputbelow,youcanseeeachpathbeingrequestedusingaGETrequest.
Youcanseethebrowserrequestingapathlike“/sites.htm”whichrendersfromthetemplateandthenwhenthebrowserseesthereferencetothe“/static/glike.css”the
browserthendoesanotherGETrequesttoretrievetheCSSwhichisstoredinthestaticfolder.Theself.request.pathstartswithaslash(/)sowhenitisappendedto“templates”thepathwehandtotherenderenginelooksliketemplates/sites.htm Whichisexactlywherewehavestoredourtemplate.ExtendingBaseTemplatesWehaveonlybeguntoscratchthesurfaceofthecapabilitiesofthetemplatinglanguage.Oncewehavesuccessfullyseparatedourviewsfromthecontroller,wecanstartlookingatwaystomanageourviewsmoreeffectively.IfyoulookattheexampleHTMLfilesusedastemplatesinthisapplication,youwillfindthatthefilesarenearlyidenticalexceptforafewsmalldifferencesbetweeneachfile.Mostofthecontentofthefileisidenticalandcopiedbetweenfiles.<head> <title>App Engine - HTML</title> <link href="/static/glike.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="header"> <h1><a href="index.htm" class="selected"> App Engine</a></h1> <ul class="toolbar"> <li><a href="sites.htm">Sites</a></li> <li><a href="topics.htm" class="selected">Topics</a></li> </ul> </div> <div id="bodycontent"> <h1>Application Engine: Topics</h1> <ul> <li>Python Basics</li> <li>Python Functions</li> <li>Python Python Objects</li> <li>Hello World</li> <li>The WebApp Framework</li> <li>Using Templates</li> </ul> </div> </body> </html>
Theonlythingsthatchangebetweenthefilesiswhichlinkisselected(i.e.class=”selected”)andtheinformationinthe“bodycontent”div.Allthematerialinthe<head>areaandnearlyallmaterialintheheaderdivareidenticalbetweenfiles.
Wecauseasignificantmaintenanceproblemwhenwerepeatthistextinmany(perhapshundreds)filesinourapplication.Whenwewanttomakeachangetothiscommoncontent–wehavetoeditallthefilesandmakethechange–thisbecomestediousanderrorprone.Italsomeansthatwehavetotesteachscreenseparatelytomakesureitisupdatedandworkingproperly.Tosolvethis,wecreateaspecialtemplatethatcontainsthecommonmaterialforeachpage.Thenthepagefilesonlyincludethematerialthatisdifferent.Hereisasamplepagefilethatismakinguseofthebasetemplate.Hereisthenewindex.htmtemplatefile:
{% extends "_base.htm" %} {% block bodycontent %} <h1>Application Engine: About</h1> <p> Welcome to the site dedicated to learning the Google Application Engine. We hope you find www.appenginelearn.com useful. </p> {% endblock %}
Thetemplatinglanguageusescurlybracestoindicateourcommandstotherenderengine.Thefirstlineofsaysthispagestartswiththetextcontainedinthefile“_base.htm”.Wearestartingwith_base.htmandextendingit.Thesecondlinesays,“whenyoufindanareamarkedasthe“bodycontentblock”inthe_base.htmfile–replacethatblockwiththetextinbetweentheblockandendblocktemplatecommands.The_base.htmfileisplacedinthetemplatedirectoryalongwithalloftherestofthetemplatefiles:
Thecontentsofthe_base.htmfilearethecommontextwewanttoputintoeachpageplusanindicationofwherethebodycontentistobeplaced:<head> <title>App Engine - HTML</title>
<link href="/static/glike.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="header"> <h1><a href="index.htm" class="selected"> App Engine</a></h1> <ul class="toolbar"> <li><a href="sites.htm">Sites</a></li> <li><a href="topics.htm" >Topics</a></li> </ul> </div> <div id="bodycontent"> {% block bodycontent %} Replace this {% endblock %} </div> </body> </html>
Weincludeatemplateenginedirectiveinthebasetemplatetoindicatethebeginningandendoftheblockthatwillbereplacedbyeachtemplatethatuses(extends)_base.htm.Thetext“Replacethis”willnotappearintheresultingHTMLaftertherenderhasbeencompleted.
WedonotneedtomakeanychangetotheControllercode–theuseofabasetemplateissomethingthatiscompletelyhandledinthetemplate.render()call.ConditionalHTMLusingifequalOutapplicationhasseveralpages–andwhilewehavemovedmostoftherepeatedtextintoabasefile,thereisoneareainthe_base.htmthatneedstochangebetweenfiles.Ifwelookatthepages,weseethataswemovebetweenpages,wewanttohavethenavigationlinkscoloreddifferentlytoindicatewhichpagewearecurrentlylookingat.
WemakethischangebyusingtheselectedclassinthegeneratedHTML.Forexampleonthetopics.htmfileweneedthe“Topics”linktobeindicatedas“selected”: <ul class="toolbar"> <li><a href="sites.htm">Sites</a></li> <li><a href="topics.htm" class="selected">Topics</a></li> </ul>
Weneedtogeneratethistextdifferentlyoneachpageandthegeneratedtextdependsonthefilewearerendering.Thepathindicateswhichpageweareon–sowhenweareontheTopicspage,thepathis“/topics.htm”.Wemakeasmallchangetothecontrollertopassinthecurrentpathintotherenderprocessasfollows:def get(self): path = self.request.path try: temp = os.path.join(os.path.dirname(__file__), 'templates' + path) outstr = template.render(temp, { 'path': path }) self.response.out.write(outstr) except: temp = os.path.join(os.path.dirname(__file__), 'templates/index.htm') outstr = template.render(temp, { 'path': path }) self.response.out.write(outstr)
Withthischange,thetemplatehasaccesstothecurrentpathfortherequest.Wethenmakethefollowingchangetothetemplate: <ul class="toolbar"> <li><a href="sites.htm" {% ifequal path '/sites.htm' %} class="selected" {% endifequal %} >Sites</a></li> <li><a href="topics.htm" {% ifequal path '/topics.htm' %} class="selected"
{% endifequal %} >Topics</a></li> </ul>
Thisinitiallylooksabitcomplicated.Atahighlevel,allitisdoingisaddingthetextclass=”selected”totheanchor(</a>)tagwhenthecurrentpathmatches“/topic.htm”or“/sites.htm”respectively.Wearetakingadvantageofthefactthatwhitespaceandend‐linesdonotmatterinHTML.Thegeneratedcodewilllookoneofthefollowingtwoways: <li><a href="topics.htm" class="selected" >Topics</a></li>
or <li><a href="topics.htm" >Topics</a></li>
Whileitlooksalittlechoppy–itisvalidHTMLandourclass=”selected”appearswhenappropriate.Lookingatthecodeinthetemplate,wecanexaminetheifequaltemplatedirective: {% ifequal path '/topics.htm' %} class="selected" {% endifequal %} Theifequaldirectivecomparesthecontentsofthepathvariablewiththestring“/topics.htm”andconditionallyincludestheclass=”select”inthegeneratedoutput.ThecombinationofthetwoifequaldirectivesmeansthatthelinksgiveustheproperlygeneratednavigationHTMLbasedonwhichpageisbeinggenerated.Thisisquitenicebecausenowtheentirenavigationcanbeincludedinthe_base.htmfile,makingthepagetemplatesverycleanandsimple:
{% extends "_base.htm" %} {% block bodycontent %} <h1>Application Engine: About</h1> <p> Welcome to the site dedicated to learning the Google Application Engine. We hope you find www.appenginelearn.com useful. </p> {% endblock %}
Thisapproachmakesitverysimpletoaddanewpageormakeachangeacrossallpages.Ingeneralwhenwecanavoidrepeatingthesamecodeoverandover,ourcodeiseasiertomaintainandmodify.MoreonTemplatesThisonlyscratchedthesurfaceofthetemplatedirectives.TheGoogleTemplateEnginecomesfromtheDjangoproject(www.django.org).Youcanreadmoreaboutthetemplatelanguagefeaturesat:http://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs
ThismaterialsisCopyrightCreativeCommonsAttribution2.5–CharlesSeveranceCommentsandquestionstocsev@umich.eduwww.dr‐chuck.com