• ログインログイン
  • 新規登録新規登録

MENU

Google App Engine 上でDjangoを動かしてホームページを作る 第6回

連載Google App Engine 上でDjangoを動かしてホームページを作る

Google App Engine 上でDjangoを動かしてホームページを作る開発レシピです。無料でサーバーを利用できるGoogle App Engineを使い、DjangoというPython言語で書かれた枠組みを用いてホームページを作っていきます。

前回までのプログラミング学習コラムに続き6回目の勉強内容です。

前回、駆け足でしたが、GAE組み込みのウェブフレームワークwebappを使ったサンプルアプリケーションの説明をしました。今回はそれを、Djangoの上に移植していく作業をしていきましょう。箇条書きで書くと下のようになります。

  1. ファイル構造をDjango用に置き換える
  2. モデルを書き換える
  3. リクエストハンドラを書き換える
  4. Urlのひも付けを書き換える

以上のことをした上で、前々回のDjango-nonrel環境の使い方で説明したように、

autoload
dbindexer
django
djangoappengine
djangotoolbox

という五つのファイル(と.yamlファイル)をプロジェクトディレクトリ内に入れればいいのでした。

さて、以上のことについて一つずつ解説していきましょう。

まず1です。ここまで読んでいただいた方は、Djangoがプロジェクト単位でディレクトリを構成するということまでわかっていると思います。その中に、一つ以上のアプリケーションを作成するのでした。

例えば、商品販売ウェブサイトを作ったとすると、そのウェブサイト一つという単位が「プロジェクト」で、その中に、「商品の注文を受け付ける」だったり、「商品のレビュー投稿を受け付けて表示する」といった個々の「アプリケーション」があるわけです。

新規でプロジェクトを作りたい際にはターミナルで、

django-admin.py startproject  プロジェクト名(ここではexample)

というコマンドを実行すると、自動的にプロジェクトディレクトリとその中身が作成されるのでした(以下のようなファイル構造が自動作成されます)。

(赤いボックスがディレクトリを、青いボックスはファイルを表します)
アプリケーションを作成する時には、

python manage.py startapp アプリケーション名(ここではapp)

とすればいいのでした。すると、

manage.pyと同じ階層にアプリケーションディレクトリとその中身が追加されるのでした。ブラウザで表示させるのはhtmlの役割なので、templatesディレクトリを作ってその中にhtmlファイルを入れたり最上階層にいれたりするのでした。それぞれのファイルについての説明は以前、既にしたので、ここでは割愛させていただきます。

このファイル構造(ストラクチャー)に、webappで書いたものを置き換えていくわけです。GAEで書くものはこんな構造をしていたはずです。

シンプルです。Pythonファイルは一個しかありません。main.pyはこのプロジェクトの実行部分ですので、これを分割して、Djangoの構造の中の同じく個々のアプリケーション実行に関わるviews.pyに入れていくことになります。

app.yamlではurlに関する記述をしていました。

handlers:
- url: .*
script: main.py

こんな記述をした覚えがあるはずです。これに該当する部分をurls.pyにも記述していきます。(これは後述します)

テンプレートももちろんDjangoシステムに適合するように変更を加えていきます。

さてまずは、先週書いたmain.pyから見返してみましょう。

import os

from google.appengine.api import memcache, users
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp.template import render
from google.appengine.ext.webapp.util import run_wsgi_app

class Greeting(db.Model):
author = db.UserProperty()
content = db.TextProperty()
date = db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp.RequestHandler):
def get(self):
user = users.get_current_user()
greetings = memcache.get('greetings')
if not greetings:
greetings = Greeting.all().order('-date').fetch(10)
memcache.add('greetings', greetings)
context = {
'user': user,
'greetings': greetings,
'login': users.create_login_url(self.request.url),
'logout': users.create_logout_url(self.request.url),
}
tmpl = os.path.join(os.path.dirname(__file__), 'main.html')
self.response.out.write(render(tmpl, context))

class GuestBook(webapp.RequestHandler):
def post(self):
greeting = Greeting()
greeting.content = self.request.get('content')
greeting.put()
memcache.delete('greetings')
self.redirect('/')

application = webapp.WSGIApplication([
( '/', MainHandler),
( '/sign', GuestBook),
], debug=True)

def main():
run_wsgi_app(application)

if __name__ == '__main__':
main()

8~11行目の部分は、データベースモデルについての記述。13~27行目の部分はデータの入力についての記述ですね。29~35行目のところはpostですのでgetの逆でデータ出力に関係しています。データベースモデルつまり、8~11行目の部分はmodels.pyに、13~27行目、29~35行目の部分はviews.pyにそれぞれ合うように書き換えつつ移植することになります。

まず8~11行目の部分をやりましょう。

以下example/main.py

from google.appengine.ext import db

class Greeting(db.Model):
author = db.UserProperty()
content = db.TextProperty()
date = db.DateTimeProperty(auto_now_add=True)

を、example/example/models.pyの中にこのように書き換えます。

from django.db import models
from django.contrib.auth.models import User

class Greeting(models.Model):
author = models.ForeignKey(User, null=True, blank=True)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)

使うフレームワークを変えるので、使えるモデルメソッドをgoogle.appengine.ext.db.Modelからdjango.db.models.Modelへと変えます。(1、2行目部分)メソッドの呼び出し方がそれぞれ違うので、クラスの中身は変わっています。authorのnull=True, blank=Trueは空白データを格納してもいいよ!とするための記述です。

>>

次に、13~27行目の部分です。これを、example/example/views.pyの中にいれます。

#example/main.py

import os

from google.appengine.api import memcache, users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.template import render

class MainHandler(webapp.RequestHandler):
def get(self):
user = users.get_current_user()
greetings = memcache.get('greetings')
if not greetings:
greetings = Greeting.all().order('-date').fetch(10)
memcache.add('greetings', greetings)
context = {
'user': user,
'greetings': greetings,
'login': users.create_login_url(self.request.uri),
'logout': users.create_logout_url(self.request.uri),
}
tmpl = os.path.join(os.path.dirname(__file__), 'main.html')
self.response.out.write(render(tmpl, context))

これを、example/example/views.pyの中にこう書きます。

# example/example/views.py

from django.core.cache import cache
from django.views.generic.simple import direct_to_template
from app.forms import CreateGreetingForm
from app.models import Greeting

def list_greetings(request):
greetings = cache.get(MEMCACHE_GREETINGS)
if greetings is None:
greetings = Greeting.objects.all().order_by('-date')[:10]
cache.add(MEMCACHE_GREETINGS, greetings)
return direct_to_template(request, 'app/main.html',
{'greetings': greetings, 'form': CreateGreetingForm()})

変えたところは基本的には、modelsと同じような箇所です。importするものともとの場所と、その使い方ですね。例えばクエリの使い方です。

webappでこうだったものが、

greetings = Greeting.all().order('-date').fetch(10)

こうなります。

greetings = Greeting.objects.all().order_by('-date')[:10]

テンプレートにデータを入れるときに、webappの時は、user,greetings,login,logoutという辞書型データを作っていれましたが、今度は、greetings以外は後で作るCreateGreetingFormクラスでまとめてデータを作っています。

入れる先はもちろん、example/templates/app/main.htmlの中です。なぜ、templatesのディレクトリの中にまたappディレクトリを作るかというと、アプリケーションごとにテンプレートディレクトリを分けることで管理をしやすくするためです。

29~35行目の部分はexample/main.pyで次のようになっていた部分を、

class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()
greeting.content = self.request.get('content')
greeting.put()
memcache.delete('greetings')
self.redirect('/')

example/app/views.pyの中に、

from django.http import HttpResponseRedirect
from app.models import Greeting
.
.
.
def create_greeting(request):
# bound form (user input in request)
if request.method == ‘POST’:
form = CreateGreetingForm(request.POST)
if form.is_valid():
greeting = form.save(commit=False)
if request.user.is_authenticated():
greeting.author = request.user
greeting.save()
cache.delete(‘greetings’)
return HttpResponseRedirect(‘/app/’)[/python]

以上のようにします。

またCreateGreetingFormが出てきましたね。これの定義は、

from app.forms import CreateGreetingForm

で定義をimportしていることからわかるように、appディレクトリの中にある、forms.pyの中に定義してあるCreateGreetingFormクラスをimportしているということです。

forms.pyなんてあったっけ?これから書きます。次のように書きましょう。

from django import forms
from app.models import Greeting

class CreateGreetingForm(forms.ModelForm):
class Meta:
model = Greeting
exclude = ('author', 'date') # "fields = ('content',)"と同じ

さて、main.htmlですが、こちらも微調整が必要です。

もともとの次の部分を、

Hello
{% if user %}
{{ user.nickname }}!
[<a href="{{ logout }}"><strong>sign out</strong></a>]
{% else %}
World!
[<a href="{{ login }}"><strong>sign in</strong></a>]
{% endif %}

{% for greeting in greetings %}

<small>[<em>{{ greeting.date.ctime }}</em>]</small>
<strong>
{% if greeting.author %}
<code>{{ greeting.author.nickname }}</code>
{% else %}
<em>anonymous</em>
{% endif %}
</strong>
wrote:
{{ greeting.content|escape }}
{% endfor %}

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

example/templates/app/main.htmlでは次のように変えます。

{% for greeting in greetings %}

<small>[<em>{{ greeting.date.ctime }}</em>]</small>
<strong>
{% if greeting.author %}
<code>{{ greeting.author.username }}</code>
{% else %}
<em>anonymous</em>
{% endif %}
</strong>
wrote:
{{ greeting.content }}
{% endfor %}

<form action="”/app" method="post">{% csrf_token %}{{ form }}<input type="submit" value="Sign " /></form>

もともとのhtmlにあった冒頭の部分が消えてますね?これはDjango違うbase.htmlというファイルに記述します。(もちろん位置はtemplates/app/base.html)

Hello
{% if user.is_authenticated %}
{{ user.username }}!
[<a href="{% url django.contrib.auth.views.logout %}"><strong>sign out</strong></a>]
{% else %}
World!
[<a href="{% url django.contrib.auth.views.login %}"><strong>sign in</strong></a>]
{% endif %}

前半部分を移植します。

以上でhtmlの書き換えは終わりですが、中身で具体的に大きく変わっているのは、四カ所です。GAEのテンプレートシステムと、Djangoのテンプレートシステムで少し使い方が違う部分を直ました。urlテンプレートタグを使っています。

Formの部分も少し変わっていますね。{{ form }}で先に書いたcreat_greeting関数でのformが表示されます。

前に述べたように、app.yamlの中身、

- url: /.*
script: main.py

の意味するところに該当するところをurls.pyの中にも書きます。以下です(fファイルはもともとはないので、作ってください)

#example/app/urls.py
from django.conf.urls.defaults import *

urlpatterns = patterns('app.views',
(r'^$', 'list_greetings'),
(r'^sign$', 'create_greeting'),
)

プロジェクト全体をつかさどるurls.pyはexample/example/urls.pyにあります。これを次のように書きましょう。

from django.conf.urls.defaults import *
from django.contrib.auth.forms import AuthenticationForm

urlpatterns = patterns('',
url(r'^$', 'django.views.generic.simple.redirect_to', {'url': '/app/',}),
url(r'^guestbook/', include('app.urls')),

# auth specific urls
url(r'^accounts/create_user/$' app.views.create_new_user'),
url(r'^accounts/login/$', 'django.contrib.auth.views.login',
{'authentication_form': AuthenticationForm,
'template_name': 'app/login.html',}),
url(r'^accounts/logout/$', 'django.contrib.auth.views.logout',
{'next_page': '/app/',}),
)

プロジェクトの設定に関する.pyファイルにはsettings.pyというものもありました。こちらも、

from djangoappengine.settings_base import *

import os

SECRET_KEY = 'この部分は自分のものに変えてください'

INSTALLED_APPS = (
'djangoappengine',
'djangotoolbox',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'app',
)

MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)

TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.request',
)

LOGIN_REDIRECT_URL = '/guestbook/'

ADMIN_MEDIA_PREFIX = '/media/admin/'
MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'media')
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), 'templates'),)

ROOT_URLCONF = 'urls'

さて、以上です。

これを動かすには、

python manage.py runserver

を実行し、

GAE上にアップするためには、

python manage.py deploy

とすればできるでしょう。

以上でDjangoをGAEで動かすアプリケーションを作ることのだいたいはできるでしょう。

さて、いかがでしたでしょうか。Djangoはinstagram,pinterest,mozillaなどの開発に使われています。GAEもフリーで使えるアプリケーション用ウェブサーバーとしてはかなり高機能ですし、個人で使う分にはふつうは無料ですむでしょう。

あまり互換性良いとは言えませんでしたが、このようにDjango-nonrelを使うことで、DjangoをGAEで動かすことができるようになりました。

オススメ記事一覧

もっと見る
完全無料!

1で登録完了!

エンジニアの仕事・年収や選考ノウハウ記事が読めるほか、
会員にはプログラミング講習やES・面接対策などリアルな無料サポートも充実。
ここだけの求人情報も多数。

今すぐ新規会員登録
Page Top