Google Application Engine Templates for HTML University of...

14
Google Application Engine Templates for HTML University of Michigan – Informatics Charles Severance While it is possible to generate all of the HMTL of your application from within strings in the Python, this is generally a poor way to author HTML. In particular, it means that every time you want to change a bit of the generated HTML you need to dig through the program, find the strings and then change the HTML code: 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>""" At the same time, our web application needs to have some parts of the web pages be generated dynamically as part of the code of the web application – either based on the user’s input or based on some information retrieved from the database. The compromise for this is to introduce the notion of a “template”. A template is a file that contains mostly HTML – but there are specially marked areas of the template that is replaced by data handed to the template from the Python code when the template is to be rendered. There are many different templating languages and syntaxes. The default template syntax used by the Google Application Engine is from the Django project. Template Syntax The template syntax in Google Application augments the HTML using curly braces to identify template directives. The template for our dumper program: <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>

Transcript of Google Application Engine Templates for HTML University of...

Page 1: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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>

Page 2: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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="/"

Page 3: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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'

Page 4: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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.

Page 5: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan
Page 6: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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

Page 7: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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

Page 8: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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

Page 9: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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.

Page 10: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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>

Page 11: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

<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.

Page 12: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

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"

Page 13: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

{% 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 %}

Page 14: Google Application Engine Templates for HTML University of …csev/courses/appengine/handouts/... · 2008. 11. 8. · Google Application Engine Templates for HTML University of Michigan

Thisapproachmakesitverysimpletoaddanewpageormakeachangeacrossallpages.Ingeneralwhenwecanavoidrepeatingthesamecodeoverandover,ourcodeiseasiertomaintainandmodify.MoreonTemplatesThisonlyscratchedthesurfaceofthetemplatedirectives.TheGoogleTemplateEnginecomesfromtheDjangoproject(www.django.org).Youcanreadmoreaboutthetemplatelanguagefeaturesat:http://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs

ThismaterialsisCopyrightCreativeCommonsAttribution2.5–[email protected]‐chuck.com