前回までのプログラミング学習コラムに続き6回目の勉強内容です。
前回、駆け足でしたが、GAE組み込みのウェブフレームワークwebappを使ったサンプルアプリケーションの説明をしました。今回はそれを、Djangoの上に移植していく作業をしていきましょう。箇条書きで書くと下のようになります。
- ファイル構造をDjango用に置き換える
- モデルを書き換える
- リクエストハンドラを書き換える
- 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で動かすことができるようになりました。