Google App EngineでDjango + Djangaeを動かしてみた

GoogleAppEngine/Python(以下、GAE)でDjangoを動かしたときのメモを残します。

 

事前調査

GAEに含まれているバージョンについて

GAEにどのバージョンが含まれているかを調べてみたところ、Django1.5をalphaサポートしているようでした。
Libraries in Python 2.7 - Python — Google Cloud Platform

ただ、現在の最新版は1.9であり、LTSも1.8なので、そのままでは使えなさそうでした。

 

GAEへDjangoを持ち込むことについて

以下の公式ドキュメントより、Django1.8をGAEへ持ち込めそうでした。
Libraries in Python 2.7 - Python — Google Cloud Platform

ただ、GAEのデータストアはNDB storage APIとかで扱うなど、そのままでは使いづらそうでした。

そのため、その部分をうまくやるライブラリがないかを探したところ、Django nonrelDjangaeがありました。以下によるとDjangaeの方がメンテナンスされていそうでした。

 
Qiitaの記事では、Django1.8とdjangae-scaffoldで動作確認をとっていたため、今回は

  • djangae-scaffold無し
  • Django1.8 + Djangae

での動作確認をとることにしました。

 

環境

  • Windows10
  • Google App Engine SDK for Python 1.9.30-2015-11-18
    • デフォルトでインストール
  • Python 2.7.11
    • デフォルトでインストール、インストール先はc:\python27
  • virtualenv環境でpip install
    • Django 1.8.7
      • プロジェクト名: myproject
      • アプリ名: mysite
      • Class-based Viewsを使用するアプリ
    • Djangae 0.9.2

 
なお、ディレクトリ構成は以下の通りです。

d:\Sandbox\djangae-thinkami-sample
├── env\ (virtualenv環境)
│    └── ...
├── libs\  (GAEに持ち込むサードパーティライブラリのインストール先)
│    └── ...
├── myproject\  (Djangoプロジェクトのディレクトリ)
│    ├── settings.py
│    └── ...
├── mysite\  (Djangoアプリのディレクトリ)
│    ├── fixtures\
│    ├── templates\
│    ├── migrations\
│    └── ...
├── app.yaml
├── appengine_config.py
├── manage.py
├── requirements.txt
└── ...

 

流れ

pip install
# Djangoプロジェクト用のディレクトリを作成
(env) d:\Sandbox>mkdir djangae-thinkami-sample
(env) d:\Sandbox>cd djangae-thinkami-sample

# virtualenv環境を作り、activate
d:\Sandbox\djangae-thinkami-sample>virtualenv -p c:\python27\python.exe env
d:\Sandbox\djangae-thinkami-sample>env\Scripts\activate

# pip install: GAEへ持っていくため、`-t`オプションを使ってDjango/Djangaeをインストール
(env) d:\Sandbox\djangae-thinkami-sample>pip install -t libs django==1.8.7
(env) d:\Sandbox\djangae-thinkami-sample>pip install djangae -t libs

# 確認
(env) d:\Sandbox\djangae-thinkami-sample>pip list
djangae (0.9.2)
Django (1.8.7)
pip (7.1.2)
setuptools (18.2)
wheel (0.24.0)

 

Djangoプロジェクト・アプリのひな形を作成

以下を参考に、Djangoプロジェクトを現在のディレクトリへ展開します。
python - Force django-admin startproject if project folder already exists - Stack Overflow

# 現在のディレクトリにDjangoプロジェクトを作成
# 末尾の`.`を忘れずに付ける
(env) d:\Sandbox\djangae-thinkami-sample>python .\libs\django\bin\django-admin.py startproject myproject .

# Djangoアプリを作成
(env) d:\Sandbox\djangae-thinkami-sample>python manage.py startapp mysite

 

Djangae向けの設定を実施

以下のDjangaeの公式ドキュメントをもとに、各種設定を行います。
Installation & Deployment - Djangae Documentation

なお、Python2.7系のため、コメントに日本語を使う場合は# -*- coding: utf-8 -*-をファイルの先頭に追加します。

 

d:\Sandbox\djangae-thinkami-sample\app.yaml

GAEで必要な設定を行うapp.yamlファイルを作成します

application: djangaethinkamisample
version: 20151228
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /_ah/(mapreduce|queue|warmup).*
  script: myproject.wsgi.application
  login: admin

- url: /.*
  script: myproject.wsgi.application

なお、app.yamlの詳細は以下の公式ドキュメントにあります。
Configuring with app.yaml - Python — Google Cloud Platform

 

d:\Sandbox\djangae-thinkami-sample\myproject\settings.py

先頭にdjangae.settings_baseのimportを追加します

from djangae.settings_base import *

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os

 
INSTALL_APPS追加します

なお、djangaeが先頭でない場合、You must place 'djangae' before any 'django' apps in INSTALLED_APPSというエラーになります。

INSTALLED_APPS = (
    'djangae',   # add thinkAmi
    'django.contrib.admin',
    ...
    'django.contrib.staticfiles',
    'mysite',   # add thinkAmi
)

 
公式ドキュメントでも推奨されているため、MIDDLEWARE_CLASSESdjangae.contrib.security.middleware.AppEngineSecurityMiddleware追加します

MIDDLEWARE_CLASSES = (
    'djangae.contrib.security.middleware.AppEngineSecurityMiddleware', # add thinkAmi
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
)

 

d:\Sandbox\djangae-thinkami-sample\manage.py

このあたりに以下の一行を変更します。

# from django.core.management import execute_from_command_line
from djangae.core.management import execute_from_command_line

 

d:\Sandbox\djangae-thinkami-sample\myproject\wsgi.py

このあたりを変更します。

# Modify thinkAmi: djangae settings
# application = get_wsgi_application()
from djangae.wsgi import DjangaeApplication
application = DjangaeApplication(get_wsgi_application())

 

ローカルにてIt Works!の確認

この時点で起動すると、DjangoIt Works!が表示されるはずです。

そこで、runserver後にhttp://localhost:8000へとアクセスしたところ、ブラウザにエラーが表示されました。

また、コマンドプロンプトには

(env) d:\Sandbox\djangae-thinkami-sample>python manage.py runserver
...
ImportError: No module named django.core.wsgi

という、Djangoを認識してなさそうなエラーがありました。

 
そのため、GAEの公式ドキュメントなどを参考に、以下の内容でappengine_config.pyを作成します。

from google.appengine.ext import vendor
vendor.add('libs')

 
なお、上記でも動作しない場合、環境変数PYTHONPATHも設定します。

(env) d:\Sandbox\djangae-thinkami-sample>set PYTHONPATH=.\libs

 
再度、runserver後にhttp://localhost:8000へとアクセスすると、別のエラーが表示されました。

(env) d:\Sandbox\djangae-thinkami-sample>python manage.py runserver
...
ImportError: No module named msvcrt

 
そのため、stackoverflowを参考にappengine_config.py追記します
python - Django 1.7 on App Engine "ImportError: No module named msvcrt" - Stack Overflow

import os
on_appengine = os.environ.get('SERVER_SOFTWARE','').startswith('Development')
if on_appengine and os.name == 'nt':
    os.name = None

 
もう一度runserver後にhttp://localhost:8000へとアクセスすると

It worked!
Congratulations on your first Django-powered page.

が表示されました。

 

GAE上にてIt Works!の確認

GAE上でも問題なく動作するかを確認します。

まずはGoogle Cloud Platform Consoleへアクセスし、GAEのアプリを作成します。

続いて、appcfg.pyを使って、ソースコードをGAEへデプロイします。

(env) d:\Sandbox\djangae-thinkami-sample>python "C:\Program Files (x86)\Google\google_appengine\appcfg.py" update ./
...
10:38 PM Completed update of app: djangaethinkamisample, version: 20151228

 
なお、初回の場合、途中で認証を求められることがありますが、許可することで

  • ブラウザにThe authentication flow has completed.が表示
  • コマンドプロンプトでは、中断していたデプロイ処理が再開・続行

となります。

 
デプロイ完了後、http://<your_app_name>.appspot.comへとアクセスすると、

Error: Server Error

The server encountered an error and could not complete your request.
...

というエラーが表示されました。

Applications Overviewにてログを見ると、

ImproperlyConfigured: Error loading either pysqlite2 or sqlite3 modules (tried in that order): No module named _sqlite3

というエラーがありました。データベース設定を修正していないため、GAEでSQLiteを使おうとしてエラーになったようです。

そのため、以下のDjangaeの公式ドキュメントを参考にして、DATABASESの設定を修正します
The Database Backend - Djangae Documentation

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
# }
from djangae.utils import on_production

DATABASES = {
    'default': {
        'ENGINE': 'djangae.db.backends.appengine'
    }
}
if on_production():
    pass
else:
    DATABASES['sql'] = {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'development.sqlite3'
    }

 
修正後、再度アップロードしてブラウザでアクセスすると、

It worked!
Congratulations on your first Django-powered page.

が表示され、GAEでDjangoの動作確認がとれました。

ここまでのDjangoアプリの全体は以下となります。
thinkAmi-sandbox/Django_Djangae_sample at 0fd064f0c1ac9ead769e70b87ee031f1a3606ed6

 

Djangoアプリの機能を追加

実装内容

次に、Djangoアプリで以下が実現できるような機能を追加します。

  1. http://<your_app_name>.appspot.com/mysite/registerへアクセス
  2. フォームへ入力し、Saveボタンを押す
  3. フォーム内容をModel(= GAEのデータストア)に保存
  4. http://<your_app_name>.appspot.com/mysite/<保存した際のID>へリダイレクトし、Modelの内容を更新可能な状態で表示

 
そのため、以下を実装します(実装の詳細は省略)。

  • myproject\urls.py
    • urlpatternsmysiteアプリ用のエントリを追加
  • mysite\urls.py
    • urlpatternsに必要なエントリを追加
  • mysite\models.py
    • ForeignKeyを試したいため、ForeignKeyを持つImpressionと、その参照先のCultivar用意
  • mysite\forms.py
    • Impressionモデルを使ったModelFormのDetailForm用意
  • mysite\widgets.py
    • フォームで<input type=date>を使うため、ModelFormで使うWidgetsを用意
  • mysite\views.py
    • Class-based Viewとして、登録用のImpressionRegisterViewと更新用のImpressionUpdateView用意
  • mysite\templates.html
    • viewで使われるテンプレートを用意
  • fixtures\cultivar.yaml
    • ForeignKeyの参照先向けのfixtureを用意
  • app.yaml
    • 今まで追加していない_ahのhandlerを追加と、fixture用にGAEのyamlライブラリを追加

 
また、フォームの内容は以下の通りです。

f:id:thinkAmi:20151231003328p:plain

 

ローカルでの確認

動作確認を行うため、マイグレーションなどを行います。

# マイグレーションファイルの作成
(env) d:\Sandbox\djangae-thinkami-sample>python manage.py makemigrations

# マイグレーション
(env) d:\Sandbox\djangae-thinkami-sample>python manage.py migrate

# Cultivarモデルにfixtureで初期データを投入
(env) d:\Sandbox\djangae-thinkami-sample>python manage.py loaddata cultivar
...
Installed 3 object(s) from 1 fixture(s)

 
その後にrunserverを行い、http://localhost:8000/mysite/registerにアクセスして表示・保存ができることを確認します。

 

GAEでの確認

GAEにアップロード後、Djangaeのドキュメントに従いマイグレーションを行います。
Local/remote management commands - Djangae Documentation

# アップロード
(env) d:\Sandbox\djangae-thinkami-sample>python "C:\Program Files (x86)\Google\google_appengine\appcfg.py" update ./

# マイグレーション
(env) d:\Sandbox\djangae-thinkami-sample>python manage.py --sandbox=remote migrate
...
urllib2.HTTPError: HTTP Error 404: Not Found Unexpected HTTP status 404

 
エラーメッセージを調べてみたところ、Remote API for Pythonまわりの問題でした。

 
そこで、app.yamlにRemote API for Pythonを使う設定を追加します

builtins:
- remote_api: on

 
再度マイグレーションを行うと、

(env) d:\Sandbox\djangae-thinkami-sample>python manage.py --sandbox=remote migrate
Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?...

Enter verification code: 

のように、コマンドプロンプトにURLが表示されます。

ブラウザを起動してそのURLをアドレスに入力すると、認証が求められます。

許可するとブラウザに

このコードをコピーし、アプリケーションに切り替えて貼り付けてください:

<your code>

が表示されます。

<your code>コマンドプロンプトに貼り付けて実行すると、マイグレーションが行われました。

...
Authentication successful.
System check identified some issues:
...
Running migrations:
...
  Applying mysite.0001_initial... OK
  Applying sessions.0001_initial... OK

 
続いてloaddataを行います。

(env) d:\Sandbox\djangae-thinkami-sample>python manage.py --sandbox=remote loaddata cultivar
...
Installed 3 object(s) from 1 fixture(s)

 
http://<your_app_name>.appspot.com/mysite/registerへアクセスして保存し、動作を確認します。

f:id:thinkAmi:20151231005702p:plain

 
また、GAEのデータストアにも登録されていました。

f:id:thinkAmi:20151231005740p:plain

 
以上で、Google App EngineDjango + Djangaeが動作することを確認できました。

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/Django_Djangae_sample

 

その他参考

GAEのサンプルについて

Google Cloud Platform - GitHubにてDjangoを検索すると、サンプルがいくつかありました。

GoogleCloudPlatform/appengine-django-skeletonSDKではなく、pip -tのものを使っていました。

 

インスタンス時間について

以下が参考になりました。
株式会社ジェニシス 技術開発事業部ブログ: 【GAE】インスタンス時間1