Google App Engine

Unleash Django with app-engine-patch

This article was written and submitted by an external developer. The Google App Engine team thanks Jesaja Everling for his time and expertise.

Jesaja Everling
February 2009

WARNING (Nov 2010): The app-engine-patch was deprecated towards the end of 2009. The creators of the Patch have moved on to create a much better tool called Django-nonrel and have told developers the same on its website. Django-nonrel is (currently) a maintained fork of the latest version of Django which allows developers to run native Django applications (via Django's ORM) on traditional SQL databases as well as non-relational datastores (including App Engine's). (This is a significant differentiator to earlier projects like the Helper and the Patch which involve modification of your data model classes.) For more information on using Django-nonrel with App Engine, please see our Django-nonrel article which shows you how to convert a native App Engine webapp app to a pure Django app.

Introduction

The Django framework can make your life as a web developer a lot easier. It takes care of a lot of common problems web developers have to deal with, and offers many "reusable apps" - battle tested pieces of code that you can plug into your project.

Because of a few conceptual differences, several Django features do not work out of the box with Google App Engine.  One of the main features that doesn't work with App Engine is the Django ORM, since the App Engine Datastore differs from a traditional relational database model off of which the Django ORM is based. app-engine-patch is a project that aims for providing all the functionality of Django while working around the limitations imposed by the missing Django ORM support. The project can be found here: http://code.google.com/p/app-engine-patch/

In this article, we will present a few reasons why you may want to consider using Django and app-engine-patch for your projects, and then demonstrate the power of app-engine-patch with a sample application. This sample will support authentication with both Google and non-Google accounts.

Reasons for considering Django over Webapp

The advantage to using Django over Google App Engine's webapp framework is that Django has been widely in use for years, for many types of web applications. Additionally, Django has an extensive developer community. There are numerous blogs dealing with Django, a very frequently used mailing-list, and the #django IRC-channel.

Webapp was developed exclusively for Google App Engine and has yet to build all this sort of community backing. Django has also become the "standard" Python web-framework. There are several other great frameworks, like Pylons or cherrypy, most of which will also work on App Engine, but Django certainly is the most popular one at the moment. It offers many features important for large projects, like built-in internationalization support, caching, authentication with sessions, support for middleware and much more. Webapp is missing most of these features. If you need them, you have to create them yourself. Lastly, Django makes you less dependent on App Engine. If you use webapp, you cannot easily switch to another system, at least at the moment.

Reasons for using app-engine-patch

app-engine-patch ports as much as possible from the Django world to App Engine. You will be able to use a lot of the reusable apps for Django without much adjustments. Porting existing Django code to App Engine will also be much easier. app-engine-patch will also reduce the differences between traditional Django and that used for App Engine. So if, for whatever reason, you wish to switch from App Engine to your own hosting solution in the future it will largely reduce the work required. And it allows you to benefit from the support the large Django community provides. app-engine-patch also ships with a library called ragendja that provides even more features, including transaction decorators and global template tags. For the full list of the features that app-engine-patch provides, have a look at the project's homepage:http://code.google.com/p/app-engine-patch/

Unlike the App Engine Helper for Django, which emulates Django models by using a custom BaseModel, app-engine-patch sticks with the regular Datastore API. This is due to the fact that it's not really possible to emulate the Django models with the App Engine Datastore, and it has the advantage that you can use new Datastore features as soon as they get released.

app-engine-patch provides a number of features that the App Engine Helper for Django is missing. Further details are available on the project homepage. Another important difference is that app-engine-patch tries to support the latest stable release of Django, whereas the Helper ships with version 0.96 (the svn trunk supports version 1.0).

Obtaining app-engine-patch

To get you off to an easy start, app-engine-patch is distributed as a self-contained sample project. You can get the latest release on the project page here: http://code.google.com/p/app-engine-patch/downloads/list. At the time of this writing, 0.9.3. is the latest version.

App-engine-patch needs the App Engine SDK to work: https://developers.google.com/appengine/downloads.

For Windows and Mac OS X, you just have to use the provided installer for the SDK. If you are on Linux, put the SDK in a folder included in your PATH environment variable or in /usr/local/google_appengine. Please make sure the SDK-folder is named google_appengine.

To start the sample project, change to the appenginepatch-sample folder, and execute manage.py runserver from the command line. app-engine-patch will start the App Engine development server behind the scenes, and you are ready to go. If you access http://localhost:8000 in your browser, you will see the sample project in action. The sample demonstrates the use of some of Django's generic views, which provide shortcuts for common tasks like creating or editing model instances.

A practical example

To demonstrate some of the features that app-engine-patch provides, we will use Django to re-create the Guestbook application from here: https://developers.google.com/appengine/docs/python/gettingstartedpython27/usingdatastore. We will use generic views, and add user authentication for both Google and non Google users.

It may initially seem that there is some overhead involved with Django's directory organization. However, the structure is beneficial as it will help you to keep your code organized and reusable, which is important for larger projects.

Django project structure

In Django, a project is split into app packages. It is possible to create certain app functionality in a way that make them independent of a given project. Thus, you can plug packages into multiple Django projects. An example of an app package would be a tagging library for a blog.

In addition to apps, Django projects also contain a global settings file and a root URL-configuration file which defines how URLs are processed by the framework. Apps can have an additional url-configuration to be included in the main URL-configuration file. Django's templates work in a similar fashion. You can have global templates used by all apps, and also app-specific templates.

But enough of the talking, lets get some work done!

Configuring your project

First, let's configure an installation of app-engine-patch so that it's ready for deployment. Open Google App Engine's app.yaml configuration file, and replace the application field with your app id. You can execute manage.py update now to deploy your app to Google App Engine.

If you look at the contents of the sample project, you will see that the sample app lives in a directory called myapp. Let's create another app, guestbook. Create a folder named guestbook, and fill it with these files:

  • __init__.py - So that Python treats this folder as a package
  • urls.py - For app-specific URL-configuration. It controls which view (the request handlers) will be executed for a given URL
  • models.py - For the data models for the app
  • views.py - To obtain the views, which is the Django term for the logic that processes a request
  • templates - A folder for your HTML templates for this app

Let's install the app into our project. To do this, include the name of your app in the list of INSTALLED_APPS in your settings.py file:

INSTALLED_APPS = (
    ...
    'guestbook',
)

If you were using Django with a relational database, you would now have to run manage.py syncdb to create the necessary database tables. With App Engine this happens on the fly.

Now let's hook our guestbook app into the global URL-routing. Change the project's global /urls.py file to include the following line:

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

Now, if you access any URL that starts with /guestbook/, the system will look in the app-specific URL configuration file /guestbook/urls.py to route the request.

Creating the models

Open your /guestbook/models.py file, and create this database model:

from google.appengine.ext import db
from django.contrib.auth.models import User

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

The user model will be provided for us by app-engine-patch, so it does not have to be specified. Since we want Django and Google user authentication at the same time, enable middleware in your settings and specifying the correct user model. Replace Django's AuthenticationMiddleware in /settings.py:

# Replace Django's AuthenticationMiddleware with HybridAuthenticationMiddleware.
MIDDLEWARE_CLASSES = (
    ...
    'ragendja.auth.middleware.HybridAuthenticationMiddleware',
    ...
)

and add

# Change the User model class
AUTH_USER_MODULE = 'ragendja.auth.hybrid_models'

# Add google_login_url and google_logout_url tags
GLOBALTAGS = ('ragendja.templatetags.googletags',)

to the end of the file.

That's all. No, really! Now you can use both Django and Google user authentication. Furthermore, you have activated template tags that can be used to render Google login and logout links in any template. To try it, create a /guestbook/templates/index.html file with this content:

<html>
<head>
</head>
<body>
<div class="login">
  {% if user.is_authenticated %}
    Welcome, {{ user.username }}
    <a href="{% google_logout_url request.get_full_path %}">Logout</a>
  {% else %}
    <a href="{% google_login_url request.get_full_path %}">Login</a>
  {% endif %}
</div>
</body>
</html>

and set the URL-routing in /guestbook/urls.py:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'index.html'}),
)

If you now load http://localhost:8000/guestbook/ in your browser, you will see Google authentication in action. Hard, wasn't it?

Note: Here you also see one of Django's generic views in action by rendering a HTML-template.

Providing access for non-Google accounts

Now let's add the authorization for people without a Google account. We will again make use of generic views as much as possible, because using them is easier and less error-prone than writing the views yourself.

The first thing is to enable users to sign up. Remember the AUTH_USER_MODULE directive we set in settings.py? It will perform some magic that allows us to import the normal Django User model and still have the hybrid authentication support.

Signing up users

To let users sign up for an account, add the following code to /guestbook/views.py:

from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect

def create_new_user(request):
    form = UserCreationForm()
    # if form was submitted, bind form instance.
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user = form.save(commit=False)
            # user must be active for login to work
            user.is_active = True
            user.put()
            return HttpResponseRedirect('/guestbook/login/')
    return render_to_response('guestbook/user_create_form.html', {'form': form})

I won't go into much detail here, suffice it to say that this is nothing else than standard Django. app-engine-patch handles the creation of users behind the scenes, including using the App Engine datastore instead of the normal database-tables used by Django.

The UserCreationForm is automatically supplied with Django. This view creates a form object, and passes it to an HTML-template called user_create_form.html. When a form is submitted and validated via a POST request, a new user will be created, and the user will be redirected to a login page. If the form is not valid, a meaningful error message is rendered.

To see this in action, there are two small things left to do. First, hook the "create_new_user" method into your URL-configuration in /guestbook/urls.py:

urlpatterns = patterns('',
    ...
    (r'^signup/$', 'guestbook.views.create_new_user'),
)

And create a template /guestbook/templates/user_create_form.html:

<html>
<head>
</head>
<body>
<form action="." method="post">
  <table>
    {{form.as_table}}
  </table>
  You can also login with your <a href="{% google_login_url "/guestbook" %}">Google account.</a>
  <input type="submit" value="submit">
</form>
</body>
</html>

Logging in a Django user

In case you hate reading long texts on the monitor as much as I do, I will make this one quick. Just add this to your /guestbook/urls.py:

urlpatterns = patterns('',
    ...
    (r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'guestbook/user_create_form.html'}),
)

Please note that I have reused the template for user creation to save you from doing another copy & paste. That's it. Create a new user or go to http://localhost:8000/guestbook/login/ to see this generic view in action.

Note: When not specified otherwise, the login generic view will redirect to "/accounts/profile/" after successful login. To change this add LOGIN_REDIRECT_URL = "/guestbook/" to settings.py, or logged in users will be greeted by a 404 message.

The Logout link that is displayed currently only logs out Google users. To log out Django users, just include a generic view in your "/guestbook/urls.py" like this:

#the "logout" generic view expects a template logged_out.html. Using this generic view, you can redirect the user to
#another page after log out.
(r'^logout/$', 'django.contrib.auth.views.logout_then_login', {'login_url': '/guestbook/'}),

and replace the logout link in /guestbook/templates/index.html with:

{% if user.is_active %} <a href="/guestbook/logout"> 
{% else %} <a href="{% google_logout_url "/guestbook/" %}">
{% endif %}Logout</a>

This works because Google users do not have the is_active field set. There are better ways to check which type of user we are dealing with, but this is simple and works for our case. The repository version of app-engine-patch includes methods for differentiation between user types.

Getting the guestbook working

Now let's add the ability to create and display guestbook entries. Add the following to the end of /guestbook/views.py:

from django.views.generic.list_detail import object_list
from django.views.generic.create_update import create_object
from django.contrib.auth.decorators import login_required
from guestbook.models import Greeting

def list_entries(request):
    return object_list(request, Greeting.all())

@login_required
def create_entry(request):
    # Add username to POST data, so it gets set in the created model
    # You could also use a hidden form field for example, but this is more secure
    request.POST = request.POST.copy()
    request.POST['author'] = str(request.user.key())
    return create_object(request, Greeting, post_save_redirect='/guestbook')

and change /guestbook/urls.py to:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^$', 'guestbook.views.list_entries'),
    (r'^sign/$', 'guestbook.views.create_entry'),
    (r'^signup/$', 'guestbook.views.create_new_user'),
    (r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'guestbook/user_create_form.html'}),
    (r'^logout/$', 'django.contrib.auth.views.logout_then_login', {'login_url': '/guestbook/'}),
)

The generic views expect templates that you have to create /guestbook/templates/greeting_list.html:

<html>
<head>
</head>
<body>
<div class="login">
  {% if user.is_authenticated %}
    Welcome, {{ user.username }}
    {% if user.is_active %}
      <a href="/guestbook/logout">
    {% else %}
      <a href="{% google_logout_url "/guestbook/" %}">
    {% endif %}Logout</a>
  {% else %}
    <a href="{% google_login_url request.get_full_path %}">Login with your Google account</a><br>
    <a href="/guestbook/login">Login with your normal account</a><br>
    <a href="/guestbook/signup">Sign up</a><br>
  {% endif %}
</div>
{% for object in object_list %}
  <p>{{object.author.username}}: {{object.content}}</p>
{% endfor %}
<a href="/guestbook/sign/">Add entry</a>
</body>
</html>

and /guestbook/templates/greeting_form.html:

<html>
<head>
</head>
<body>
<form method="POST" action=".">
  {{form.content}}
  <input type="submit" value="create">
</form>
</body>
</html>

Signing the guestbook now works. We have replaced the generic view that rendered index.html for the URL /guestbook/ by a reference to the function that shows the list of entries. The login_required decorator provided by Django ensures that a user has to be logged in to access the view in question. By default, the decorator expects the login URL to be located at /accounts/login/ to change this modify your settings.py file with:

LOGIN_URL = '/guestbook/login'

Note: If we wanted anonymous users to be able to sign the guestbook, we would have to take care of the fact that anonymous users don't have a key-attribute.

Conclusion

We now have a (very) basic project that allows users to sign in both with Google and non-Google accounts, and to add entries to a guestbook. We have made extensive use of generic views, to demonstrate how they can make common tasks a lot easier. If this was your first encounter with Django on App Engine, I hope that this article is enough to get you started. If you already are a proficient Django user, I hope we made it interesting for you!

About the Author...

Jesaja Everling

Jesaja Everling lives in the former capital of Germany, Bonn. Having started to learn Python not much more than two years ago, Jesaja was amazed when he learned that he could use his now favorite programming language for web-development. After deciding to try out Django, he never looked back. He had the luck that the company he is working for during his studies of Technical Journalism needed somebody that would get deep down and dirty with Django development.

Being a self-declared early adopter of Google products, he was excited when he heard of App Engine, and started using it as soon as he could. When looking at a computer screen and not working or studying, he tries to get the hang out of using the Datastore, or hangs around in the appengine channel on IRC and swears at his timezone that is constantly making him miss all the interesting discussions.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.