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 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.