Create a simple Guestbook Application

In this blog post, we would create a simple Guestbook application based on the set up configuration of Django on Google App Engine that we have done previously (part 1,part 2)

If you have gone through the above, your project would have the simple structure like this:

This Guestbook application is based on the Google App Engine for Python 2.7 Tutorial. Apart from that, many references also come from the Official Django Tutorial and the Django Book. I found the materials presented there very concise and easy to understand and I highly recommend them.

To create our application, we would proceed with the following steps:

  1. Create the application folder
  2. Defining the models
  3. Defining the views
  4. Defining the templates
  5. Defining the static file folder
  6. Defining the urls

Create the Application Folder

There are 3 locations our application can be put in the project:

  1. In the root project folder (together with main.py and manage.py and at the same level with Project_Name folder)
  2. In the Project_Name folder
  3. In a dedicated folder for all applications called "apps"

In the 1st and 3rd approach, the application can be referenced like:

from guestbook.models import *

In contrast, in the 2nd approach, the application needs to be referenced like:

from Project_Name.guestbook.models import *

Besides, for the 3rd approach to work, the folder "apps" needs to be added to the system path. Instruction to do so can be found here

So depending on your preference, you can choose the location for your application folder accordingly. In this blog post, we would use the 1st approach, which is also used in the Official Django Tutorial:

We proceed to create a package in the root project folder named guestbook. A folder with an __init__.py is created as a result.


Defining the MODELS

We would use Google App Engine datastore model to create the models for our guestbook application. We proceed to create a PyDev module models (which is just a file models.py) in the guestbook package and define the following:

from google.appengine.ext import ndb

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.'''
    author = ndb.UserProperty()
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)

Defining the VIEWS

We would define the views just like we do for any Django application, with some additional Google App Engine operations to manage the data models. We proceed to create a PyDev module views (which is just a file views.py) in the guestbook package and define the following:

from django.http import HttpResponseRedirect
from django.views.generic.simple import direct_to_template

from google.appengine.api import users

from guestbook.models import Greeting, guestbook_key, DEFAULT_GUESTBOOK_NAME

import urllib

def main_page(request):
    guestbook_name = request.GET.get('guestbook_name', DEFAULT_GUESTBOOK_NAME)
    
    # Ancestor Queries, as shown here, are strongly consistent with the High
    # Replication Datastore. Queries that span entity groups are eventually
    # consistent. If we omitted the ancestor from this query there would be
    # a slight chance that Greeting that had just been written would not
    # show up in a query.
    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(request.get_full_path())
        url_linktext = 'Logout'
    else:
        url = users.create_login_url(request.get_full_path())
        url_linktext = 'Login'

    template_values = {
        'greetings': greetings,
        'guestbook_name': guestbook_name,
        'url': url,
        'url_linktext': url_linktext,
    }
    return direct_to_template(request, 'guestbook/main_page.html', template_values)

def sign_post(request):
    if request.method == 'POST':
        guestbook_name = request.POST.get('guestbook_name')
        greeting = Greeting(parent=guestbook_key(guestbook_name))
    
        if users.get_current_user():
            greeting.author = users.get_current_user()
    
        greeting.content = request.POST.get('content')
        greeting.put()
        return HttpResponseRedirect('/?' + urllib.urlencode({'guestbook_name': guestbook_name}))
    return HttpResponseRedirect('/')

Defining the TEMPLATES

In the main_page view above, we use the HTML template to render the content of the page. We need to configure the TEMPLATE_DIRS in the settings.py file in the Project_Name package:

import os
ROOT_PATH = os.path.dirname(__file__)
TEMPLATE_DIRS = (os.path.join(ROOT_PATH, "templates"),)

This will let Django look into all "templates" folders in the all applications to find the templates to render. Do take note of the comma near the end, it is important that it’s there.

We would also need to add “guestbook” into the list of INSTALLED_APPS:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'guestbook',
)

After that, in the guestbook package, we would create a folder called "templates" and a subfolder called "guestbook" nested under it. This is to differentiate the template folders of different applications. Then, we would create the files main_page.html in the subfolder guestbook:

<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/static/css/main.css" />
  </head>
  <body>
    {% for greeting in greetings %}
      {% if greeting.author.nickname %}
        <b>{{ greeting.author }}</b> wrote:
      {% else %}
        An anonymous person wrote:
      {% endif %}
      <blockquote>{{ greeting.content|escape }}</blockquote>
    {% endfor %}

    <form action="/sign/" method="post">
      {% csrf_token %}
      <input type="hidden" name="guestbook_name" value="{{ guestbook_name }}" />
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>

    <a href="{{ url }}">{{ url_linktext }}</a>

  </body>
</html>

Defining the Static File Folder

In the template above, we serve a static stylesheet file called main.css. To do this, we would creat a folder called "static" in the root project folder. We would instruct Google App Engine to serve all files in this folder statically by adding a static_dir handler in the app.yaml file:

handlers:
  
- url: /static
  static_dir: static
  
- url: /.*
  script: main.application

Then, we would create the "css" folder and the corresponding main.css file in this static folder.

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

Defining the URLs

We would define 2 URLs to serve 2 views main_page and sign_post accordingly. We first modify the urls.py file in the Project_Name package to direct all requests to guestbook application:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^', include('guestbook.urls')),
)

We would then create another PyDev module urls (which is just a file urls.py) in the guestbook package to process all requests related to guestbook application:

from django.conf.urls.defaults import *
from guestbook.views import main_page, sign_post

urlpatterns = patterns('',
    (r'^sign/$', sign_post),
    (r'^$', main_page),
)

Our application has been completed! The final folder structure would look like below:


You can proceed to run and test the application. Please note that when we run our application, a file named index.yaml would be automatically created in the root project folder. This is the index file of Google App Engine.