22
Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent data with App Engine and Google's Datastore. This chapter continues with that discussion by stepping through a simplified Twitter application. 1. App Specification Create Twitter 0.1. The application should allow any user to enter tweets and should display the tweets of all users. For this version, we'll ignore who enters the tweets and user accounts altogether. 2. Database Design Our database needs are simple: we need a database class for storing tweets. Each tweet should include the status message and the date and time it was entered. In the Customer example of last chapter, all of the fields were of type StringProperty. There are, however, a number of property (field) types defined in the Google's Datastore API. For a list, see http://code.google.com/appengine/docs/python/datastore/ typesandpropertyclasses.html . For our Tweet class, we need one StringProperty for the status message, and a DateTimeProperty for the date. Here's the class, which we'll define in a file named model.py: from google.appengine.ext import db class Tweet(db.Model): statusMessage = db.StringProperty(required=True) submissionTime = db.DateTimeProperty(auto_now=True) Recall that defining such a db.Model class just sets up the structure of our persisent data, performing a function analagous to the SQL create function which creates a database table. Later, we'll create actual records for the table in response to the user submitting an HTML form.

Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

  • Upload
    others

  • View
    14

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

Chapter 19: Twitter in TwentyMinutes

In the last chapter, we learned how to create and query persistent data withApp Engine and Google's Datastore. This chapter continues with thatdiscussion by stepping through a simplified Twitter application.

1. App Specification

Create Twitter 0.1. The application should allow any user to enter tweetsand should display the tweets of all users. For this version, we'll ignore whoenters the tweets and user accounts altogether.

2. Database Design

Our database needs are simple: we need a database class for storing tweets.Each tweet should include the status message and the date and time it wasentered.

In the Customer example of last chapter, all of the fields were of typeStringProperty. There are, however, a number of property (field) typesdefined in the Google's Datastore API. For a list, seehttp://code.google.com/appengine/docs/python/datastore/typesandpropertyclasses.html.

For our Tweet class, we need one StringProperty for the status message,and a DateTimeProperty for the date. Here's the class, which we'll define in afile named model.py:

from google.appengine.ext import dbclass Tweet(db.Model):

statusMessage = db.StringProperty(required=True)submissionTime = db.DateTimeProperty(auto_now=True)

Recall that defining such a db.Model class just sets up the structure of ourpersisent data, performing a function analagous to the SQL create functionwhich creates a database table. Later, we'll create actual records for thetable in response to the user submitting an HTML form.

Page 2: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

The 'required=True' parameter of the statusMessage StringProperty specifiesthat any tweet records created must have a non-empty value in this field.The 'auto_now=True' parameter of the DateTimeProperty tells theDataStore to automatically set the date on each tweet record stored.

3. The HTML Template

We'll implement this version of Twitter with a single HTML template namedindex.html. The page needs a form for entering tweets, and a scripting codefor loop that will unfold and show all of the previous tweets. Here it is:

<!-- required: a template variable 'tweets' which is a list of objects --><!-- each of which has statusMessage and submissionTime fields. -->

<h2> What are you doing right now? </h2><form action="/tweetSubmit" method="get">

<input type="text" name="status" value="status"><input type="submit" value="Submit">

</form>

<br/><br/><h2> Previous Tweets{% for tweet in tweets %}

{{tweet.status}} <br/>{{tweet.submissionTime}}

{% endfor %}

The headers (h2s), breaks, and form are all standard HTML. The code withinthe double-curly brackets is scripting code using Django syntax, andspecifies the dynamic part of this page.

In this case, the dynamic part of the page is for displaying the previoustweets that have been entered. As set up, this page is expecting a list ofTweet records named tweets to be sent by the controllers that render thispage. It also expects that each of the tweet records in the list will have fieldsfor statusMessage and submissionTime. These requirements are specified atthe top of file within an HTML comment (<!-- ... -->).

4. The Config File

The config file for this application is as simple as possible:

application: dwtwitterversion: 1

Page 3: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

runtime: pythonapi_version: 1

handlers:- url: /.*

script: twitter_controller.py

The only 'custom' parts of it are the application name dwtwitter, which mustmatch the id we give the application when we register at appspot.com, andthe name of the controller file, twitter_controller.py, where all of ourcontroller code will be placed.

5. The Controller

Our controller code is in twitter_controller.py, as specified in the config file.We need two event-handlers for this application, one to load the main pageoriginally, and one that responds to the user entering a new tweet.

Our first task is to specify the mappings between URL requests and theevent-handlers we'll define. The two requests are "/" and "/tweetSubmit",the first pertaining to when the main page is loaded, the latter pertaining tothe action defined within our HTML form for entering a tweet. Here is themapping table we'll define near the bottom of the controller code:

application = webapp.WSGIApplication(

[('/', MainHandler),('/tweetSubmit',TweetSubmitHandler)

],debug=True)

With those mappings defined, we are ready to define the event-handlers forour application. First, the MainHandler which will be called when the userfirst visits the site. Remember, all the handlers need to provide the HTMLtemplate with a single template variable named tweets which is a list ofTweet objects. Here's the code:

class MainHandler(webapp.RequestHandler):def get(self):

tweetList=db.GqlQuery("Select * from Tweets")template_values={'tweets':tweetList}# render the page using the template enginepath = os.path.join(os.path.dirname(__file__),'index.html')

Page 4: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

self.response.out.write(template.render(path,template_values))

The MainHandler just performs a query to get all of the previously enteredTweets. This variable is then inserted into the template_values with a key of'tweets'. It is this key that must match the template variable in the HTMLtemplate:

{% for tweet in tweets %}{{tweet.status}} <br/>{{tweet.submissionTime}}

{% endfor %}

Note that the variable 'tweet' in the HTML template is a place holderrepresenting the currrent item in the list as it is traversed. So just 'tweets'and not 'tweet' correctly appears in the controller's template_values.

The second handler needed is the TweetSubmitHandler. This handler will becalled when the user clicks on the submit button of the form within theHTML:

<form action="/tweetSubmit" method="get"><input type="text" name="status" value="status"><input type="submit" value="Submit">

</form>

If the user entered 'just chilling' in the text box, the following URL would besent to the server:

nameofsite.com/tweetSubmit?status='just chilling'

The request is /tweetSubmit?status?'just chilling'

The first part, /tweetSubmit, just causes the TweetSubmitHandler to becalled due to our specification in the mapping table. When that handler isinvoked, it can get to the request parameter status using functionself.request.get.

Here's the code for the event-handler:

class TweetSubmitHandler(webapp.RequestHandler):def get(self):

status=self.request.get("status")twitter = Twitter()twitter.statusMessage=statustwitter.put()

Page 5: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

self.redirect('/')

First, the handler gets the parameter named 'status' from the formparameters. If the user entered 'just chilling', that will be the value of thevariable 'status'. It then creates a Twitter object, an instance of thedatabase class we created near the top of this chapter. It then sets the'statusMessage' field of the Twitter object to the variable status ('justchilling'). Finally, it calls 'put' to actually create the record in the database,and redirects to '/', which leads to the MainHandler being called again. Thathandler sets up our template_values with the previously entered tweets,including the one just entered.

Page 6: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

Chapter 20: Account Management

This chapter introduces the App Engine method for managing user accountsand user generated data in your applications. We continue with the Twitterexample, and describe how to restrict the site to only validated users, howto keep track of each user's account information, and how to show eachuser only her tweets.

Introduction

If you use tools like Google Docs or Sites, you've probably signed on using aform like the following:

With App Engine, you can easily create apps that manage user accounts inthe same manner as these Google tools, including requiring that your userslogin with Google accounts, and having them sign in using the form shownabove.

App Engine provides this functionality with:

• The ability to specify, in the app.yaml configuration file, the pages ofthe site that should be password protected.

• Library code that provides easy access to the Google accountinformation of your site visitors. You can use this information as is orwrite your own account objects that "wrap" the Google user objectsand store additional information about your users.

Page 7: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

Requiring Login

In the app.yaml file, you specify how an application is configured, includinghow each request should be processed. Along with specifying the controllerfile, you can also state that a user must be logged in to access a page:

handlers:

- url: /script: app_controller.py

- url: /.*script: app_controller.pylogin: required

The first specification states that requests for the home page of the site ('/')should be dispatched to the controller code in app_controller.py, and that nologin is required to visit that page. The second specifies that all other pagesshould also be dispatched to the same app_controller.py file, but that theuser will have to login before visiting those pages.

When a request arrives for login:required pages, the App Engine server firstchecks if the user is signed in with their Google account. If so, the controllercode is invoked as usual. If not, the user is sent to Google's sign-in dialog,the one shown at the top of this chapter. If the user successfully logs in, thecontroller code is then invoked.

Besides login:required, you can also specify pages that can only be accessedby site administrators with login:admin. Site administrators are themaintainers of the site, the ones behind the scenes who perform suchoperations as removing spam or blocking abusive users. For the programsyou are writing, you are the one and only site administrator.

The rules are the same as with the login:required pages, but App Engine willalso check to see if the user logging in is an administrator of your site. Youcan specify administrators for your site in the site's dashboard atappspot.com-- just choose Developers in the left panel of the dashboard andyou can send email invites for collaborators.

For more information about App Engine config files, seehttp://code.google.com/appengine/docs/python/config/appconfig.html

Page 8: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

Accessing User Information in the Controller

Once a user has logged in to the application you can access her information-- Google nickname and email-- using App Engine's users module.Specifically, you can call 'users.get_current_user' to access the currentlysigned in user. This function returns a user object, which has fields'nickname' and 'email'. Here's an example of controller code for accessingthe user's information and putting it in the template_values for display:

class SomeController(webapp.RequestHandler):def get(self):

user = users.get_current_user()nickname = user.nickname()template_values={'nickname':nickname}path = os.path.join(os.path.dirname(__file__),'somepage.html')self.response.out.write(template.render(path,template_values))

For further information about the users module, see http://code.google.com/appengine/docs/python/users/userclass.html.

Marking the Submitter in User-Generated Data

Web 2.0 applications like Facebook and Twitter store data for the user,including profile information, bookmarks, notes, and other data commonlyreferred to as user-generated.

For such data, the application must store a user id along with theinformation being stored. For instance, consider once again the Twitterapplication from the previous chapter, and specifically the Tweet databaseclass:

from google.appengine.ext import dbclass Tweet(db.Model):

statusMessage = db.StringProperty(required=True)submissionTime = db.DateTimeProperty(auto_now=True)

As written, the class records the time of submission, but it doesn't recordwho submitted the status message (the tweet).

Fortunately, App Engine provides a special class called a UserProperty forstoring such information:

from google.appengine.ext import dbclass Tweet(db.Model):

Page 9: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

statusMessage = db.StringProperty(required=True)submissionTime = db.DateTimeProperty(auto_now=True)submitter = db.UserProperty()

A UserProperty object can be set to the user that is returned from the libraryfunction 'users.get_current_user()'. App Engine doesn't actually store a copyof the user's information, just a key that can get us back to that informationlater.

The addition to the TweetSubmit handler is quite simple:

class TweetSubmitHandler(webapp.RequestHandler):def get(self):

user = users.get_current_user()status=self.request.get("status")twitter = Twitter()twitter.statusMessage=statustwitter.submitter = usertwitter.put()self.redirect('/')

Now, each time a tweet is submitted, it is marked with the user whosubmitted it. This is of course essential for being able to list only a user'stweets, which we didn't do in the original Twitter sample (we showed thetweets from all users).

Let's reconsider the MainHandler:

class MainHandler(webapp.RequestHandler):def get(self):

tweetList=db.GqlQuery("Select * from Tweets")template_values={'tweets':tweetList}# render the page using the template enginepath = os.path.join(os.path.dirname(__file__),'index.html')self.response.out.write(template.render(path,template_values))

The query in this version of the handler selects all tweets, independent ofwho submitted them. We can modify this with by making use of theusers.get_current_user() function and a Where clause in the query:

class MainHandler(webapp.RequestHandler):def get(self):

user = users.get_current_user()tweetList=db.GqlQuery("Select * from Tweets where

submitter=:1",user)

Page 10: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

template_values={'tweets':tweetList}# render the page using the template enginepath = os.path.join(os.path.dirname(__file__),'index.html')self.response.out.write(template.render(path,template_values))

When the query is made, it will only return tweets which have a sumbitterfield that matches the current user.

Custom User Accounts

The Google user account objects only provide fields for nickname and email,and don't allow you to gather custom profile information about users.

To track custom account information, you can "wrap" a Google UserPropertywithin a database class::

class Account(db.Model):first=db.StringProperty()last=db.StringProperty()user = db.UserProperty()

This class tracks a first and last name for each account, and links to theGoogle User account with the field user, which is defined as typeUserProperty and points at a built-in Google user object. Because theAccount class encloses Google user data along with additional data, it istermed a wrapper class.

To make use of the Account class, you'll need to perform some bookkeepingwhen the user first logs into the application with her Google Account. In theMainHandler event-handler, you can check if the current Google useraccount object is associated with one of the wrapper Account objects. If itnot, you just create a new Account instance in your event-handler, set it'sfields, and store it in the database.You can set the user property to the valueof 'users.current_user()' and, since you don't yet know the user's first andlast name, set those to the default value of the Google nickname:

# code within an event-handler

user = users.get_current_user()# does this user already have an account?account = db.Gql('Select * from Account where user = :1',user)if not account: # no account exists yet for this user

account= Account()account.user=useraccount.first=user.nickname() # or some other default value like ' '

Page 11: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

account.last=user.nickname()account.put()

# whether a new or existing account, send the user to profile pageself.redirect('/profile?'+user.key()) # send them to the profile page

With the call to 'redirect' in the sample, you can also immediately send theuser to a profile page that allows her to set her first and last name (we'llshow this later).

Recording the Account with the Tweet

Because you are now representing users with your Account objects insteadof Google users directly, you'll need to modify the Tweet class to referencean account instead of a user:

class Tweet (db.Model):status=db.StringProperty()date = db.DateProperty(auto=true)submitter = db.UserProperty()submitter = db.ReferenceProperty(Account)

You'll also need to modify the TweetSubmit controller:

class TweetSubmitHandler(webapp.RequestHandler):def get(self):

user = users.get_current_user()accountQuery = db.GqlQuery('Select * from Account where

user=:1',user)account = accountQuery.get()status=self.request.get("status")twitter = Twitter()twitter.statusMessage=statustwitter.submitter = accounttwitter.put()self.redirect('/')

Note that the db.GqlQuery returns a query object, and not the result of thequery. We can send a query directly in the template_values to a web pagetemplate to be processed, but sometimes we need to know the result(s)while still in the controller. In the above sample, this is the case as we wantto set the submitter field of the just submitted tweet to the account. So wecall get on the query, which returns the first, and in this case only, result.

With this change, each tweet will now point to the additional information inthe user's Account object. In our example, this additional information

Page 12: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

includes the first and last name. You could also include information such as aprofile image and thus show the user's picture with each tweet listing.

Showing User Information with Tweets

The tweetlist which our controller sends to the HTML template has all theinformation about the tweets, including a pointer to the submitterinformation. To show that information, we need only modify the HTMLtemplate. Here are the modifications:

<!-- required: a template variable 'tweets' which is a list of objects --><!-- each of which has statusMessage and submissionTime fields. -->

<h2> What are you doing right now? </h2><form action="/tweetSubmit" method="get">

<input type="text" name="status" value="status"><input type="submit" value="Submit">

</form><br/><br/><h2> Previous Tweets </h2>{% for tweet in tweets %}

{{tweet.status}} <br/>{{tweet.submissionTime}}&nbsp;by&nbsp{{tweet.account.lastName}}

{% endfor %}

&nbsp is HTML for a blank space. tweet.account gives us an account object,which has the field lastName.

Showing All Tweets or User Tweets

Suppose that you want to allow the user to see either just her tweets, ortweets from all users, and for the latter you want to show the name of thesubmitter along with the tweet. Suppose also that you want the web page toprovide links that let the user choose either my tweets or all tweets.

First, you'll need to modify the HTML template to add links for the twochoices. Here's the revised code:

<!-- required: a template variable 'tweets' which is a list of objects -->

<!-- each of which has statusMessage and submissionTime fields. -->

<h2> What are you doing right now? </h2>

Page 13: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

<form action="/tweetSubmit" method="get"><input type="text" name="status" value="status"><input type="submit" value="Submit">

</form>

<br/><br/><h2> Previous Tweets{% for tweet in tweets %}

{{tweet.status}} <br/>{{tweet.submissionTime}}&nbsp;by&nbsp{{tweet.account.lastName}}

{% endfor %}<br/><a href='/myTweets'>My Tweets</a>&nbsp<a href='/allTweets'>All Tweets</a>

Links are specified with anchor tags and here the 'href' attribute is set to arequest back to your server, not a full URL. The requests for the two links is'myTweets' and 'allTweets' respectively.

Second, you'll need to add handler mappings for '/myTweets' and'/allTweets' in the mapping table:

application = webapp.WSGIApplication([

('/', MainHandler),('/tweetSubmit',TweetSubmitHandler),('/myTweets', MainHandler),('/allTweets', AllTweetHandler)

],debug=True)

'/myTweets' is mapped to the MainHandler, since you've already modifiedthat to show just the user's tweets. The 'allTweets' request will be handledby a new handler, AllTweetHandler.

Here's the AllTweetHandler. It is the original MainHandler before we beganconsidering users, and it uses a query with no Where clause:

class AllTweetsHandler(webapp.RequestHandler):def get(self):

user = users.get_current_user()tweetList=db.GqlQuery("Select * from Tweets")template_values={'tweets':tweetList}# render the page using the template engine

Page 14: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

path = os.path.join(os.path.dirname(__file__),'index.html')self.response.out.write(template.render(path,template_values))

Adding Login and Logout Links to the App

Sites that require login will have at least one page, accessible to anyone,that let's users see what the site is about. Your application will need to leadusers from this page to Gogle's registration/login page. The users moduleprovides a "create_login_url" function to get the URL for a Google loginpage. It also provides a "create_logout_url" function to allow users to logout.You can call both from your controller with code like the following:

class OpenPageHandler(webapp.RequestHandler):def get(self):

user=users.get_current_user()if user:

self.redirect('/profile?user='+user.key())else:

login_url=users.create_login_url(self.request.uri)logout_url=users.create_logout_url(self.request.uri)template_values={'login_url':login_url, 'logout_url':logout_url}# render the page using the template enginepath = os.path.join(os.path.dirname(__file__),'openPage.html')self.response.out.write(template.render(path,template_values))

In this example, if the user is already logged in, get_current_user returns anentry and the user is redirected to another page-- the home page forreturning users of the system. If get_current_user returns Null, the currentvisitor is not a logged in user. In this case, create_login_url is called to getthe URL for the Google login page. When create_login_url is called, youprovide a parameter which is the URL that should be returned to once theuser does login. In the sample above, this return URL is set toself.request.uri, so control will be returned to this controller code (this timewith a non-NULL user entry).

After obtaining the login_url and logout_url, the code above sticks them intoa template_value. OpenPage.html can then display these links along with itsother introductory information. Here's a sample HTML file with the links:

<html><body>

<p> The purpose of this site is ...</p><a href={{login_url}}>Login</a><br/><a href={{logout_url}}>Login</a><br/>

Page 15: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

</body></html>

Note that there is also a 'create_logout_url' which, when clicked, logs a userout of the application. Both the login and logout_urls are often placed in theheader and/or footer of each page.

Profile Page

Let's now consider the development of a user profile page. Knowing how tocreate them is important as they are common to most web 2.0 apps. Theirdevelopment also is instructive in terms of some fundamental HTML anddatabase issues.

The profile page should display the user's information and allow them to editit:

To begin, we'll assume that each user can only view and edit their ownprofile page. Later, we'll consider a profile page that can be viewed byothers.

Let's begin with mapping the requests we'll need to controller event-handlers. We'll need a request->handler that loads the profile page initially,and another for handling things when the user clicks on the submit button toupdate the info. Here are the additions to the mapping table:

application = webapp.WSGIApplication([

('/', MainHandler),('/tweetSubmit',TweetSubmitHandler),('/myTweets', MainHandler),('/allTweets', AllTweetHandler)('/profile',ProfileHandler)('/ProfileSubmit',profileSubmitHandler)

],debug=True)

The ProfileHandler controller just needs to get the current user's accountobject and place it into the template values so the profile.html template can

Page 16: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

access it:

class ProfileHandler(webapp.RequestHandler):def get(self):

user=users.get_current_user()query = db.GqlQuery('Select * from Account where user=:1',user)account=query.get()template_values={'account':account}# render the page using the template enginepath = os.path.join(os.path.dirname(__file__),'profile.html')self.response.out.write(template.render(path,template_values))

Next, let's consider the HTML template profile.html. Because we want theuser to be able to view the existing account information, we'll set the valueattribute of each text box in our form to a field of the template value'account':

<!-- account required as a template value --!><form action="/profileSubmit" method="get">

First Name: <input type="text" name="first"value={{account.first}}><br/>

Last Name:<input type="text" name="last"value={{account.last}}><br/>

<input type="submit" value="Submit"></form>

The data put into the value attribute appears in the text box when the formis rendered. So the current values of account.first and account.last willappear, as "David" and "Wolber" appeared in the form rendering above.

When the user submits, we send the action '/profileSubmit' to the controller.Because of our mappings, profileSubmitHandler is called. The job of thiscontroller is to get the user's entries for the account fields and modify theaccount object in the database:

class ProfileSubmitHandler(webapp.RequestHandler):def get(self):

user=users.get_current_user()query = db.GqlQuery('Select * from Account where user=:1',user)account=query.get()firstName = self.request.get('first')lastName = self.request.get('last')# modify the account objectaccount.first=firstNameaccount.last=lastName

Page 17: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

account.put()self.redirect ('/profile')

We query for the account object using the current user information. We thencall self.requst.get to get the data from the input boxes. This data may bethe same as what's in the database if the user didn't modify it. But weassume that its changed, modify the fields of the account object with thedata from the form, and call put() to save the changes persistently in thedatabase.

Note that put() is used here as a database "update". In our previous code,put() was used to "insert" a new record in the database. The key differenceis that here, put() was called on an object that was the result of a query--an existing database record. In the tweet samples, we use the objectcreation statement:

tweet= Tweet()

to get a new recorde and then eventually call put to "insert" it into thedatabase.

A Public Profile Page

The template page profile.html above shows the current user herinformation. There is no way for user X to view the page of user Y.

Of course sometimes this is desired: for instance, if X is reading everyone'stweets and sees one from Y she likes, she may want to follow a link to viewmore information about Y. For this reason, most sites have some type ofpublic profile page.

A public profile page is different: it doesn't have a form, as no editing cantake place. It also must be parameterized with a user id so it knows whichuser to display.

With App Engine, every database class is given a field key which uniquelyidentifies each record. Even though the Account class has three fields listed:

class Account(db.Model):first=db.StringProperty()last=db.StringProperty()user = db.UserProperty()

the system takes care of providing it with another field, key, which is aunique identifier.

Page 18: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

This key can be used to send the publicProfile page a parameter thatuniquely identifies which user to display. For instance, when tweets arelisted, we can have the key of the submitter specified as a parameter to thepublicProfile page:

{% for tweet in tweets %}{{tweet.status}} <br/>{{tweet.submissionTime}}&nbsp;by &nbsp;<a href='publicProfile?key={{tweet.submitter.key}}'>

{{tweet.submitter.last}}</a>{% endfor %}<br/>

The anchor tag will show the submitter's last name and generate a requestsomething like:

publicProfile?key=734r78927423

when the link is clicked, the 734r78927423 being the key of the account whosubmitted that particular tweet.

The mapping table must be updated to handle the publicProfile request:

application = webapp.WSGIApplication([

('/', MainHandler),('/tweetSubmit',TweetSubmitHandler),('/myTweets', MainHandler),('/allTweets', AllTweetHandler)('/profile',ProfileHandler)('/ProfileSubmit',profileSubmitHandler)('/publicProfile',PublicProfileHandler)

],debug=True)

and the PublicProfileHandler will access the account object using the key:

class Public ProfileHandler(webapp.RequestHandler):def get(self):

user=users.get_current_user()query = db.GqlQuery('Select * from Account where user=:1',user)key = self.request.get('key')account=Account.get(key)template_values={'account':account}

Page 19: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

# render the page using the template enginepath = os.path.join(os.path.dirname(__file__),'publicProfile.html')self.response.out.write(template.render(path,template_values))

Here, self.request.get obtains the parameter key from the request URL. Eachdatabase class has a method get which accepts a key as a parameter. So

Account.get(key)

returns an account object with the given key.

Given that we don't want the account information edited, we use a differentHTML template file, publicProfile.html, to display the account information:

<!-- account required as a template value --!>First Name: {{account.first}}<br/>Last Name: {{account.last}}

Uploading Images

Images can be displayed on a web page with the <img> tag, e.g.,

<img src='/images/birds.jpg' />

That code is sufficient for rendering static images that always appear onyour pages. But what about images that are uploaded by a user? Suchuploading is the very purpose of photo-sharing sites like Flickr, and is alsocommon to Web 2.0 applications like Facebook which include pictures of allusers. To handle such uploading, your site needs a form for allowing theuser to choose a web image or one from their hard disk, and the uploadedfiles must be stored persistently on the server. Fortunately, App Engineprovides some help for performing these common tasks.

Storing ImagesFiles can be stored in an AppEngine Database using the BlobProperty, whichrepresents a bunch of bits. For instance, the following Account class stores aprofile picture:

class Account(db.Model):first=db.StringProperty()last=db.StringProperty()user = db.UserProperty()profile_pic = db.BlobProperty()

Page 20: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

HTML forms allow for inputs of type "file". Such an input type prompts theuser to choose a file with a dialog. For such forms, the enctype should be setto "multipart/form-data" and the method to "post", as the file might be largeand "get" has limits on the number of bytes that can appear in the request.The following example let's the user choose an image file for their profile(and enter a first and last name):

<!-- account required as a template value --!><form action="/profileSubmit" enctype="multipart/form-data"method="post">

First Name: <input type="text" name="first"value={{account.first}}><br/>

Last Name:<input type="text" name="last"value={{account.last}}><br/

Profile Picture: <input type="file" name="picfile"><br/><input type="submit" value="Submit">

</form>

The ProfileSubmitHandler must be modified to get the file name entered bythe user and create the Blob of the files bits:

class ProfileSubmitHandler(webapp.RequestHandler):def get(self):

user=users.get_current_user()query = db.GqlQuery('Select * from Account where user=:1',user)account=query.get()firstName = self.request.get('first')lastName = self.request.get('last')pic = self.request.get("picfile")# modify the account objectaccount.first=firstNameaccount.last=lastNameif pic:

account.profile_pic = db.Blob(pic)account.put()self.redirect ('/profile')

Displaying Stored Images

Images stored as blobs aren't loaded as static files, so you can't use theconfig file to catch requests for them and keep a controller event-handlerfrom being called. Instead, you'll need controller code to serve the picturesfrom the blobs stored in the database, and you'll need a special request for

Page 21: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

uploading images.

An event-hanlder like the following will suffice:

class ImageHandler (webapp.RequestHandler):def get(self):

key=self.request.get('key')account = db.get(key)if account.profile_pic:

self.response.headers['Content-Type'] = "image/png"self.response.out.write(account.profile_pic)

else:self.error(404)

and you'll need to add a mapping:

application = webapp.WSGIApplication(...('/loadImage',ImageHandler)

],debug=True)

The ImageHandler controller code expects the key of an account as aparameter. It uses that key to get the account object from the database.Note that the image we are showing might or might not be that of thecurrent user. Thus we can't just get the account object from the currentuser.

Given the account object, ImageHandler then grabs the saved blob inaccount.profile_pic, and writes that file directly to the browser. Note that thecontent-type must be set.

Given such a request->handler, an HTML template can display an image withcode like the following:

<img src='loadImage?key={{account.key}}' alt='noimage'></img>

The src of 'loadImage' results in ImageHandler being called.{{account.key}} will return the account's key field as the parameter to thecontroller.

Page 22: Chapter 19: Twitter in Twenty Minuteswolber/bookChapters/CS_Text_19_20.pdf · Chapter 19: Twitter in Twenty Minutes In the last chapter, we learned how to create and query persistent

Summary

Web 2.0 is a read-write web with the users being key agents in the process.This chapter has described methods for handling the user accounts andprofiles of an application, and techniques for storing and processing user-generated data like tweets.