diff --git a/appengine/datastore_namespaces/README.md b/appengine/datastore_namespaces/README.md new file mode 100644 index 000000000000..3fa4109bdb21 --- /dev/null +++ b/appengine/datastore_namespaces/README.md @@ -0,0 +1,32 @@ +# Guestbook with Namespaces + +This application implements the Python [Guestbook sample][7] but uses +datastore namespaces to keep values in separate places. + +Guestbook is an example application showing basic usage of Google App +Engine. Users can read & write text messages and optionaly log-in with +their Google account. Messages are stored in App Engine (NoSQL) +High Replication Datastore (HRD) and retrieved using a strongly consistent +(ancestor) query. + +## Products +- [App Engine][1] + +## Language +- [Python][2] + +## APIs +- [NDB Datastore API][3] +- [Users API][4] + +## Dependencies +- [webapp2][5] +- [jinja2][6] + +[1]: https://developers.google.com/appengine +[2]: https://python.org +[3]: https://developers.google.com/appengine/docs/python/ndb/ +[4]: https://developers.google.com/appengine/docs/python/users/ +[5]: http://webapp-improved.appspot.com/ +[6]: http://jinja.pocoo.org/docs/ +[7]: https://github.com/GoogleCloudPlatform/appengine-guestbook-python/tree/part6-staticfiles diff --git a/appengine/datastore_namespaces/__init__.py b/appengine/datastore_namespaces/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/appengine/datastore_namespaces/app.yaml b/appengine/datastore_namespaces/app.yaml new file mode 100644 index 000000000000..3ee4fce473c0 --- /dev/null +++ b/appengine/datastore_namespaces/app.yaml @@ -0,0 +1,18 @@ +application: guestbook-namespaces +version: 1 +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: /stylesheets + static_dir: stylesheets + +- url: /.* + script: guestbook.application + +libraries: +- name: webapp2 + version: latest +- name: jinja2 + version: latest diff --git a/appengine/datastore_namespaces/appengine_config.py b/appengine/datastore_namespaces/appengine_config.py new file mode 100644 index 000000000000..8f60834bb7e1 --- /dev/null +++ b/appengine/datastore_namespaces/appengine_config.py @@ -0,0 +1,73 @@ +# Copyright 2010 Google Inc. All Rights Reserved. + +""" +Manages the namespace for the application. + +This file presents ways an ISV (Independent Software Vendor) might use +namespaces to distribute the guestbook application to different corporate +clients. The original guestbook.py is left unchanged. Our namespace choosing +hook is run when datastore or memcache attempt to resolve the namespace. +When defined in appengine_config.py the lib_config mechanism substitutes this +function for the default definition which returns None. This hopefully shows +how easy it can be to make an existing app namespace aware. + +Setting _NAMESPACE_PICKER has the following effects: + +If _USE_SERVER_NAME, we read the server name +foo.guestbook-isv.appspot.com and set the namespace. + +If _USE_GOOGLE_APPS_DOMAIN, we allow the namespace manager to infer the +namespace from the request. + +If _USE_COOKIE, then the ISV might have a gateway page that sets a cookie +called 'namespace' for example, and we read this cookie and set the namespace +to its value. Note this is not a secure use of cookies. + +Other possibilities not implemented here include using a mapping from user to +namespace and possibly setting a namespace cookie from this mapping. If the +mapping is stored in datastore, we would probably not wish to look it up on +every query. +""" + +__author__ = 'nverne@google.com (Nicholas Verne)' + + +import Cookie +import os + +from google.appengine.api import namespace_manager + + +_USE_SERVER_NAME = 0 +_USE_GOOGLE_APPS_DOMAIN = 1 +_USE_COOKIE = 2 + +_NAMESPACE_PICKER = _USE_SERVER_NAME + + +def namespace_manager_default_namespace_for_request(): + """Determine which namespace is to be used for a request. + + The value of _NAMESPACE_PICKER has the following effects: + + If _USE_SERVER_NAME, we read server name + foo.guestbook-isv.appspot.com and set the namespace. + + If _USE_GOOGLE_APPS_DOMAIN, we allow the namespace manager to infer + the namespace from the request. + + If _USE_COOKIE, then the ISV might have a gateway page that sets a + cookie called 'namespace', and we set the namespace to the cookie's value + """ + name = None + + if _NAMESPACE_PICKER == _USE_SERVER_NAME: + name = os.environ['SERVER_NAME'] + elif _NAMESPACE_PICKER == _USE_GOOGLE_APPS_DOMAIN: + name = namespace_manager.google_apps_namespace() + elif _NAMESPACE_PICKER == _USE_COOKIE: + cookies = os.getenv('HTTP_COOKIE', None) + if cookies: + name = Cookie.BaseCookie(cookies).get('namespace') + + return name diff --git a/appengine/datastore_namespaces/guestbook.py b/appengine/datastore_namespaces/guestbook.py new file mode 100644 index 000000000000..86c011d80eab --- /dev/null +++ b/appengine/datastore_namespaces/guestbook.py @@ -0,0 +1,85 @@ +import os +import urllib + +from google.appengine.api import users +from google.appengine.ext import ndb + +import jinja2 + +import webapp2 + +JINJA_ENVIRONMENT = jinja2.Environment( + loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), + extensions=['jinja2.ext.autoescape']) + +DEFAULT_GUESTBOOK_NAME = 'default_guestbook' + + +# We set a parent key on the 'Greetings' to ensure that they are all in the +# same entity group. Queries across the single entity group will be consistent. +# However, the write rate should be limited to ~1/second. + +def guestbook_key(guestbook_name=DEFAULT_GUESTBOOK_NAME): + """Constructs a Datastore key for a Guestbook entity with guestbook_name""" + return ndb.Key('Guestbook', guestbook_name) + + +class Greeting(ndb.Model): + """Models an individual Guestbook entry with author, content, and date.""" + author = ndb.UserProperty() + content = ndb.StringProperty(indexed=False) + date = ndb.DateTimeProperty(auto_now_add=True) + + +class MainPage(webapp2.RequestHandler): + + def get(self): + guestbook_name = self.request.get('guestbook_name', + DEFAULT_GUESTBOOK_NAME) + greetings_query = Greeting.query( + ancestor=guestbook_key(guestbook_name)).order(-Greeting.date) + greetings = greetings_query.fetch(10) + + if users.get_current_user(): + url = users.create_logout_url(self.request.uri) + url_linktext = 'Logout' + else: + url = users.create_login_url(self.request.uri) + url_linktext = 'Login' + + template_values = { + 'greetings': greetings, + 'guestbook_name': urllib.quote_plus(guestbook_name), + 'url': url, + 'url_linktext': url_linktext, + } + + template = JINJA_ENVIRONMENT.get_template('index.html') + self.response.write(template.render(template_values)) + + +class Guestbook(webapp2.RequestHandler): + + def post(self): + # We set the same parent key on the 'Greeting' to ensure each Greeting + # is in the same entity group. Queries across the single entity group + # will be consistent. However, the write rate to a single entity group + # should be limited to ~1/second. + guestbook_name = self.request.get('guestbook_name', + DEFAULT_GUESTBOOK_NAME) + greeting = Greeting(parent=guestbook_key(guestbook_name)) + + if users.get_current_user(): + greeting.author = users.get_current_user() + + greeting.content = self.request.get('content') + greeting.put() + + query_params = {'guestbook_name': guestbook_name} + self.redirect('/?' + urllib.urlencode(query_params)) + + +application = webapp2.WSGIApplication([ + ('/', MainPage), + ('/sign', Guestbook), +], debug=True) diff --git a/appengine/datastore_namespaces/index.html b/appengine/datastore_namespaces/index.html new file mode 100644 index 000000000000..68481ebd58e0 --- /dev/null +++ b/appengine/datastore_namespaces/index.html @@ -0,0 +1,35 @@ + +{% autoescape true %} + + + + + + + + {% for greeting in greetings %} + {% if greeting.author %} + {{ greeting.author.nickname() }} wrote: + {% else %} + An anonymous person wrote: + {% endif %} +
{{ greeting.content }}
+ {% endfor %} + +
+
+
+
+ +
+ +
Guestbook name: + + +
+ + {{ url_linktext }} + + + +{% endautoescape %} diff --git a/appengine/datastore_namespaces/index.yaml b/appengine/datastore_namespaces/index.yaml new file mode 100644 index 000000000000..41d3b929a6bb --- /dev/null +++ b/appengine/datastore_namespaces/index.yaml @@ -0,0 +1,6 @@ +indexes: +- kind: Greeting + ancestor: yes + properties: + - name: date + direction: desc diff --git a/appengine/datastore_namespaces/stylesheets/main.css b/appengine/datastore_namespaces/stylesheets/main.css new file mode 100644 index 000000000000..a8666e2666a1 --- /dev/null +++ b/appengine/datastore_namespaces/stylesheets/main.css @@ -0,0 +1,4 @@ +body { + font-family: Verdana, Helvetica, sans-serif; + background-color: #DDDDDD; +}