|
| 1 | +# Copyright 2015 Google Inc. All rights reserved. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +# [START all] |
| 16 | +import cgi |
| 17 | +import cStringIO |
| 18 | +import logging |
| 19 | +import urllib |
| 20 | +import webapp2 |
| 21 | + |
| 22 | +from google.appengine.ext import ndb |
| 23 | +from google.appengine.api import memcache |
| 24 | +from google.appengine.api import users |
| 25 | + |
| 26 | + |
| 27 | +class Greeting(ndb.Model): |
| 28 | + """Models an individual Guestbook entry with author, content, and date.""" |
| 29 | + author = ndb.StringProperty() |
| 30 | + content = ndb.StringProperty() |
| 31 | + date = ndb.DateTimeProperty(auto_now_add=True) |
| 32 | + |
| 33 | + |
| 34 | +def guestbook_key(guestbook_name=None): |
| 35 | + """Constructs a Datastore key for a Guestbook entity with guestbook_name.""" |
| 36 | + return ndb.Key('Guestbook', guestbook_name or 'default_guestbook') |
| 37 | + |
| 38 | + |
| 39 | +class MainPage(webapp2.RequestHandler): |
| 40 | + def get(self): |
| 41 | + self.response.out.write('<html><body>') |
| 42 | + guestbook_name = self.request.get('guestbook_name') |
| 43 | + |
| 44 | + greetings = self.get_greetings(guestbook_name) |
| 45 | + stats = memcache.get_stats() |
| 46 | + |
| 47 | + self.response.write('<b>Cache Hits:{}</b><br>'.format(stats['hits'])) |
| 48 | + self.response.write('<b>Cache Misses:{}</b><br><br>'.format( |
| 49 | + stats['misses'])) |
| 50 | + self.response.write(greetings) |
| 51 | + |
| 52 | + self.response.write(""" |
| 53 | + <form action="/sign?{}" method="post"> |
| 54 | + <div><textarea name="content" rows="3" cols="60"></textarea></div> |
| 55 | + <div><input type="submit" value="Sign Guestbook"></div> |
| 56 | + </form> |
| 57 | + <hr> |
| 58 | + <form>Guestbook name: <input value="{}" name="guestbook_name"> |
| 59 | + <input type="submit" value="switch"></form> |
| 60 | + </body> |
| 61 | + </html>""".format(urllib.urlencode({'guestbook_name': guestbook_name}), |
| 62 | + cgi.escape(guestbook_name))) |
| 63 | + |
| 64 | + # [START check_memcache] |
| 65 | + def get_greetings(self, guestbook_name): |
| 66 | + """ |
| 67 | + get_greetings() |
| 68 | + Checks the cache to see if there are cached greetings. |
| 69 | + If not, call render_greetings and set the cache |
| 70 | +
|
| 71 | + Args: |
| 72 | + guestbook_name: Guestbook entity group key (string). |
| 73 | +
|
| 74 | + Returns: |
| 75 | + A string of HTML containing greetings. |
| 76 | + """ |
| 77 | + greetings = memcache.get('{}:greetings'.format(guestbook_name)) |
| 78 | + if greetings is None: |
| 79 | + greetings = self.render_greetings(guestbook_name) |
| 80 | + if not memcache.add('{}:greetings'.format(guestbook_name), |
| 81 | + greetings, 10): |
| 82 | + logging.error('Memcache set failed.') |
| 83 | + return greetings |
| 84 | + # [END check_memcache] |
| 85 | + |
| 86 | + # [START query_datastore] |
| 87 | + def render_greetings(self, guestbook_name): |
| 88 | + """ |
| 89 | + render_greetings() |
| 90 | + Queries the database for greetings, iterate through the |
| 91 | + results and create the HTML. |
| 92 | +
|
| 93 | + Args: |
| 94 | + guestbook_name: Guestbook entity group key (string). |
| 95 | +
|
| 96 | + Returns: |
| 97 | + A string of HTML containing greetings |
| 98 | + """ |
| 99 | + greetings = ndb.gql('SELECT * ' |
| 100 | + 'FROM Greeting ' |
| 101 | + 'WHERE ANCESTOR IS :1 ' |
| 102 | + 'ORDER BY date DESC LIMIT 10', |
| 103 | + guestbook_key(guestbook_name)) |
| 104 | + output = cStringIO.StringIO() |
| 105 | + for greeting in greetings: |
| 106 | + if greeting.author: |
| 107 | + output.write('<b>{}</b> wrote:'.format(greeting.author)) |
| 108 | + else: |
| 109 | + output.write('An anonymous person wrote:') |
| 110 | + output.write('<blockquote>{}</blockquote>'.format( |
| 111 | + cgi.escape(greeting.content))) |
| 112 | + return output.getvalue() |
| 113 | + # [END query_datastore] |
| 114 | + |
| 115 | + |
| 116 | +class Guestbook(webapp2.RequestHandler): |
| 117 | + def post(self): |
| 118 | + # We set the same parent key on the 'Greeting' to ensure each greeting |
| 119 | + # is in the same entity group. Queries across the single entity group |
| 120 | + # are strongly consistent. However, the write rate to a single entity |
| 121 | + # group is limited to ~1/second. |
| 122 | + guestbook_name = self.request.get('guestbook_name') |
| 123 | + greeting = Greeting(parent=guestbook_key(guestbook_name)) |
| 124 | + |
| 125 | + if users.get_current_user(): |
| 126 | + greeting.author = users.get_current_user().nickname() |
| 127 | + |
| 128 | + greeting.content = self.request.get('content') |
| 129 | + greeting.put() |
| 130 | + self.redirect('/?' + |
| 131 | + urllib.urlencode({'guestbook_name': guestbook_name})) |
| 132 | + |
| 133 | + |
| 134 | +app = webapp2.WSGIApplication([('/', MainPage), |
| 135 | + ('/sign', Guestbook)], |
| 136 | + debug=True) |
| 137 | + |
| 138 | +# [END all] |
0 commit comments