Vous êtes sur la page 1sur 28

4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?

tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

App Engine >

Python App Engine Codelab


Maintained by Google Developer Relations
Aug 2013

Objective/Description:
This codelab will demonstrate how to build applications using the Python version of Google App Engine. It is broken
down into three sections. After completing this codelab, you should be able to create (and deploy) applications on
Google App Engine using Python.

Part 1 Focuses on the basics which is to build a simple guestbook application. Incidentally, this application is based on
the existing "Getting Started" online tutorial in the official docs, but it also extends upon it a bit as well as offers more
detailed explanations.

Part 2 Focuses on providing a set of examples of how to use the various App Engine services such as email, XMPP.

Part 3 Shows how to use URLFetch, Cron, Task Queues and the Channel API to push browser updates from the
server. This is a standalone lab and can be run independently from the first two labs.

Requirements:
You have (at least) two options to do this codelab:

Option 1 Download SDK & Develop Locally (preferred; deploy option)

A laptop/notebook computer... you can share with others if you wish


Python 2.7.x (also see the separate mostly-equivalent Java codelab)
Python should already be available on Mac & Linux (or other POSIX) systems...
only PC users need to download & install Python: http://python.org/download
App Engine SDK: http://developers.google.com/appengine/downloads
Development environment: an IDE or a text editor and command-shell pair
Valid Google Account (deploy only)
Working mobile phone with SMS/text messaging enabled (deploy only)
Valid email address (optional)
Jabber/XMPP instant messaging client (optional)

Option 2 Web Browser & Internet Access (no deploy option)

A laptop/notebook computer... you can share with others if you wish


Internet access
Go to http://cloud-playground.appspot.com

Introduction

This codelab is based on the online tutorial found


at http://developers.google.com/appengine/docs/python/gettingstarted. That "Hello World" tutorial for Google App
Engine involves the creation and deployment of a web-based "guest book" application. If you follow the tutorial, you'll
see "Run/Modify" buttons on the code samples -- clicking on those buttons will drop you into the Cloud Playground with
that snippet of code for you to experiment with. If desired, you can do that tutorial first before coming here. This codelab
takes that existing tutorial and expands on it by offering more of an in-depth explanation and step-by-step process.

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Alternatives

If jumping directly into code like we do here is too overwhelming, and you wish to get more background and move more
slowly, go to the Google Developers Academy where you can take the intro to cloud computing & App Engine class first
then do that Python 101 class afterwards. Both the tutorial and Academy classes will get you about halfway through
part 1 of this codelab.

Notes

The web examples use the Jinja2 Django-flavored templating system.


There are older examples found at:
http://code.google.com/p/google-app-engine-samples
Be aware that this code is slightly older... it should still run however.
For data storage, the Python example uses the NoSQL Datastore API
For Win32 & MacOS platforms, a Google App Engine Launcher UI is available.
It comes with the .msi and .dmg downloads, respectively.

Deploying (optional but STRONGLY ENCOURAGED; option 1 only)

To deploy your app to the Internet, you need a Google Account


Go to http://appengine.google.com and register
You will need a mobile phone with text messaging enabled
Totally optional... can just use SDK development server
Allowed to create up to 10 applications to run live in production
Apps default to http://APPNAME.appspot.com domain names
For more details, check out http://sites.google.com/site/gdevelopercodelabs/app-engine/creating-your-app-
engine-account

Part I: Getting Started

Option 1: Setup & the Launchers


This “preliminary” lab is all about getting things setup and working. We will be using the development server that comes
with the SDK so Internet connectivity is not necessary (unless you need to download the SDKs and related software such
as Eclipse). By the end of this short “lab,” you should be able to create a new project, run the development server on the
auto-generated application “stub” and be able to visit the app at http://localhost:8080 (or whatever port you started up your
app on).

The specific tasks to accomplish here:

Get/install Python if necessary


Get/install the App Engine SDK
Decide whether to use the Launcher UI (or not)
Decide whether you will be on the cmd-line or use an IDE
Create the application handler file ( main.py )
Create the application configuration file ( app.yaml )
Learn how to start the SDK development server

If you bring up the launcher and create a new application, it will create your files automatically and give you 1-click ability
to start the server. They will look like the following on Win32 and Mac OS X systems:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Figure 1a. Google App Engine Launcher on PCs

Figure 1b. Google App Engine Launcher on Mac OS X

Regarding application names, you can choose whatever you want for development, but be aware that you will need to
rename it to deploy live. Pick something appropriate and unique. We suggest call it “helloworld” – keep it all lowercase;
users have had problems when using CamelCasing their app names.

Option 2: Cloning the Hello World Project


If you're using Cloud Playground, go to the site then copy the Hello World project (circled in the image [Figure 2a] below).

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Once you've cloned it, it shows up in your set of projects. Select that you wish to work on it by clicking on the blue arrow
button to the left (Figure 2b).

You'll then bring up the project and the app.yaml config file, as you can see here in Figure 2c:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

You can also select the main application file main.py too (FIgure 2d):

You'll be learning about these 2 files in the next section.

By the way, we mentioned there are more than 2 options to proceeding with the codelab. If you're not on a PC or Mac and
doing Option 1 and not using Option 2's Cloud Playground, you may be using a Linux or some other system purely from
the command-line (no UIs at all). In this case, you'll need to enter the app code by hand. It’s not that bad, as you'll see in
the upcoming section.

Exploring the Code


Let's explore the main.py code first. It should look something like the below -- BTW, code generated by the Launcher will
also feature a bunch of comments at the top with Google copyright notice -- they don't play a part in the codelab so we'll
just leave them out. The code you get from the Launcher or the Cloud Playground should look something like:

#!/usr/bin/env python
import webapp2
class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.write('Hello world!')
app = webapp2.WSGIApplication([
('/', MainHandler)
], debug=True)

Here is what the relevant pieces of code do:

Requests to / are handled by the MainHandler class, specifically its get() method
Configuration is done via the app.yaml file
main() creates a web application object and run_wsgi_app() executes the server loop

Now that you have an application handler file, you’re ready to go... that is, if using the Launcher or Cloud Playground,
you'll get this in the body of the app.yaml configuration file (or enter by hand if using neither):

1 application: helloworld
2 version: 1
3 runtime: python27
4 api_version: 1
5
6 handlers:
7 - url: .*
8 script: main.app

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Option 1: Once you have both of these files, then you’re ready to run the server. It’s as easy as one click in the launcher. If
you are on the command-line, you need to run the server manually:

$ dev_appserver.py helloworld

Now you should be able to hit http://localhost:8080 (the port may be diffierent if using the Launcher, in which case it's
easier to just click the Run button in the UI) and see the expected output in your browser:

Figure 3a. “Hello World!” in plain text

Option 2: Those using Cloud Playground do less work as there's already a server running. Just click the run button in
the web UI to see the exact same output (in the output window at the bottom), as shown here in Figure 3b:

Figure 3b. “Hello World!” in plain text from the Cloud Playground UI

Please get this working as you need to complete each section of the lab in order to move to the next.

Before moving onto the next part of the lab, make a few final tweaks: the first is to add a comma after the "('/',
MainHandler)" 2-tuple to avoid any issues when adding new handlers.

Also, you want to test out the dynamic nature of the development server: enclose "Hello World!" in <h1> tags so as to
output HTML instead of plain text. Once you save this change, you shouldn't have to restart the server before hitting
your browser and seeing the change immediately:

Figure 4a. "Hello World!" in HTML (H1 header tag)

Optionally, you can also provide the proper MIME header as part of your response... in our case, we could
add: self.response.headers['Content-Type'] = 'text/html' as the first line of the handler’s get() method. Those of
you doing Option 2 have probably figured out that you can change the code directly in the Cloud Playground, hit the run
button and see the changes reflected immediately, like what you see below in Figure 4b.

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Figure 4b. "Hello World!" with added H1 header tag in the Cloud Playground

For the remainder of the codelab, we'll just show the necessary bits for Option 1. Option 2 users can mimic the changes in
the code for the same effect as you can pretty much complete all of Part 1 using the Cloud Playground.

Adding web forms


Use triple quotes to add input HTML form with text field
Form action is /sign which does not exist yet... 404 error

In this part of the codelab, we’re going to give your users the ability to input data. Rather than simply displaying “Hello
World,” let’s create a simple form with one text field and allow the user to submit it to your app. Part of the form is defining
the “action” for it, which is the URL that the browser will POST the form variables to. In this case, it’s /sign . However, since
we haven’t defined a handler for it, this request – when a user clicks on the “Sign Guestbook” button – will generate a 404
error, which you will either see in your browser, or it will be blank... it depends complete on your browser.

Leave the “Hello World” line as-is and add the form via another call to self.response.out.write() . Python’s triple
quotes (3 single or 3 double quotes) allows users to embed special characters like NEWLINEs directly into a string
verbatim. What does this mean for users? No more long string wrapping, awkward backslashes, or ugly string
concatentation. You can cut-n-paste blocks of HTML directly into the source. The indentation of the string data does not
matter, however, we indent it in a manner consistent with the Python code around it if nothing but for readbility.

To make this happen, add another call right below the "Hello World" output line:

self.response.write('''
<form action="/sign" method=post>
<input type=text name=content>
<input type=submit value="Sign Guestbook">
</form>
''')

You should now see the text field form and submit button now:

Figure 5. Adding a web form

Again, there is no handler for guestbook signing yet, so you will get a 404 error or a blank screen if you click on the button!

Similar to the previous example, the changes here are so trivial that we do not need to show you the entire main.py file.

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Accept form input & display to user


Create handler for /sign : GuestBook.post()
Show output: user input

Obviously, the next step is to be able to process the user input. We will create a new handler to handle the /sign URL that
we created in the previous section of the codelab. We’ll call our new handler class GuestBook , although you’re free to use
any name you like. Forms use the HTTP POST method, so that’s why we’re creating a post() method (insetad of get() ).

Outside of a minor <br> tag added to the original form, you’ll be doing 2 things in this lab:

Adding the GuestBook class – watch the spelling... you don’t need to use CamelCasing as we’ve
done here but it should be consistent – and its post() method which simply outputs the user input.
Adding another 2-tuple when creating the application to define the handler ( GuestBook )
for /sign URLs

import webapp2

class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.write('<h1>Hello world!</h1>')
self.response.write('''
<form action="/sign" method=post>
<input type=text name=content>
<br><input type=submit value="Sign Guestbook">
</form>
''')

class GuestBook(webapp2.RequestHandler):
def post(self):
self.response.write(
'<h2>You wrote:</h2> %s' % self.request.get('content')
)

app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

As you can see from the image below, the extra “<br>” caused the button to go below the form field.

Figure 6. Filling out a web form

When you submit the form, the “/sign” handler will display the output back to you:

Figure 7. Filling out a web form

One word of caution is that the app (at the moment) accepts all text entered by the user, including characters that make up
HTML tags, i.e., <, >, etc. If you want to escape these characters to avoid “injection” attacks, import the escape() function
from the Python standard library cgi module and wrap the content in it, i.e., from cgi import escape followed elsewhere
in your code with :
self.response.out.write('<h2>You wrote:</h2> %s' % escape(self.request.get('content')) .

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Datastore: Saving app data to the cloud


Form data changed to textarea

Create and use new data model class

GET / -> Show output -> fetch & display stored entries

POST /sign –> Store user input in datastore -> redirect to /

Uses Query interface to retrieve all objects in datastore


Commented out line uses equivalent GqlQuery interface

Web applications wouldn’t nearly be as exciting if they didn’t store any data right? The App Engine datastore is arguably
the most complex component you will need to learn to become proficient at App Engine, but it is also the single thing that’s
the most difficult to implement if you were doing it yourself: creating a reliable, distributed, scalable, persistent data storage
mechanism. This is not a relational database with rows and tables... you cannot and should not think of it in this way. It is
more like an “object database” with “objects” or “entities.”

In this lab, we will store user input as the first major step at creating our online guestbook.

The home page behavior will also change... instead of just presenting a form for user input, we will show the previous
guestbook entries first as well as tweak the HTML a bit. So here are the changes that need to be made:

Demote “Hello World” by removing the header tag but leave it intact otherwise (more later)
Replace title with one that reads, “My GuestBook” – also add an <ol> tag to enumerate posts
Fetch all previous entries by calling the data model’s all() method
Loop through each entry and display to user
Terminate the <ol> list and change the text field to a multiline textarea

Once all those changes have been made, main.py should now look like:

from google.appengine.ext import db


import webapp2

class Greeting(db.Model):
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp2.RequestHandler):
def get(self):
self.response.write('Hello world!')
self.response.write('<h1>My GuestBook</h1><ol>')
#greetings = db.GqlQuery("SELECT * FROM Greeting")
greetings = Greeting.all()
for greeting in greetings:
self.response.write('<li> %s' % greeting.content)
self.response.write('''
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form>
''')

class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')

app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

Those of you who have a sharp eye will notice that one of the lines in get() is commented out. If you’re coming from a
relational world, you may feel more at home with App Engine’s GqlQuery interface, letting you use a subset of SQL to
issue the same request. You can use that line instead of the query used on line 18 but we don’t recommend that as you
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

need to start getting used to object queries anyway, and now is as good a time as any.

As mentioned above, the home page behavior is now different, and you should see those changes reflected below:

Figure 8. New form and saved data

Figure 9. Form submission redirects to (same) home page

User authentication
Add author field to schema

Authentication with Google Accounts

Show user name if logged in or sign-in link if not

Using the Users service (users API)


get_current_user() returns None if not logged in
create_login_url() returns valid login page link
nickname() method returns logged-in user’s name

At some point, application developers realize it’s a good thing to have unique users rather than treating everyone the
same. Google provides its own user authentication services as well as supports alternatives such as OAuth and
OpenID. The changes for this section of the lab are less intensive than the previous section.

Our example will use Google’s authentication service, which involves importing google.appengine.api.users (see line
4 above). The get_current_user() function is used to get the login of the current user. We need this in several places,
the first of which is the home page – we want to greet the currently-logged in user (lines 16-23). This call is also required
when a user submits an entry (lines 31-33) because this is the only time you can save this fact, and it’s pretty useful
information to differentiate who made which posts.

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

More specifically, the changes you need to make are:

Add import of users


Change “Hello World” to greet user ( nickname() )
Create a link if user not logged in ( create_login_url() )
Save the logged in user in the data model

Along with the other changes, your updated main.py will now look like:

from google.appengine.api import users


from google.appengine.ext import db
import webaapp2

class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
self.response.write('Hello %s' % user.nickname())
else:
self.response.write(
'Hello World! [<a href=%s>sign in</a>]' % \
users.create_login_url(self.request.uri)
)
self.response.write('<h1>My GuestBook</h1><ol>')
#greetings = db.GqlQuery("SELECT * FROM Greeting")
greetings = Greeting.all()
for greeting in greetings:
self.response.write('<li> %s' % greeting.content)
self.response.write('''
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form>
''')

class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

Your app should function before as expected except for a couple of things... the first thing users will now notice is a sign-in
link at the top:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Figure 10. New login link at the top

When users click to login, they should see a login prompt similar to this:

Figure 11. User login prompt

After logging in, they’re redirected back to the home page, which now shows user info:

Figure 12. App knows user is logged in

Force user logins

Require users to login before accessing site


Show sign-in and sign-out links as appropriate
Customizing queries

This section of the codelab features a couple of minor yet interesting updates to the application. We’re requiring users be
logged in before being able to access the home page. Also, let’s change the query a bit... because a guestbook is a time-

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

sensitive application, let’s sort the entries to newest-first (reverse chronological) order.

The changes you’re making here include:

Last time we added a login link; it’s only fair to add one for logout!
Redirect to the login link if not logged in
Sort by reverse order on the date and only show the ten newest posts

The “trickiest” code snippet is the customization of the query. We provide (onlines 18-21), both the change using the object
query as well as the GqlQuery so you can see the difference. Here is the resulting main.py :

from google.appengine.api import users


from google.appengine.ext import db
import webapp2

class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
self.response.write('Hello %s! [<a href=%s>sign out</a>]' % \
(user.nickname(), users.create_logout_url(self.request.uri)
))
else:
self.redirect(users.create_login_url(self.request.uri))
return
self.response.write('<h1>My GuestBook</h1><ol>')
#greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")
greetings = Greeting.all().order('-date').fetch(10)
for greeting in greetings:
self.response.write('<li> %s' % greeting.content)
self.response.write('''
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form>
''')

class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')

app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

As far as behavior changes go, users will now see a logout link at the top:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Figure 13. New logout link at the top

There won’t be a login link any more because users who aren’t are automatically sent to the login
screen seen earlier in the previous codelab section. You can confirm this by just clicking on the
“sign out” link.

Web templates
Add templating to simplify display
Move all HTML out of code into template (index.html)
Update all variable access for template
Create template context dictionary
Render template w/filename & context
() not necessary for calling functions/methods

Show posts with their authors


It would have been much uglier to put this logic in code

As your application grows both in terms of size, scope, and audience, the need for a visual designer increases. You wish
to have the UI be designed by... designers yet want the engineers to stay focused on getting the backend working. This is
where web templates come in. In this lab section, we move all of the HTML out into a web template.

App Engine supports a variety of templating systems, but those are separate downloads, and you would need to bundle
those with your app when you deploy live. However, the one from Django comes with App Engine and is available in both
the SDK as well as in production.

If you’re not familiar with templating systems, they are usually HTML combined with some sense of logic such
as if conditional statements and looping mechanisms like a traditional programming for loops. Django’s is no exception. In
order to pass the data from the application to the templating system, you need to create what is called a context, meaning
the variable namespace for the template itself. You'll see in the new code below and see how all of the HTML
(via self.response.out.write() calls) has been removed along with the relevant loops and conditionals. To summarize,
for this section of the codelab, the exact steps to take are:

Add a couple of new import statements


Remove the HTML output
Create a template context
Locate the template
Render the template, passing in its context

Below is the (new) template file, which you can call index.html, that contains most of what was removed... we will add
anything else back later:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

<html><body>Hello {{ user.nickname }}!


<h1>My GuestBook</h1><ol>
{% for greeting in greetings %}
<li> {{ greeting.content|escape }}
{% endfor %}
</ol><hr>
<form action="/sign" method=post>
<textarea name=content rows=3 cols=60></textarea>
<br><input type=submit value="Sign Guestbook">
</form></body></html>

Although the syntax may be foreign to you newbies, you should get the gist of how the template works as well as the
syntax of the Django template flow control directives (called Django tags) which is somewhat Python-flavored. You should
also observe that callables in Django’s templating system do not need to be called (absence of calling parentheses: () )...
they use the pipe ( | ) symbol and are meant to be similar to Unix&reg; pipes. Such callables are known as
Django filters and called automatically. You can learn more at the Django Built-in template tags and filters page as well as
the general Django template language page.

The remaining handler code after performing the steps outlined above looks like this:

from os import path


from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext.webapp.template import render
import webapp2
class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)
class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
greetings = Greeting.all().order('-date').fetch(10)
context = {
'user': user,
'greetings': greetings,
}
tmpl = path.join(path.dirname(__file__), 'index.html')
self.response.out.write(render(tmpl, context))
class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

Static file support


Add login and logout links to template

Static files...

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Add stylesheet (static/css/main.css)


Move template (static/html/index.html)
Add new entry to app.yaml for handling

Add timestamp to posts/entries

Update app.yaml to support /static/css requests

In the previous section, we started to logically segregate the files which are the responsibility of the engineer as well as the
visual designer. In this section, we take it one step further: static file support.

The user experience/interaction designer will want to do more than just creating web templates. Templates are just one
example of static files which are used to render web pages. Other static files include Cascading Style Sheets (CSS files)
and images, both of which can be used to give all the pages of a website a common look-and-feel.

To keep things organized, we create a top-level static directory containing subdirectories for CSS, HTML, and image
files. For our example, we’ll only do the first two. The template we created in the previous section is moved to
the static/html subdirectory while a brand new CSS file debuts in static/css . Here is what you need to do in this
section of the codelab:

Add back login & logout links


Move template to static/html

In fact, the only changes you need to make in the handler code are:

Changed our minds about requiring user logins


Add "static/html" in front of the page to index.html
Add the missing context for the login and logout links

from os import path


from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext.webapp.template import render
import webapp2
class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
greetings = Greeting.all().order('-date').fetch(10)
context = {
'user': user,
'greetings': greetings,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
tmpl = path.join(path.dirname(__file__), 'static/html/index.html')
self.response.write(render(tmpl, context))

class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')
app = webapp2.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

For the template, you can see below that we’ve added back the login and logout links, hence the reason why we required
the additional context variables above. There is also a reference to our new CSS file, main.css , as well. The other big

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

change is that instead of just enumerating and displaying the guestbook entries, we prepend each post with a timestamp
of when the post was made as well as the author (if known, or “anonymous” if not). Our guestbook is starting to look a little
more “professional” now!

<html><head>
<link type="text/css" rel="stylesheet" href="/static/css/main.css" />
</head>
<body>Hello
{% if user %}
{{ user.nickname }}!
[<a href="{{ logout }}"><b>sign out</b></a>]
{% else %}
World!
[<a href="{{ login }}"><b>sign in</b></a>]
{% endif %}

<h2>Top 10 Most Recent Guestbook Entries</h2>

{% for greeting in greetings %}


<br>
<small>[<i>{{ greeting.date.ctime }}</i>]</small>
<b>
{% if greeting.author %}
<code>{{ greeting.author.nickname }}</code>
{% else %}
<i>anonymous</i>
{% endif %}
</b>
wrote:
{{ greeting.content|escape }}
{% endfor %}

<hr>
<form action="/sign" method="post">
<textarea name="content" rows="3" cols="60"></textarea>
<br><input type="submit" value="Sign Guestbook">
</form>
</body></html>

The shortest addition in this section of the codelab is the CSS file (main.css) itself. It sets the default font and
background color and can be seen here:

body {
font-family: Verdana, Helvetica, sans-serif;
background-color: #DDDDDD;
}

The web template does make a call to the webserver to retrieve the CSS file. Your application does not have a handler
for /static/css so it needs to be added. Here is our new app.yaml file:

application: helloworld
version: 1
runtime: python
api_version: 1
handlers:
- url: /static/css
static_dir: static/css
- url: .*
script: main.py

These two lines need to be placed on top of your original ones because the latter handles all requests (via its universal
regular expression ".*"). You want the /static/css match to come first so it won't be "caught" by the universal one.

Adding caching & Django template inheritance


Moved most of template into a base template (for reuse)

Added more CSS directives

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

Add caching of greetings (via memcache)


Requests reissued within 10s will not hit the datastore

Timeout is optional, allowing for persistence until eviction

Differs from online tutorial, which:


Caches entire HTML string
Shows memcache stats
Does not support templating
Uses GqlQuery instead of Query interface
http://code.google.com/appengine/docs/python/memcache/usingmemcache.html

It’s time for a double feature: in this codelab section, we’re going to add caching using the App Engine Memcache API as
well as introduce template inheritance.

Fetching lots of data from the datastore can be time-consuming. Applications generally see a marked increase in
performance by caching often used and accessed data. Memcache is merely a hash table in the cloud, and comes both
with a simple API as well as more intermediate features that you expect. In our application, we’re going to cache the set of
guestbook entries. Specifically, we will cache them for ten seconds – this is sufficiently long enough so that you can
observe browser refreshes hitting the memcache but isn’t long enough to let the data in the cache go stale.

Another goal of this section further addresses the “common look-and-feel” theme we alluded to earlier. It is desireable that
web pages have the same general overall look along with the same header and footer links. It makes no sense to put the
same HTML in multiple HTML files when they can all share a “boilerplate” original. In our simple app, this comprises just
the “Hello World” greeting at the top along with login or logout link and the “import” of the CSS file – these would be shared
by all templates which extend from the base.

The diffs are straightforward, so you need to do the following:

Import the Memcache API package


Code to check if we have a cache hit
If not in-cache, we need to fetch & cache data
New entries auto-flushes the (now) stale cache

The complete handler which includes the changes from the previous as well as this section:

from os import path


from google.appengine.api import memcache, users
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp.template import render
import webapp2

class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
greetings = memcache.get('greetings')
if not greetings:
greetings = Greeting.all().order('-date').fetch(10)
memcache.add("greetings", greetings, 10)
context = {
'user': user,
'greetings': greetings,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
tmpl = path.join(path.dirname(__file__), 'static/html/index.html')
self.response.write(render(tmpl, context))

class GuestBook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
user = users.get_current_user()
if user:
greeting.author = user
greeting.content = self.request.get('content')
greeting.put()
memcache.delete('greetings')
self.redirect('/')
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

app = webapp.WSGIApplication([
('/', MainHandler),
('/sign', GuestBook),
], debug=True)

Below is our new base template, base.html, which includes “Hello World” and the login or logout link plus the reference
to the CSS file:

<html><head>
<link type="text/css" rel="stylesheet" href="/static/css/main.css" />
</head>
<body>Hello
{% if user %}
{{ user.nickname }}!
[<a href="{{ logout }}"><b>sign out</b></a>]
{% else %}
World!
[<a href="{{ login }}"><b>sign in</b></a>]
{% endif %}

{% block content %}
{% endblock %}
</body></html>

In the base template above, you will see that there is room reserved for the actual content, e.g., in the Django block tag.
Below in the stripped page content file, index.html, we extend the base template and specify the content to drop into
the base template when rendered. Otherwise, it’s exactly the same as the template file from the previous codelab section:

{% extends "base.html" %}
{% block content %}
<h2>Top 10 Most Recent Guestbook Entries</h2>

{% for greeting in greetings %}


<br>
<small>[<i>{{ greeting.date.ctime }}</i>]</small>
<b>
{% if greeting.author %}
<code>{{ greeting.author.nickname }}</code>
{% else %}
<i>anonymous</i>
{% endif %}
</b>
wrote:
{{ greeting.content|escape }}
{% endfor %}

<hr>
<form action="/sign" method="post">
<textarea name="content" rows="3" cols="60"></textarea>
<br><input type="submit" value="Sign Guestbook">
</form>
{% endblock %}

The background color has been removed from and styles added for HTML header, anchor, and paragraph tags in our
main.css file:

body {
font-family: Verdana, Helvetica, sans-serif;
color: #bdd;
background: #255;
padding: 0 5em;
margin: 0
}
h1 {
padding: 2em 1em;
background: #577
}
h2 {
color: #add;
border-top: 1px dotted #fff;
margin-top: 2em
}
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

a {
color: #9ff
}
p {
margin: 1em 0
}

In a more realistic situation, you would have a UE/UI/UX graphic designer working on the CSS independently of the
developer, allowing various team members to work on tasks simultaneously. The only file they may both access would be
the web template.

Once you’ve made the modifications above and have a running app, be sure to make a new entry into your datastore.
Then refresh your page several times and visit the memcache viewer on the admin console to confirm you’re hitting the
cache as well as fetching and re(caching) after 10s have elapsed. You can also confirm the cache is flushed as-expected
when a new post is made because the redirect to the home page should show the new post right away.

Part 2: Using other App Engine Services: email, XMPP


Now that we have a basic guestbook running, there are some optional features we can add to our app with just a few
simple changes. App Engine provides a rich set of APIs to give you more flexibility in your applications. We will take a look
at:

Sending email (receiving email also supported)


Chat/IM/Jabber (XMPP)
Logging (coming soon)

Other App Engine APIs not covered in this codelab include:

Application statistics (AppStats)


Blobstore
Task queues/cron/deferred
URLfetch
Profiling
Capabilities
remote_api

Other things you can do outside of the App Engine APIs include:

Twitter
PubSubHubBub
Google Wave bot

Sending email
Sending email is quite easy with App Engine. There are necessary restrictions, but it can be as simple as this code
snippet:

from google.appengine.api import mail


import webapp2
class MainHandler(webapp2.RequestHandler):
def get(self):
mail.send_mail('xxx@APP-ID.appspotmail.com',
RECIP_EMAIL, 'test subject', 'test body')
self.response.out.write('email sent')
app = webapp2.WSGIApplication([
('/.*', MainHandler),
], debug=True)

The basic rules of sending email are:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

The sender address must be one of the following:


An administrator/developer of the app sending email
A Google Accounts user logged into your app
Any valid receiving address for your app ( xxx@APP-ID.appspotmail.com ) where “xxx”
can be anything & “APP-ID” is as above

The development server will only log messages and not really send them unless you:
Specify an SMTP server (with login, passwd, etc.) or
Enable Sendmail on your local machine
http://code.google.com/appengine/docs/python/tools/devserver.html#Using_Mail

Attachments allowed must be of preapproved MIME type


http://code.google.com/appengine/docs/python/mail/overview.html#Attachments

Email payloads count against quotas and have maximum limits


http://code.google.com/appengine/docs/python/mail/overview.html#Quotas_and_Limits

Be sure to substitute in the correct values for the sender and receive above before integrating into your application. What
is in the code snippet above will not compile.

App Engine also supports the receipt of email, but that is another topic for another time. Anyway, it should be fairly
straightforward to upgrade your guestbook by adding an email feature. Here are some ideas as to what you can do to your
app to enhance it with email:

The administrator when anyone makes a post


All previous posters when anyone makes a post
A receipt to a poster that their post was approved/published/stored

For our guestbook example, we'll do the first item in the list above:

Email the administrator for every new post (supporting anonymous posts)

Here is our modified code:

1 from os import path


2 from google.appengine.api import mail, memcache, users
3 from google.appengine.ext import db
4 from google.appengine.ext.webapp.template import render
5 import webapp2
6
7 class Greeting(db.Model):
8 author = db.UserProperty()
9 content = db.StringProperty(multiline=True)
10 date = db.DateTimeProperty(auto_now_add=True)
11
12 class MainHandler(webapp2.RequestHandler):
13 def get(self):
14 user = users.get_current_user()
15 greetings = memcache.get('greetings')
16 if not greetings:
17 greetings = Greeting.all().order('-date').fetch(10)
18 memcache.add("greetings", greetings, 10)
19 context = {
20 'user': user,
21 'greetings': greetings,
22 'login': users.create_login_url(self.request.uri),
23 'logout': users.create_logout_url(self.request.uri),
24 }
25 tmpl = path.join(path.dirname(__file__), 'static/html/index.html')
26 self.response.write(render(tmpl, context))
27
28 class GuestBook(webapp2.RequestHandler):
29 def post(self):
30 greeting = Greeting()
31 user = users.get_current_user()
32 if user:
33 greeting.author = user
34 name = user.nickname()
35 else:
36 name = 'anonymous'
37 greeting.content = self.request.get('content')

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
38 greeting.put()
39 memcache.delete('greetings')
40 mail.send_mail(
41 user and user.email() or 'postmaster@APP-ID.appspotmail.com', # from
42 'RECIP_EMAIL', # to
43 'GuestBook post from %s' % name, # subj
44 '%s wrote:\r\n\r\n"%s"' % (name, greeting.content), # body
45 )
46 self.redirect('/')
47
48 app = webapp.WSGIApplication([
49 ('/', MainHandler),
50 ('/sign', GuestBook),
51 ], debug=True)
52
53
54
55
56
57

The changes made to the previous iteration include:

(line 2) Importing the mail module


(lines 34-36) Need to send a “sender name” in email
(lines 40-45) Send actual email (need 4 valid fields)

Chat/XMPP/Instant Messaging
Another form of communication that people use these days is instant messaging. XMPP (Extensible Messaging and
Presence Protocol) is an open XML-based protocol (originally named Jabber) that enables this type of communication
between users (and chat robots)!

App Engine supports XMPP and has implemented 4 basic instant message (IM) operations:

Sending of instant messages


Receiving of instant messages
Chat invitations to potential chat partners
Status query (is a user online/available?)

Similar to email, activity is only logged via the development server. You really need to upload it live to experience this
functionality.

Sending IMs can be as simple as xmpp.send_message(jid, msg) where a “jid” is synonymous to a “Jabber ID” or the user
you’re trying to communicate with, and msg is a string representing the message itself. However, things aren’t always as
simple. You usually have to first check if a user is accepting your message, and if a user is online before sending an IM. In
order to do this, we define Subscriber class for tracking users' subscription/online status, and check is_friend and status
properties first.

Receiving of messages requires a separate handler, because when an IM comes in to your app, App Engine POSTS to
http://APP-ID.appspot.com/_ah/xmpp/message/chat/ , thus you have to handle those inbound requests. Likewise,
subscription notifications are sent to http://APP-ID.appspot.com/_ah/xmpp/subscription/SUBSCRIPTION_STATUS/ where
SUBSCRIPTION_STATUS is one of subscribe/subscribed/unsubscribe/unsubscribed, and online status
notifications are sent to http://APP-ID.appspot.com/_ah/xmpp/presence/ONLINE_STATUS where ONLINE_STATUS
is one of available/unavailable/probe(probe is for requesting user's online status, not a
notification). Please see more details at User Subscriptions and User Presence in our documentation.

In the application below, we’ve built both a sending and receiving of IMs. The sending occurs if a user is recognized as
being friend, and online; if the user is not a friend, an invitation to chat is sent instead, and if the user is a friend, but offline,
then nothing happens. On the receiving side, we simply capture the inbound message and immediately turn it around and
reply to the send that we received their message (and what it was).

import hashlib
from google.appengine.api import xmpp
from google.appengine.api import users
from google.appengine.ext import db
import webapp2

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
class Subscriber(db.Model):
address = db.StringProperty()
is_friend = db.BooleanProperty(default=False)
status = db.StringProperty(required=False)
@classmethod
def get_by_email(cls, email):
return cls.get_by_key_name(hashlib.sha1(email).hexdigest())

class MainHandler(webapp2.RequestHandler):
def get(self):
if users.get_current_user() is None:
self.redirect(users.create_login_url( self.request.uri))
return
user_address = users.get_current_user().email()
user = Subscriber.get_by_email(user_address)
if user and user.is_friend is True:
if user.status == "available":
xmpp.send_message(user_address, "test msg")
self.response.write("A message sent.")
else:
self.response.write("The user is offline.")
else:
xmpp.send_invite(user_address)
self.response.write("An invitation sent.")

class XMPPHandler(webapp2.RequestHandler):
def post(self):
msg = xmpp.Message(self.request.POST)
msg.reply("I got your msg: '%s'" % msg.body)

class XMPPPresenceHandler(webapp.RequestHandler):
def post(self, presence):
from_jid = self.request.get('from').split('/')[0]
def txn():
user = Subscriber.get_by_email(from_jid)
if user:
user.status = presence
return user.put()
else:
return Subscriber(key_name=hashlib.sha1( from_jid).hexdigest(),
address=from_jid, status=presence).put()
db.run_in_transaction(txn)
return

class XMPPSubscriptionHandler(webapp2.RequestHandler):
def post(self, subscription):
from_jid = self.request.get('from').split('/')[0]
is_friend = False
if subscription == "subscribe" or subscription == "subscribed":
is_friend = True
def txn():
user = Subscriber.get_by_email(from_jid)
if user:
user.is_friend = is_friend
return user.put()
else:
return Subscriber(key_name=hashlib.sha1(from_jid).hexdigest(),
address=from_jid, is_friend=is_friend).put()
db.run_in_transaction(txn)
return

app = webapp2.WSGIApplication([
('/_ah/xmpp/message/chat/', XMPPHandler),
('/_ah/xmpp/presence/(.*)/', XMPPPresenceHandler),
('/_ah/xmpp/subscription/(.*)/', XMPPSubscriptionHandler),
('/', MainHandler),
], debug=True)

Be sure to add inbound_services entries in your app.yaml file in order to activate those XMPP services. The new app.yaml
file will look like:

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

application: helloworld
version: 1
runtime: python27
api_version: 1
inbound_services:
- xmpp_message
- xmpp_presence
- xmpp_subscribe

handlers:
- url: /static/css
static_dir: static/css
- url: .*
script: main.py

All xmpp functions send_invite() , and send_message() also take a from_jid identifier. Similar to email, this can be
either APP-ID@appspot.com or xxx@APP-ID.appspotchat.com (yes, xxx can be anything and be sure to reread that
domain name!) If not provided to these functions, it defaults to APP-ID@appspot.com .

It’s also not inconceivable that we could add IM features to the guestbook application like the ones for email we suggested
above:

A simple “ping” could be sent to the guestbook owner notifying of a new entry
A user could add an entry to the guestbook by sending an IM to the “guestbook” app/user

Instant messaging is a popular and useful form of communication like email is, but unlike email, there is more of a factor of
instant gratification.

One good example of extending the simple XMPP app above as well as our GuestBook application is to add a “chatbot”
that takes a few simple commands, one of which returns the top 5 most recent guestbook entries:

1 from os import path


2 from google.appengine.api import mail, memcache, users, xmpp
3 from google.appengine.ext import db
4 from google.appengine.ext.webapp.template import render
5 import webapp2
6
7 class Greeting(db.Model):
8 author = db.UserProperty()
9 content = db.StringProperty(multiline=True)
10 date = db.DateTimeProperty(auto_now_add=True)
11
12 def getGreetings(fetch=10):
13 greetings = memcache.get('greetings')
14 if not greetings:
15 greetings = Greeting.all().order('-date').fetch(fetch)
16 memcache.add("greetings", greetings, 10)
17 return greetings
18
19 class MainHandler(webapp2.RequestHandler):
20 def get(self):
21 user = users.get_current_user()
22 greetings = getGreetings()
23 context = {
24 'user': user,
25 'greetings': greetings,
26 'login': users.create_login_url(self.request.uri),
27 'logout': users.create_logout_url(self.request.uri),
28 }
29 tmpl = path.join(path.dirname(__file__), 'static/html/index.html')
30 self.response.write(render(tmpl, context))
31
32 class GuestBook(webapp2.RequestHandler):
33 def post(self):
34 greeting = Greeting()
35 user = users.get_current_user()
36 if user:
37 greeting.author = user
38 name = user.nickname()
39 else:
40 name = 'anonymous'
41 greeting.content = self.request.get('content')

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…
42 greeting.put()
43 memcache.delete('greetings')
44 mail.send_mail(
45 user and user.email() or 'postmaster@APP-ID.appspotmail.com', # from
46 'RECIP_EMAIL', # to
47 'GuestBook post from %s' % name, # subj
48 '%s wrote:\r\n\r\n"%s"' % (name, greeting.content), # body
49 )
50 self.redirect('/')
51
52 class GBChatBot(webapp2.RequestHandler):
53 def post(self):
54 message = xmpp.Message(self.request.POST)
55 cmd = message.body[0:5].lower()
56 if cmd == '/list':
57 greetings = getGreetings(5)
58 reply = '%s\r\n\r\n%s' % (
59 '5 Most Recent Guestbook entries:',
60 '\r\n'.join(['%s: %s' % (
61 '*%s*' % g.author.nickname() if g.author else '_anonymous_',
62 g.content[:40]) for g in greetings
63
64 ]))
65 elif cmd == '/help':
66 reply = '''
67 Guestbook Chatbot 0.2
68 Supported commands are:
69 */list* (5 most recent entries)
70 */help* (this help msg)'''
71 else:
72 reply = '''
73 Command "%s" not supported!
74 Send "/help" for command list.''' % message.body
75 message.reply(reply)
76
77 app = webapp2.WSGIApplication([
78 ('/', MainHandler),
79 ('/sign', GuestBook),
80 ('/_ah/xmpp/message/chat/', GBChatBot),
81 ], debug=True)
82
83
84
85
86
87

The most notable new additions to this version of main.py are the new GBChatBot class and the creation of a separate
function to get the greetings we need to return to the user (5 for the chatbot and 10 for the website). Here are a summary
of all the changes we had to make from the previous version:

(line 2) Importing the xmpp module


(lines 12-17, 22, 57) Need single routine to fetch data
(lines 52-75) New GBChatBot class
(line 80) New handler for inbound XMPP messages

NOTE: the application at this point will not accept instant messages yet. You need to be invited to be able to chat with the
bot. To make this happen, just run the first XMPP app (the simple one before we integrated it into our GuestBook app). If
you hit that app at ‘/’, you will receive an invite. We will leave an exercise to the reader to add an opt-in feature to the
guestbook app, perhaps when users create a guestbook entry. Once the invite is sent, you can go to your XMPP
application and accept the chat request from your app/bot; then you will be able to send commands to it.

Part 3: Using other App Engine Services: UrlFetch, Cron, Channel API, Task
Queues

This third part is a stand alone module which will show you how to use the services:

UrlFetch - to fetch data from another Internet source


Cron - to run recurring offline jobs
Channel API - to push updates to browser windows without needing to refresh (aka Comet)
Task Queues - To run a long offline batch process
https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

The complete application these exercise build to is an application that fetches a news feed and then pushes the titles,
headlines and descriptions to browsers via the Channel API.

Note: the complete zipped version of this application is available here.


(instructions on how to run the complete application are located at the end of this section)

Create boilerplate JavaScript code

You need to have the basic JS code:

Channel API:
<script type="text/javascript" src="/_ah/channel/jsapi"></script>

Getting the token from the server:


function getToken(){
var xhr = new XMLHttpRequest();
xhr.open('GET', '/xxx', false); // or whatever; calls TokenHandler.get() (see
below)
xhr.send(null);
return(xhr.responseText);
};

Open channel to server once token received


function openNewsChannel(token){
var channel = new goog.appengine.Channel(token);
var socket = channel.open();
socket.onopen = onOpened; // opt do it w/a handler (sample does)
};

To access the entire .html file used, just grab the file from the ZIP file above or jump over to the Java codelab momentarily.

Using URLFetch to retrieve an XML feed

This section will show how to extract content from an XML feed. In this example a news feed will be fetched and displayed.
A further exercise will fetch the feed and persist it in the datastore for later use.

The handler you should create -- we'll call ours FetchNewsHandler -- will fetch an XML news feed, parse the data, and
persist it in the datastore. Although it could be run manually, it is best suited to be run by a cron job as it will continually
refresh the datastore with the latest news feeds. These steps explain how to build this servlet.

create a NewsItem data model


create a handler to fetch news from an XML RSS feed

# feed item data model


class NewsItem(db.Model):
. . .

# fetches an XML RSS news feed and displays it


class FetchNewsHandler(webapp2.RequestHandler):
def get(self):
# delete old news items
. . .
# get, parse, and store feed items; we
# suggest xml.dom, xml.etree in stdlib or
# 3rd-party: BeautifulSoup, lxml, html5lib
. . .
# display stored feed items
# (for debugging; remove for prod)
. . .

Building a Servlet to create Channel API Tokens for client access

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

This handler will respond to a JavaScript client and create a new Channel API Client ID and along with a token which is
then returned back to the client so it can set up a communication channel.
(For further documentation on the Channel API and setting up a Client ID and Tokens,
see http://code.google.com/appengine/docs/python/channel/overview.html)

create TokenHandler
auto-generate a "client ID"
somehow persist the client ID by using the datastore so the server can keep track of all connections
create a channel using the API
return the token

class TokenHandler(webapp2.RequestHandler):
def get(self):
. . .

Building a Handler to Broadcast News headlines to clients

This handler, will broadcast the headlines that have been persisted in the datastore to any browser clients. It first queries
the current clients id (that have also been persisted in the datastore), and then queries the news items. It then loops
through each news item and broadcasts to all clients.

Although this servlet can be run manually, it is best to be launched as an offline process using a task Queue. A final step
will show to make this servlet launchable via a task queue.

Break this out into a function if you want to run it as a deferred task.

create a MessageHandler class


query all the NewsItems
broadcast to all clients by sending a msg to all the channels
(by looping through all the client IDs you saved earlier)

class MessageHandler(webapp2.RequestHandler):
def get(self):
. . .

Installing and running the complete Application

Installing and running the complete application can be done in order to see the entire set of services in this section working
together. These steps can be done even before doing the individual steps to build this application.

1. Download the sample application from the link above.


2. Unzip this file into a clean directory.
3. Start up dev_appserver or the launcher on it

Running the app.


(Important, upon first run, you must first seed the News feed database.)

1. In the console, you should that http://localhost:8080 (or whatever port you started the dev is now accessible.
2. Important before accessing the application at the root directory, you must first seed the datastore with news feeds.
This is done by executing the following "cron" job manually
1. From the main page in your browser, click on "Login to view news client".
2. (Important) If you are signed in, click on "sign out" link. Do not click on "proceed to news client"
link.
3. Once you've signed out, click on the "Sign in" link and when the login page appears, select the "Sign in as
Administrator" check box, then click "Log In" button.
4. After logging in as an administrator, manually launch the cron job to fetch news feed data by
accessing: http://localhost:8080/cron/fetchNews
This will fetch the current news feed data and persist it into the local datastore for further use. You should
also see the data fetched displayed in the browser window.
5. Optional: Go to the local SDK Console and verify that the data has been persisted.
http://localhost:8080/_ah/admin/datastore

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1
4/29/2018 https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F…

3. Now that the local datastore has been populated with new data, we can now access it from the application. Return
to the URL: http://localhost:8080
4. If you are not signed in, please sign in.
5. Upon verifying that you are signed in, click on the link "proceed to news client". (newsclient.html)
6. You should see the page "Breaking News Stories" as a header. You should also see browser Alert popup indicating
that you are now 'connected to the server'. - Click ok.
7. Now you can launch the Task that will start broadcasting out a series of news headlines to this browser. To launch
this task, open a new tab and access the url: http://localhost:8080/enqueueMsgs
This will launch a task queue that will start broadcasting messages to any clients that are currently logged in.
8. Now return to the new client page (newsclient.html) in the other tab.
9. You should now see news headlines and updates appearing in the browser window every 3 seconds!

The End

We hope that you’ve gotten a useful App Engine hands-on tutorial. There is plenty of documentation to read regarding the
existing online tutorial, and we have tried to add more specifics to the exercise of getting the Guestbook application
running.

https://sites.google.com/site/gdevelopercodelabs/app-engine/python-codelab?tmpl=%2Fsystem%2Fapp%2Ftemplates%2Fprint%2F&showPrintDialog=1

Vous aimerez peut-être aussi