AppEngine 06 Templates
-
Upload
ferdiansby -
Category
Documents
-
view
234 -
download
0
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